While porting cryosym, I came across what I believe is a bug in the D2 orientation-test helper g_sync_d2 (tests/test_orient_d2.py).
Summary:
g_sync_d2 resolves the per-image symmetry-group ambiguity (each estimated rotation
may be g_i * R_i for some g_i in the D2 group) by encoding the measured relative
group element as a root of unity and doing a rank-1 spectral synchronization:
A_g[i, j] = np.exp(-1j * 2 * np.pi / order * idx) # tests/test_orient_d2.py:435 (order = 4).
This is copied from the cyclic-symmetry helper g_sync
(src/aspire/abinitio/commonline_utils.py:334), where it is correct. The problem is
that the encoding implicitly assumes the ambiguity group is cyclic, and
D2 is not cyclic - it is the Klein four-group Z2 x Z2, and it is not possible to encode its elements into {1,-1,i,-i}.
Possible Fix:
Instead of the current synchronization encoding, it is possible to split it into two Z_2 synchronization steps (I did something similar in cryosym).
Empirical Confirmation:
Using Claude I reproduced the helper's algorithm in isolation and ran a clean-case check: pick
random ground-truth rotations, apply a known per-image group element to form a
noise-free "estimate", run the helper, then align with the same global Procrustes
step ASPIRE uses (Rotation.find_registration / apply_registration) and measure the
mean angular error.
- C3 / C4 / C5 (cyclic): A_g spectrum [12, 0, 0, 0, 0] (rank-1), 0/200 trials
exceed 1 degree.
- D2 (Z2 x Z2): A_g spectrum [9.32, 5.87, 1.83, ...] (not rank-1), 140/200 trials
fail, errors up to 90 degrees.
- D2 with a Z2 x Z2 sync (two independent {+1,-1} sign-syncs):
back to 0/200. The only change is the encoding, which isolates the group
structure as the cause.
However I'm not sure how this can be extended to other groups such as O/T/I.
A user reported we have an issue with our D2 cl test. Pasted below. @j-c-c mentioned he had encountered this working with Ruyi's code and may have a fix upstream already.