Skip to content

DnonDn: restore the fold block for odd n >= 5 (invariant was incomplete) - #55

Merged
johmathe merged 1 commit into
mainfrom
johmathe/fix-dn-odd-n-fold
Oct 14, 2026
Merged

johmathe merged 1 commit into
mainfrom
johmathe/fix-dn-odd-n-fold

Conversation

@johmathe

Copy link
Copy Markdown
Collaborator

Problem

n3 = n2d if n % 2 == 0 else max(n2d - 1, 1) dropped the block β_{ρ₁, ρ_{n2d}} for odd n ≥ 5. For odd n, ρ₁ ⊗ ρ_{n2d} = ρ_{n2d−1} ⊕ ρ_{(n+1)/2}, and ρ_{(n+1)/2} folds back onto ρ_{n2d} — so this block is the fold, and without it the continuous twist F_k → R(kθ)·F_k preserves every selected entry. The invariant was incomplete (Jacobian rank 2n−1 instead of 2n) and invert could not recover the orbit for odd n ≥ 5.

Fix

  • n3 = n2d for all n.
  • invert: the fold branch is generalised from n = 3 to every odd n (fold block = the 2D block labelled n2d in the last decomposition, fold_scale = n, n_shift = 2n, rotation measured from the chain-extracted F̂). Even-n path untouched; n = 3 behaviour unchanged.
  • Docstring corrected ("for odd n ≥ 5 there is no fold" was wrong).

Breaking

output_size grows by 16 and the layout changes for odd n ≥ 5 (n=5: 21 → 37, n=7: 37 → 53). Even n and n = 3 unchanged.

Tests

Jacobian rank == 2n for n = 3…12 (was 2n−1 for odd n ≥ 5); continuous twist changes β (odd n); twist by 2π/n is invariant; orbit recovery for n = 3…12 (1e-8). Sweep n = 3…32 × 5 seeds × 64 signals: worst roundtrip 4e-8, every reconstruction in the D_n orbit.

Part of the v0.4.0 stack (C).

For odd n the last chain block beta_{rho1, rho_n2d} is the fold:
rho_1 x rho_n2d = rho_{n2d-1} + rho_{(n+1)/2}, and rho_{(n+1)/2} is
equivalent to rho_n2d. The constructor dropped it for odd n >= 5
(n3 = n2d - 1, commented as a "g-invariance bug" fix), which left the
selective bispectrum blind to the continuous twist
F_k -> R(k theta) F_k: the Jacobian had rank 2n - 1 instead of 2n,
and invert() returned a signal on that one-parameter family rather
than in the D_n orbit of the input (roundtrip of the selected entries
still passed, which is why the existing tests did not catch it).

n3 = n2d for all n now. invert() generalizes the n = 3 fold resolution
to every odd n: the fold block re-extracts rho_n2d in a frame rotated
by R(-n theta), so the angle is measured against the chain value of
F_n2d (which is S itself at n = 3), with fold_scale = n and 2n
candidate shifts. Even n is unchanged. output_size grows by 16 for odd
n >= 5 (n=5: 37, n=7: 53).

Tests: Jacobian rank == 2n for n = 3..12, a generic twist changes the
bispectrum while theta = 2 pi / n does not, and orbit recovery for all
n = 3..12 (previously even n only). Sweep n = 3..32 x 5 seeds x 64:
worst roundtrip 4e-8, all reconstructions in the orbit.
@codecov

codecov Bot commented Oct 14, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 95.45%. Comparing base (05e7fdd) to head (be107a2).
⚠️ Report is 2 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main      #55      +/-   ##
==========================================
- Coverage   95.46%   95.45%   -0.01%     
==========================================
  Files          11       11              
  Lines        2227     2224       -3     
==========================================
- Hits         2126     2123       -3     
  Misses        101      101              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@johmathe
johmathe merged commit b3746db into main Oct 14, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant