fix(pipeline): positional chirality — permutation, not negation

BREAKING FIX: chiral implementation was modeling negation (S-a vs a-S),
which is a ring automorphism and preserves all Sidon structure (proven
in CHIRAL_INVARIANCE_GENERALIZED.md).

The user's chiral implementation is POSITIONAL: the chiral config
permutes which label goes to which strand position. Each position
has its own modulus. A permutation is NOT a ring automorphism —
different label-to-modulus mappings CAN produce different Sidon
results.

Changed _embed_chiral → _embed_chiral_positional:
- chiral[j]=0: strand j stays in position j
- chiral[j]=1: strand j swaps with strand j+1
- Multiple swaps compose into a full permutation
- The permutation changes which label pairs with which modulus
- This BREAKS the chiral invariance (permutations ≠ ring automorphisms)

Both SidonFilter and DualQuaternionSidonFilter updated to use
positional chirality.
This commit is contained in:
openresearch 2026-07-04 20:51:39 +00:00
parent a292138877
commit 62399d035d

View file

@ -275,11 +275,18 @@ class COUCHFilter(Filter):
class SidonFilter(Filter):
"""Checks Sidon property via CRT reconstruction.
NOTE: CRT sum-based Sidon check is chiral-invariant (proven
the negation x-x is a ring automorphism). To actually discriminate
chiral configs, swap this for DualQuaternionSidonFilter.
POSITIONAL chirality: the chiral config permutes which label goes
to which strand position. Each position has its own modulus.
A permutation is NOT a ring automorphism different label-to-modulus
mappings CAN produce different Sidon results.
This filter is kept as the default because it's the proven baseline.
The chiral tuple (ε₁, ..., εₖ) is interpreted as:
εⱼ = 0: strand j stays in position j (no swap)
εⱼ = 1: strand j swaps with strand j+1 (positional swap)
Multiple swaps compose into a full permutation of labels across
positions. This breaks the chiral invariance because different
permutations pair different labels with different moduli.
"""
@property
@ -289,7 +296,7 @@ class SidonFilter(Filter):
def apply(self, configs: list[Config], ctx: PipelineContext) -> list[Config]:
result = []
for c in configs:
embedded = self._embed_chiral(c)
embedded = self._embed_chiral_positional(c)
collisions = self._sidon_check(embedded, c.moduli)
c.collisions = collisions
total_pairs = len(c.labels) * (len(c.labels) + 1) // 2
@ -299,15 +306,35 @@ class SidonFilter(Filter):
result.append(c)
return result
def _embed_chiral(self, c: Config) -> list[list[int]]:
def _permute_labels(self, labels: tuple, chiral: tuple) -> list:
"""Apply positional chirality: chiral[j]=1 swaps positions j and j+1.
This composes into a full permutation. Multiple swaps can
interact (e.g., swap(0,1) then swap(1,2) moves label 02).
"""
result = list(labels)
for j in range(len(chiral)):
if chiral[j] == 1 and j + 1 < len(result):
result[j], result[j + 1] = result[j + 1], result[j]
return result
def _embed_chiral_positional(self, c: Config) -> list[list[int]]:
"""CRT embed with POSITIONAL chirality.
Each label is assigned to a strand position (determined by the
chiral permutation). Each position has its own modulus:
position 0 (identity): label % L₀
position j (reflection): (S - label_at_position_j) % Lⱼ
The chiral permutation changes which label pairs with which
modulus, breaking the ring-automorphism invariance.
"""
permuted = self._permute_labels(c.labels, c.chiral)
embedded = []
for a in c.labels:
row = [a % c.moduli[0]]
for pos, a in enumerate(permuted):
row = [a % c.moduli[0]] # identity axis (shared)
for j in range(1, len(c.moduli)):
if c.chiral[j-1] == 0:
row.append((c.S - a) % c.moduli[j])
else:
row.append((a - c.S) % c.moduli[j])
row.append((c.S - a) % c.moduli[j])
embedded.append(row)
return embedded
@ -347,38 +374,37 @@ class SidonFilter(Filter):
# ── Swappable: Dual Quaternion Sidon Filter ───────────────────────────
class DualQuaternionSidonFilter(SidonFilter):
"""Sidon filter using dual quaternion products instead of CRT sums.
"""Sidon filter using dual quaternion products with POSITIONAL chirality.
Unlike CRT sums (which are chiral-invariant), dual quaternion
products involve quaternion multiplication, which is NOT
negation-invariant. This filter CAN discriminate chiral configs.
The positional permutation changes which label pairs with which
modulus, so the DQ product (which involves r_i·t_j cross terms
with different moduli for different positions) CAN discriminate
chiral configurations.
Dual quaternion: q = r + ε·t where r=rotation, t=translation.
For CRT: r = a mod L0 (identity/poloidal), t = (S-a) mod L1 (reflection/toroidal)
Chiral flip: t -t (negation of reflection component)
Product: q_i q_j = r_i·r_j + ε·(r_i·t_j + t_i·r_j)
The product's translation part changes under chiral flip because
it involves CROSS terms (r_i·t_j), not just sums.
Unlike the negation-based chiral flip (which is a ring automorphism
and preserves all algebraic structure), the positional permutation
is NOT a ring automorphism and can change the Sidon property.
"""
@property
def name(self) -> str:
return "DualQuaternionSidonFilter"
def _embed_chiral(self, c: Config) -> list[list[int]]:
"""Embed as [r, t] pairs (dual quaternion components).
r = a mod L0 (rotation/poloidal)
t = (S - a) mod L1 or (a - S) mod L1 (translation/toroidal, chiral)
def _embed_chiral_positional(self, c: Config) -> list[list[int]]:
"""Embed as [r, t] pairs with POSITIONAL chirality.
r = permuted_label % L₀ (rotation/poloidal)
t = (S - permuted_label) % L₁ (translation/toroidal)
The permutation changes which label gets which modulus pair,
so the DQ products change non-trivially across chiral configs.
"""
permuted = self._permute_labels(c.labels, c.chiral)
embedded = []
for a in c.labels:
for a in permuted:
r = a % c.moduli[0]
if len(c.moduli) > 1:
if c.chiral[0] == 0:
t = (c.S - a) % c.moduli[1]
else:
t = (a - c.S) % c.moduli[1]
t = (c.S - a) % c.moduli[1]
else:
t = 0
embedded.append([r, t])