[Breaking] Deprecate Orbital.dx2 with Orbital.dx2_y2 - #94
Conversation
be9ac88 to
c4279b5
Compare
|
🤖 Automated PR review generated by Claude, posted on behalf of @shyuep. PR is marked draft, so this is preliminary — based on static analysis of the diff; no code was executed locally. Clean, well-scoped deprecation of
LGTM in direction; ready for full review once taken out of draft. |
c4279b5 to
4d6171e
Compare
4d6171e to
488af2b
Compare
shyuep
left a comment
There was a problem hiding this comment.
Automated PR review generated by Claude (on behalf of @shyuep; re-review after new commits)
Overall this is a clean deprecation: legacy dx2 handling in from_dict/from_old_dict paths, one-time DeprecationWarnings, and good test coverage. Full CI matrix is green. A few points:
_OrbitalMeta.__getattribute__overhead — this intercepts every class-level attribute access onOrbital, including hot parsing loops (Orbital.s,Orbital(kk), etc.). Sincedx2no longer exists as a member, defining__getattr__on the metaclass instead would only fire on missing names and be zero-cost for everything else.- Serialization forward-compat (your open checkbox) — agreed this is the main risk: dicts emitted by
as_dict()will now containdx2_y2, which older pymatgen versions and downstream consumers (atomate2, emmet, lobsterpy) that key on"dx2"cannot read. Suggest either emitting the legacy key for one deprecation cycle or explicitly calling this out in the release notes as a schema change. cohp.pykey rewrite —orb.replace("dx2", "dx2_y2")guarded by"dx2_y2" in orbworks, but a more targeted regex (or splitting the label on-) would be more robust against future label formats.- Pickle compat is fine since
Orbitalreduces by value anddx2_y2keeps index 8.
Marked as draft, so treat this as preliminary feedback. Nice work on the exhaustive IO-path updates (cp2k, jdftx, vaspout.h5).
488af2b to
bc632ff
Compare
shyuep
left a comment
There was a problem hiding this comment.
Automated PR review generated by Claude (on behalf of @shyuep)
Re-review of the new commits (rename + legacy-serialization compatibility). The deserialization shims in bandstructure.from_old_dict, CompleteCohp.from_dict and CompleteDos.from_dict look correct, with single-shot warnings — good.
_OrbitalMeta.__getattribute__intercepts every class-attribute access onOrbital(including enum-internal machinery and hot loops over members). Sincedx2no longer exists as a member, a metaclass__getattr__would fire only on the missing name and is essentially free for all other access. Recommend switching.canonical_orb = orb if "dx2_y2" in orb else orb.replace("dx2", "dx2_y2")— fine for LOBSTER-style labels, but a brief comment on the expected key format would help.- Your own flag on
as_dict()now emittingdx2_y2is the real breaking change for downstream consumers. Suggest an explicit changelog/release-note entry, and consider keeping serialization output as-is for one release with only reads accepting both.
CI green (5/5). Otherwise LGTM.
bc632ff to
711d5e1
Compare
|
Let's mark this as draft for now, I still haven't got time to check potential downstream effect and at least we should implement #120 first... |
Orbital.dx2 with Orbital.dx2_y2Orbital.dx2 with Orbital.dx2_y2
shyuep
left a comment
There was a problem hiding this comment.
Automated PR review generated by Claude on behalf of @shyuep.
Re-review after the force-push (3067c49/711d5e1). The rename is complete and consistent across dos, plotter, cp2k, jdftx and vasp/outputs, and the read-side shims with one-shot DeprecationWarnings are correct. However, all three points from the previous review are still open:
_OrbitalMeta.__getattribute__still intercepts every class-attribute access onOrbital, including enum internals and hot loops (Orbital.dxy,Orbital.dz2, ... inget_site_t2g_eg_resolved_dos). Sincedx2is no longer a member, a metaclass__getattr__fires only on the missing name and costs nothing otherwise. Please switch.canonical_orb = orb if "dx2_y2" in orb else orb.replace("dx2", "dx2_y2")still has no comment on the expected LOBSTER key format, and would also rewrite an unrelated substringdx2in a composite label.- No
CHANGES.md/COMPATIBILITY.mdentry.as_dict()now emitsdx2_y2, which is the actual breaking change for downstream consumers — see #128 for the new[breaking]convention.
CI green (5/5). Still marked draft, so no merge action taken.
711d5e1 to
594a234
Compare
shyuep
left a comment
There was a problem hiding this comment.
Automated PR review generated by Claude (posted on behalf of @shyuep — not a human review; please treat as advisory).
Re-reviewing after the new commit. CI is fully green (lint + all four test matrix jobs). The rename itself is correct — dx2 was always a misnomer for d_{x^2-y^2} — and the legacy-name handling in from_dict/from_old_dict for CompleteDos, CompleteCohp and BandStructure covers the read path well, with the warned_legacy_dx2 flag keeping the warning to one per deserialization.
On the open question in the PR description (downstream effects of as_dict()): the read path is handled, but the write path is the real break. After this PR as_dict() emits dx2_y2, so files written by new pymatgen cannot be read by older pymatgen or by any external consumer that keys on "dx2" (LOBSTER post-processing tooling, MP/emmet documents, atomate2 parsers). Two options:
- Conservative: keep writing
"dx2"inas_dict()for one release cycle while accepting both on read. Zero forward-compat break, deprecation warning on write instead. - Clean break: write
"dx2_y2"as done here, but bump the document version / add an explicit note to CHANGES.md so downstream can gate on it.
I'd lean conservative given how much serialized COHP/DOS data exists in the wild, but either is defensible — it just needs to be a deliberate decision recorded in CHANGES.md, which this PR does not touch.
Specific code comments:
_OrbitalMeta.__getattribute__should be__getattr__.__getattribute__intercepts every class-level attribute access onOrbital, so it adds a Python-level function call plus a string comparison to hot paths like theorb in (Orbital.dxy, Orbital.dxz, Orbital.dyz)checks insideget_site_t2g_eg_resolved_dosand the projection loops. Sincedx2is no longer a member,__getattr__on the metaclass is only reached on the miss and is free otherwise:
def __getattr__(cls, name):
if name == "dx2":
warnings.warn(...)
return cls.dx2_y2
raise AttributeError(name)-
canonical_orb = orb if "dx2_y2" in orb else orb.replace("dx2", "dx2_y2")incohp.pyis fragile. These labels are composite (e.g."2px-3dx2"), and a label containing both spellings — or one wheredx2appears twice — short-circuits on the guard and gets only partly renamed. Safer to split on the separator and map each token, or use a regex with a boundary:re.sub(r"dx2(?!_y2)", "dx2_y2", orb). -
Orbital.dx2_y2.nameis now"dx2_y2". Downstream code doingorbital.name == "dx2"orstr(orbital)comparisons breaks silently — theOrbital.dx2/Orbital["dx2"]aliases do not help there. Worth an explicit line in the CHANGES.md/migration note. -
No deprecation timeline.
DeprecationWarningwith no target removal version tends to live forever. Suggest naming a release in the message (pymatgen has used@deprecated/deprecateddecorators elsewhere for this). -
Minor:
EnumMetais the legacy alias forEnumType(3.11+). Fine for the supported floor, butEnumTypeif/when 3.11 is dropped.
Still marked draft — the substance is in good shape; the blocker is deciding the as_dict() policy in point 0 and documenting the break.
6a57d7c to
683b2db
Compare
shyuep
left a comment
There was a problem hiding this comment.
🤖 Automated PR review generated by Claude (posted via @shyuep's account; static analysis of the diff + CI only, no code executed)
Rename coverage checks out: I swept every remaining dx2 occurrence under src/ and the leftovers are all unrelated (idx2 locals, and the JDFTx perturb-ion dx2 displacement tag in jdftxinfile_master_format.py). Legacy-name handling is also correctly scoped to BandStructure.from_old_dict only, since modern as_dict stores projections positionally rather than orbital-keyed. CI is green.
Two substantive comments:
1. Drop the DeprecationWarning from the as_dict() paths. CompleteDos.as_dict() and CompleteCohp.as_dict() now warn whenever dx2_y2 is present — i.e. for every user whose code is already fully correct. There is no action the caller can take to silence it, and it will fail any downstream suite running -W error::DeprecationWarning (a common pymatgen-adjacent config). The legacy-key emission is an internal compatibility decision; the # TODO(2027-08-17) comments already record it. Recommend keeping the TODOs and deleting both warnings.warn blocks. The read-path warnings (_OrbitalMeta, from_old_dict) are the right place for user-facing deprecation and should stay.
2. _OrbitalMeta.__getattr__ should delegate rather than hard-raise.
raise AttributeError(name)On Python 3.11 (the project floor) EnumType.__getattr__ still participates in member and alias resolution, so unconditionally raising can shadow lookups the base metaclass would have served. Safer:
try:
return super().__getattr__(name) # type: ignore[misc]
except AttributeError:
raise AttributeError(name) from NoneAlso worth noting hasattr(Orbital, "dx2") now emits the warning, so downstream feature-detection code will get noise — acceptable, but a stacklevel sanity check on that path would be nice.
Minor: the warnings.warn messages say "removed on 2027-08-17" / "on or after 2027-08-17" for the same event — worth making the two strings consistent.
Otherwise the approach (metaclass shim + __getitem__ shim + symmetric ser/deser) is sound and the added tests in test_core.py cover the deprecation surface well.
dee72f3 to
166ed91
Compare
shyuep
left a comment
There was a problem hiding this comment.
Automated PR review generated by Claude (posted on behalf of @shyuep). Re-review after the latest commits (conservative serialization path + deprecation deadline).
- Two-phase design is sound: enum value 8 unchanged;
_OrbitalMeta.__getattr__/__getitem__aliasdx2→dx2_y2withDeprecationWarningand an explicit 2027-08-17 removal date; serializers keep writing the legacydx2key for now so downstream consumers of serialized data are unaffected.TODO(2027-08-17)markers on every legacy path make phase 2 easy to execute. - Legacy read paths look complete:
CompleteCohp.from_dict(incl. mixed labels viare.sub(r"dx2(?!_y2)")),CompleteDos.from_dict, andBandStructure.from_old_dict(one-shot warning). Round-trip tests assert no spuriousDeprecationWarnings on canonical data — nice. - IO mappings (cp2k, jdftx, vaspout.h5, plotter) consistently updated.
- All CI checks green (lint + full test matrix).
LGTM.
166ed91 to
6766ca3
Compare
|
Automated PR review generated by Claude (scheduled routine, posted on behalf of @shyuep) — re-review after new commits since the 2026-07-19 automated review. LGTM. The two-phase deprecation is well executed: enum rename with Two minor notes, neither blocking: (1) code that compares |
close materialsproject/pymatgen#4588
previously materialsproject/pymatgen#4590
Breaking Changes
Orbital.dx2is renamed toOrbital.dx2_y2(enum value8unchanged). To avoid breaking consumers of serialized data, this is a two-phase transition:Orbital.dx2/Orbital["dx2"]still resolve (with aDeprecationWarning);CompleteCohp.as_dict()/CompleteDos.as_dict()still write the legacydx2key; readers accept bothdx2anddx2_y2.dx2_y2is serialized and the legacy alias/read paths are removed.Migration: use
Orbital.dx2_y2/"dx2_y2"; existing serialized data needs no conversion.