mirror of
https://github.com/allaunthefox/SilverSight.git
synced 2026-07-31 01:25:21 +00:00
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:
parent
a292138877
commit
62399d035d
1 changed files with 58 additions and 32 deletions
|
|
@ -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 0→2).
|
||||
"""
|
||||
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])
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue