coloring_nested_tire_graphs: SR + PDS chain consistency holds robustly
Redo step 2 and step 3 under SR (no chord effect, the correct model
under PDS where O-faces are not G-faces).
Step 2 (pairwise, 14 cases): all compatible. T_1's γ-side projection
saturates {1,2,3}^γ under outward PDS (m_1 ≥ γ from step 1), so
intersection = T_2's projection, always nonempty.
Step 3 (chain consistency, 10 chains up to 6 tires): all compatible.
Forward propagation along the chain shows monotonically growing
support sizes (roughly 3x per step), never empties. Free choice
accumulates outward.
Implication: chain consistency under SR + PDS is essentially
automatic for "open" chains. The remaining gap is the boundary
condition at the outermost level (e.g., the outer triangle of a
triangulated sphere has only 6 valid σ-permutations); whether the
forward state always contains one of those is the next experiment.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,254 @@
|
|||||||
|
"""Chain pigeonhole under SR + PDS, no chord constraints.
|
||||||
|
|
||||||
|
Under the correct PDS modelling, each tire's T'_{f'} has γ inner-spoke
|
||||||
|
pendants regardless of O's chord structure (chord edges of O become
|
||||||
|
G'-edges between inner-spoke vertices, but those edges are NOT in
|
||||||
|
T'_{f'} since neither endpoint is in V(f')). So the only inputs are
|
||||||
|
cycle lengths (m, k) per tire.
|
||||||
|
|
||||||
|
For each tire T with B_out length m and B_in length k:
|
||||||
|
Π_T ⊆ {1,2,3}^m × {1,2,3}^k
|
||||||
|
= { (σ_U, σ_D) : σ comes from a proper edge 3-colouring of C_{m+k} }
|
||||||
|
|
||||||
|
Step 2 (pairwise): for adjacent tires T_1, T_2 sharing γ, do
|
||||||
|
π_U(T_1)|_γ and π_D(T_2)|_γ overlap? By step-1 saturation, yes —
|
||||||
|
T_1's γ-side saturates {1,2,3}^γ when m_1 ≥ |γ|, etc.
|
||||||
|
|
||||||
|
Step 3 (chain consistency): chain T_1 | T_2 | ... | T_n. σ at each
|
||||||
|
shared cycle must be jointly consistent. Propagate forward via the
|
||||||
|
joint supports and check if state ever empties.
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
from itertools import product
|
||||||
|
|
||||||
|
from tire_fiber_chords import d_positions_for, u_positions_for
|
||||||
|
from tire_fiber_chords_fast import proper_cycle_colorings, induced_sigma_vec
|
||||||
|
|
||||||
|
|
||||||
|
_pi_cache: dict = {}
|
||||||
|
|
||||||
|
|
||||||
|
def joint_support_SR(m: int, k: int) -> set[tuple[tuple[int, ...], tuple[int, ...]]]:
|
||||||
|
"""Π_T = {(σ_U, σ_D)} pairs for SR tire (no chord)."""
|
||||||
|
key = (m, k)
|
||||||
|
if key in _pi_cache:
|
||||||
|
return _pi_cache[key]
|
||||||
|
n = m + k
|
||||||
|
d_pos = d_positions_for(m, k)
|
||||||
|
u_pos = u_positions_for(m, k)
|
||||||
|
c = proper_cycle_colorings(n)
|
||||||
|
sigma = induced_sigma_vec(c)
|
||||||
|
sigma_u = sigma[:, u_pos]
|
||||||
|
sigma_d = sigma[:, d_pos]
|
||||||
|
pairs = set(zip(map(tuple, sigma_u.tolist()),
|
||||||
|
map(tuple, sigma_d.tolist())))
|
||||||
|
_pi_cache[key] = pairs
|
||||||
|
return pairs
|
||||||
|
|
||||||
|
|
||||||
|
# --- Step 2 ---
|
||||||
|
|
||||||
|
def step2_SR(gamma: int, m_1: int, k_2: int) -> dict:
|
||||||
|
"""T_1 has (m=m_1, k=γ). T_2 has (m=γ, k=k_2)."""
|
||||||
|
pi1 = joint_support_SR(m_1, gamma)
|
||||||
|
pi2 = joint_support_SR(gamma, k_2)
|
||||||
|
sd1 = {p[1] for p in pi1} # γ-projection from T_1's side
|
||||||
|
su2 = {p[0] for p in pi2} # γ-projection from T_2's side
|
||||||
|
fwd = sd1 & su2
|
||||||
|
rev = sd1 & {s[::-1] for s in su2}
|
||||||
|
return {
|
||||||
|
'γ': gamma, 'm_1': m_1, 'k_2': k_2,
|
||||||
|
'|sd1|': len(sd1), '|su2|': len(su2), '3^γ': 3**gamma,
|
||||||
|
'sd1_saturates': len(sd1) == 3**gamma,
|
||||||
|
'su2_saturates': len(su2) == 3**gamma,
|
||||||
|
'fwd': len(fwd), 'rev': len(rev),
|
||||||
|
'compatible': bool(fwd or rev),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# --- Step 3 ---
|
||||||
|
|
||||||
|
def step3_chain(chain: list[tuple[int, int]]) -> dict:
|
||||||
|
"""Forward-propagate joint supports along a tire chain.
|
||||||
|
|
||||||
|
chain[i] = (m_i, k_i) for tire T_i. Adjacency requires k_{i+1} = m_i
|
||||||
|
(T_{i+1}'s B_in = T_i's B_out = shared γ).
|
||||||
|
|
||||||
|
State at step i = set of σ at the "current" shared cycle γ_i = L_i,
|
||||||
|
which is reachable through T_1, ..., T_i.
|
||||||
|
|
||||||
|
Returns: compatibility result and the state size trajectory.
|
||||||
|
"""
|
||||||
|
# Validate adjacency
|
||||||
|
for i in range(len(chain) - 1):
|
||||||
|
if chain[i][0] != chain[i + 1][1]:
|
||||||
|
return {'error': f'chain adjacency mismatch at i={i}: '
|
||||||
|
f'T_{i+1}.m={chain[i][0]} != T_{i+2}.k={chain[i+1][1]}'}
|
||||||
|
|
||||||
|
pis = [joint_support_SR(m, k) for (m, k) in chain]
|
||||||
|
|
||||||
|
# Initial state: σ at L_1 from T_1's σ_U-projection (any σ_D ok since
|
||||||
|
# the innermost boundary L_0 has no further constraint).
|
||||||
|
state = {p[0] for p in pis[0]}
|
||||||
|
trajectory = [len(state)]
|
||||||
|
|
||||||
|
for i in range(1, len(chain)):
|
||||||
|
new_state = set()
|
||||||
|
pi = pis[i]
|
||||||
|
# T_i has B_in = L_i, B_out = L_{i+1}. pair = (σ at L_{i+1}, σ at L_i).
|
||||||
|
# For each pair, if σ at L_i in current state, add σ at L_{i+1} to new state.
|
||||||
|
for sigma_outer, sigma_inner in pi:
|
||||||
|
if sigma_inner in state:
|
||||||
|
new_state.add(sigma_outer)
|
||||||
|
trajectory.append(len(new_state))
|
||||||
|
if not new_state:
|
||||||
|
return {
|
||||||
|
'chain': chain,
|
||||||
|
'compatible': False,
|
||||||
|
'failed_at_tire_index': i,
|
||||||
|
'trajectory': trajectory,
|
||||||
|
}
|
||||||
|
state = new_state
|
||||||
|
|
||||||
|
return {
|
||||||
|
'chain': chain,
|
||||||
|
'compatible': True,
|
||||||
|
'final_state_size': len(state),
|
||||||
|
'trajectory': trajectory,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def step3_chain_with_reflections(chain: list[tuple[int, int]]) -> dict:
|
||||||
|
"""Like step3_chain but also tries flipping orientation at each
|
||||||
|
junction (since adjacent tires may have reversed γ-orientations
|
||||||
|
in the actual plane embedding).
|
||||||
|
|
||||||
|
More expensive: 2^(len(chain)-1) orientation choices."""
|
||||||
|
n = len(chain)
|
||||||
|
pis = [joint_support_SR(m, k) for (m, k) in chain]
|
||||||
|
|
||||||
|
# Try every combination of orientation flips at junctions.
|
||||||
|
# flip[i] = True means flip σ at L_{i+1} when bridging T_i → T_{i+1}.
|
||||||
|
# Equivalent: reverse the σ_D of T_{i+1} before matching.
|
||||||
|
|
||||||
|
best = None
|
||||||
|
for flips in product([False, True], repeat=n - 1):
|
||||||
|
state = {p[0] for p in pis[0]}
|
||||||
|
traj = [len(state)]
|
||||||
|
ok = True
|
||||||
|
for i in range(1, n):
|
||||||
|
pi = pis[i]
|
||||||
|
new_state = set()
|
||||||
|
if flips[i - 1]:
|
||||||
|
# Reverse σ at L_i (the boundary between T_{i-1} and T_i)
|
||||||
|
state_match = state
|
||||||
|
for sigma_outer, sigma_inner in pi:
|
||||||
|
if sigma_inner[::-1] in state_match:
|
||||||
|
new_state.add(sigma_outer)
|
||||||
|
else:
|
||||||
|
for sigma_outer, sigma_inner in pi:
|
||||||
|
if sigma_inner in state:
|
||||||
|
new_state.add(sigma_outer)
|
||||||
|
traj.append(len(new_state))
|
||||||
|
if not new_state:
|
||||||
|
ok = False
|
||||||
|
break
|
||||||
|
state = new_state
|
||||||
|
if ok:
|
||||||
|
if best is None or len(state) > best['final_state_size']:
|
||||||
|
best = {
|
||||||
|
'chain': chain,
|
||||||
|
'compatible': True,
|
||||||
|
'final_state_size': len(state),
|
||||||
|
'trajectory': traj,
|
||||||
|
'flips': flips,
|
||||||
|
}
|
||||||
|
|
||||||
|
if best is not None:
|
||||||
|
return best
|
||||||
|
return {
|
||||||
|
'chain': chain,
|
||||||
|
'compatible': False,
|
||||||
|
'tried_orientations': 2 ** (n - 1),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# --- Test scenarios ---
|
||||||
|
|
||||||
|
PAIRWISE_CASES = [
|
||||||
|
# (γ, m_1, k_2): T_1 has B_in=γ; T_2 has B_out=γ.
|
||||||
|
(3, 3, 3),
|
||||||
|
(4, 4, 3),
|
||||||
|
(4, 4, 4),
|
||||||
|
(5, 5, 3),
|
||||||
|
(5, 6, 5),
|
||||||
|
(6, 6, 3),
|
||||||
|
(6, 6, 4),
|
||||||
|
(6, 6, 5),
|
||||||
|
(6, 6, 6),
|
||||||
|
(8, 8, 4),
|
||||||
|
(8, 8, 6),
|
||||||
|
(8, 10, 8),
|
||||||
|
(10, 10, 8),
|
||||||
|
(12, 12, 6),
|
||||||
|
]
|
||||||
|
|
||||||
|
# 3-tire and longer chains.
|
||||||
|
# Each tuple (m, k) -- adjacent (m_i, k_i), (m_{i+1}, k_{i+1}) need k_{i+1} = m_i.
|
||||||
|
CHAIN_CASES = [
|
||||||
|
# Strictly outward-growing PDS:
|
||||||
|
[(4, 3), (5, 4), (6, 5)],
|
||||||
|
[(4, 3), (6, 4), (8, 6)],
|
||||||
|
[(5, 4), (6, 5), (7, 6)],
|
||||||
|
[(6, 3), (8, 6), (10, 8)],
|
||||||
|
# Stalled growth:
|
||||||
|
[(4, 3), (5, 4), (5, 5)],
|
||||||
|
[(4, 3), (4, 4), (5, 4)],
|
||||||
|
# Shrinking (anti-PDS, for stress):
|
||||||
|
[(3, 4), (3, 3)], # would need k_2=m_1=3 -- ok
|
||||||
|
# Longer chains:
|
||||||
|
[(4, 3), (5, 4), (6, 5), (7, 6)],
|
||||||
|
[(4, 3), (5, 4), (6, 5), (7, 6), (8, 7)],
|
||||||
|
[(4, 3), (5, 4), (6, 5), (7, 6), (8, 7), (9, 8)],
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
print("=" * 80)
|
||||||
|
print("Step 2 under SR + PDS (no chord)")
|
||||||
|
print("=" * 80)
|
||||||
|
print(f"{'γ':>3} {'m_1':>4} {'k_2':>4} {'|sd1|':>6} {'|su2|':>6} {'3^γ':>7} "
|
||||||
|
f"{'sat1':>5} {'sat2':>5} {'fwd':>5} {'rev':>5} compat")
|
||||||
|
for γ, m_1, k_2 in PAIRWISE_CASES:
|
||||||
|
r = step2_SR(γ, m_1, k_2)
|
||||||
|
sat1 = "✓" if r['sd1_saturates'] else "·"
|
||||||
|
sat2 = "✓" if r['su2_saturates'] else "·"
|
||||||
|
ok = "YES" if r['compatible'] else "NO"
|
||||||
|
print(f"{γ:>3} {m_1:>4} {k_2:>4} {r['|sd1|']:>6} {r['|su2|']:>6} {r['3^γ']:>7} "
|
||||||
|
f"{sat1:>5} {sat2:>5} {r['fwd']:>5} {r['rev']:>5} {ok}")
|
||||||
|
|
||||||
|
print()
|
||||||
|
print("=" * 80)
|
||||||
|
print("Step 3: chain consistency (3+ tires) under SR + PDS")
|
||||||
|
print("=" * 80)
|
||||||
|
for chain in CHAIN_CASES:
|
||||||
|
adj_ok = all(chain[i][0] == chain[i + 1][1] for i in range(len(chain) - 1))
|
||||||
|
adj = "OK" if adj_ok else "(adjacency mismatch)"
|
||||||
|
if not adj_ok:
|
||||||
|
print(f" SKIP {chain}: {adj}")
|
||||||
|
continue
|
||||||
|
# Use the orientation-checking version
|
||||||
|
r = step3_chain_with_reflections(chain)
|
||||||
|
chain_str = " | ".join(f"({m},{k})" for (m, k) in chain)
|
||||||
|
if r.get('compatible'):
|
||||||
|
print(f" {chain_str}: COMPATIBLE, final state size {r['final_state_size']}, "
|
||||||
|
f"trajectory {r['trajectory']}")
|
||||||
|
else:
|
||||||
|
failed = r.get('failed_at_tire_index', '?')
|
||||||
|
print(f" {chain_str}: **INCOMPATIBLE** (failed at tire index {failed})")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
================================================================================
|
||||||
|
Step 2 under SR + PDS (no chord)
|
||||||
|
================================================================================
|
||||||
|
γ m_1 k_2 |sd1| |su2| 3^γ sat1 sat2 fwd rev compat
|
||||||
|
3 3 3 27 27 27 ✓ ✓ 27 27 YES
|
||||||
|
4 4 3 81 78 81 ✓ · 78 78 YES
|
||||||
|
4 4 4 81 81 81 ✓ ✓ 81 81 YES
|
||||||
|
5 5 3 243 171 243 ✓ · 171 171 YES
|
||||||
|
5 6 5 243 243 243 ✓ ✓ 243 243 YES
|
||||||
|
6 6 3 729 396 729 ✓ · 396 396 YES
|
||||||
|
6 6 4 729 549 729 ✓ · 549 549 YES
|
||||||
|
6 6 5 729 726 729 ✓ · 726 726 YES
|
||||||
|
6 6 6 729 729 729 ✓ ✓ 729 729 YES
|
||||||
|
8 8 4 6561 2943 6561 ✓ · 2943 2943 YES
|
||||||
|
8 8 6 6561 5601 6561 ✓ · 5601 5601 YES
|
||||||
|
8 10 8 6561 6561 6561 ✓ ✓ 6561 6561 YES
|
||||||
|
10 10 8 59049 53049 59049 ✓ · 53049 53049 YES
|
||||||
|
12 12 6 531441 160503 531441 ✓ · 160503 160503 YES
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
Step 3: chain consistency (3+ tires) under SR + PDS
|
||||||
|
================================================================================
|
||||||
|
(4,3) | (5,4) | (6,5): COMPATIBLE, final state size 714, trajectory [78, 234, 714]
|
||||||
|
(4,3) | (6,4) | (8,6): COMPATIBLE, final state size 4914, trajectory [78, 540, 4914]
|
||||||
|
(5,4) | (6,5) | (7,6): COMPATIBLE, final state size 2172, trajectory [240, 720, 2172]
|
||||||
|
(6,3) | (8,6) | (10,8): COMPATIBLE, final state size 46074, trajectory [396, 4410, 46074]
|
||||||
|
(4,3) | (5,4) | (5,5): COMPATIBLE, final state size 240, trajectory [78, 234, 240]
|
||||||
|
(4,3) | (4,4) | (5,4): COMPATIBLE, final state size 234, trajectory [78, 78, 234]
|
||||||
|
(3,4) | (3,3): COMPATIBLE, final state size 27, trajectory [27, 27]
|
||||||
|
(4,3) | (5,4) | (6,5) | (7,6): COMPATIBLE, final state size 2160, trajectory [78, 234, 708, 2160]
|
||||||
|
(4,3) | (5,4) | (6,5) | (7,6) | (8,7): COMPATIBLE, final state size 6528, trajectory [78, 234, 714, 2160, 6528]
|
||||||
|
(4,3) | (5,4) | (6,5) | (7,6) | (8,7) | (9,8): COMPATIBLE, final state size 19644, trajectory [78, 234, 714, 2160, 6516, 19644]
|
||||||
Reference in New Issue
Block a user