Do not record the identity as a rotation axis in PointGroupAnalyzer - #134
Open
HiroYokoyama wants to merge 2 commits into
Open
Do not record the identity as a rotation axis in PointGroupAnalyzer#134HiroYokoyama wants to merge 2 commits into
HiroYokoyama wants to merge 2 commits into
Conversation
_check_rot_sym() walked idx down to 1, where the operation tested is a
360-degree rotation - the identity - which is_valid_op() accepts about any
axis. It therefore appended a (axis, 1) entry to self.rot_sym for every axis
it was given.
_proc_sym_top() reads len(self.rot_sym) >= 2 as "dihedral", so a molecule
whose unique axis carries no rotation of its own but which has one
perpendicular C2 ended up with rot_sym == [(axis, 1), (perp_axis, 2)] and was
named D{rot} by _proc_dihedral(). get_symmetry_operations() is generated from
the real generators and knows nothing about the phantom axis, so the analyzer
contradicted itself: sch_symbol "D2", a group of order 4, alongside 2
operations.
Tetramethylhydrazine, (CH3)2N-N(CH3)2, shows this on an undistorted geometry
at every tolerance from 0.01 to 0.5 A. Its only C2 axis reproduces the
structure to 1e-4 A; a C2 about either of the other two principal axes leaves
atoms 1.42 A from their nearest same-element neighbour, and a search over
20000 random axes finds no third one. The molecule is C2.
The loop now stops at 2, so rot_sym only ever holds genuine rotation axes.
_proc_sym_top() must then look for a perpendicular C2 unconditionally: that
search used to be gated on len(self.rot_sym) > 0, a condition only ever
satisfied by the phantom entry, and gating it on a now-empty rot_sym would
report C1 for these molecules instead.
tests/symmetry passes unchanged (91 passed, 5 skipped before and after) plus
the added regression test.
shyuep
reviewed
Aug 28, 2026
shyuep
left a comment
Member
There was a problem hiding this comment.
Automated PR review generated by Claude (posted by @shyuep's review bot).
Diagnosis and fix are correct. idx=1 in _check_rot_sym registered a 360° rotation (the identity) as a rotation axis about any axis, so len(rot_sym) >= 2 misrouted single-perpendicular-C2 molecules to _proc_dihedral (D2 symbol with only 2 ops — self-contradictory). Stopping the loop at 2 and unconditionally running _check_perpendicular_r2_axis is the right pair of changes; either alone would be wrong (C1 regression), as the PR notes.
Two points to verify before merge:
rot_symcan now be empty after_check_rot_symwhere the phantom identity entry previously guaranteed at least one element. Please audit the other callers (_proc_asym_top,_proc_cyclic, spherical-top path) for anyself.rot_sym[0]access orlen(rot_sym) > 0logic that implicitly relied on the identity entry. The passing test suite suggests this is fine, but an explicit check is warranted since the change is in a shared helper.- The body states this may change
sch_symbolfor affected molecules but the breaking-change box is unticked — per repo convention, tick it and prefix the title with[breaking].
Test is well-constructed (regression on Schoenflies symbol, operation count, and rot_sym order). CI is green. LGTM modulo point 1.
_proc_sym_top no longer guarantees a non-empty rot_sym, so the fall-through into max(self.rot_sym) raised ValueError for an isotropic inertia tensor with no rotational symmetry. Previously that molecule was reported as D1, which is not a point group, from the same phantom identity entry.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
I found an issue in the symmetry analyzer: when a molecule whose only rotational symmetry is a single C2 axis (e.g., tetramethylhydrazine) is analyzed, sch_symbol reports it as D2, while get_symmetry_operations() returns only 2 operations. D2 is a group of order 4, so this is a self-contradiction.
I then used Claude Opus 5 to analyze the issue, and found that _check_rot_sym() registers a 360° rotation (the identity, which is valid about any axis) in rot_sym as a rotation axis, so the condition len(rot_sym) >= 2 in _proc_sym_top() routes the molecule to the dihedral branch.
Measured data:
This PR stops the loop in _check_rot_sym() at 2, and also removes the if len(self.rot_sym) > 0: guard in _proc_sym_top() so that the perpendicular C2 search always runs; that guard was only ever satisfied by the phantom entry, so fixing the loop alone would make these molecules report as C1 instead.
This PR also adds a regression test for tetramethylhydrazine in tests/symmetry/test_analyzer.py; the test fails on main with assert 'D2' == 'C2'.
This may be a breaking change:
sch_symbol changes for molecules whose unique axis of inertia carries no rotation of its own but which have one perpendicular C2 axis: they were reported as D2 and are now reported as C2. get_symmetry_operations() and get_pointgroup() are unaffected — they already returned the 2-operation group. Code that compared sch_symbol against the previous (incorrect) value must be updated; nothing else is affected.
Checklist
ruff check/ruff format --checkpassedBreaking changes?
behavior changes, changed defaults/output, dropped Python versions, ...).
If so, prefix the title with
[breaking]and describe the change andmigration steps under
## Breaking Changesbelow.Breaking Changes