Research-Stack/5-Applications/tools-scripts/demo/gwl_interaction_law_demo.py

384 lines
11 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""
gpl_interaction_law_demo.py
Demonstrates the GPL rotational coupling and local interaction law.
Shows: Frame compatibility → Weight → Force → Evolution → Convergence
"""
import math
import numpy as np
from dataclasses import dataclass
from typing import List, Tuple
import matplotlib.pyplot as plt
@dataclass
class Frame:
"""Rotational frame of a μ-seed."""
theta: int # Azimuthal: 0-15 (22.5° steps)
phi: int # Polar: 0-7
psi: int # Torsion: 0-7
chi: int # Chirality: 0=D, 1=L
a: float # Activation: 0-15
x: float # Position X
y: float # Position Y
def effective_theta(self) -> float:
"""Effective angle in radians, accounting for chirality."""
base = self.theta * (2 * math.pi / 16)
return base if self.chi == 0 else -base
def compute_weight(f_i: Frame, f_j: Frame, sigma: float = 2.0) -> float:
"""
Compute interaction weight w_ij.
w_ij = cos(Δθ) * cos(Δφ) * (1 - 2|Δχ|) * exp(-|Δp|²/2σ²)
"""
# Rotational alignment
delta_theta = (f_j.theta - f_i.theta) % 16
cos_theta = math.cos(delta_theta * 2 * math.pi / 16)
delta_phi = (f_j.phi - f_i.phi) % 8
cos_phi = math.cos(delta_phi * math.pi / 8)
# Chirality (0 if different, 1 if same)
chiral_factor = 1 - 2 * abs(f_j.chi - f_i.chi)
# Spatial proximity
dx = f_j.x - f_i.x
dy = f_j.y - f_i.y
dist_sq = dx*dx + dy*dy
proximity = math.exp(-dist_sq / (2 * sigma * sigma))
return cos_theta * cos_phi * chiral_factor * proximity
def compute_force(f_i: Frame, f_j: Frame, w_ij: float) -> Tuple[float, float]:
"""
Compute force F_ij = w_ij * (a_j - a_i) * direction.
Returns: (F_x, F_y)
"""
dx = f_j.x - f_i.x
dy = f_j.y - f_i.y
dist = math.sqrt(dx*dx + dy*dy)
if dist < 0.001:
return (0.0, 0.0)
# Direction unit vector
ux, uy = dx/dist, dy/dist
# Activation gradient
da = f_j.a - f_i.a
# Force magnitude
F_mag = w_ij * da
return (F_mag * ux, F_mag * uy)
def evolve_frame(f: Frame, F_x: float, F_y: float, alpha: float = 0.1) -> Frame:
"""
Update frame based on force.
Simple Euler integration.
"""
new_a = f.a + alpha * math.sqrt(F_x*F_x + F_y*F_y)
new_a = max(0.0, min(15.0, new_a)) # Clip to bounds
return Frame(
theta=f.theta,
phi=f.phi,
psi=f.psi,
chi=f.chi,
a=new_a,
x=f.x,
y=f.y
)
class GPLSimulation:
"""Simulate GPL interaction dynamics."""
def __init__(self, frames: List[Frame]):
self.frames = frames
self.history = [self.get_state()]
def get_state(self) -> List[Tuple[float, float, float]]:
"""Get current state (x, y, a)."""
return [(f.x, f.y, f.a) for f in self.frames]
def step(self):
"""One evolution step."""
n = len(self.frames)
forces = [(0.0, 0.0) for _ in range(n)]
# Compute all pairwise forces
for i in range(n):
for j in range(n):
if i == j:
continue
w = compute_weight(self.frames[i], self.frames[j])
F = compute_force(self.frames[i], self.frames[j], w)
forces[i] = (forces[i][0] + F[0], forces[i][1] + F[1])
# Update all frames
new_frames = []
for i, f in enumerate(self.frames):
new_f = evolve_frame(f, forces[i][0], forces[i][1])
new_frames.append(new_f)
self.frames = new_frames
self.history.append(self.get_state())
def run(self, steps: int = 100):
"""Run simulation for multiple steps."""
for _ in range(steps):
self.step()
# Check convergence
if self.check_convergence():
break
return self.frames
def check_convergence(self, threshold: float = 0.01) -> bool:
"""Check if converged (activation changes small)."""
if len(self.history) < 2:
return False
prev = self.history[-2]
curr = self.history[-1]
max_change = max(abs(c[2] - p[2]) for c, p in zip(curr, prev))
return max_change < threshold
def demo_two_node_interaction():
"""Demonstrate basic weight and force between two nodes."""
print("=" * 70)
print("TWO-NODE INTERACTION LAW DEMONSTRATION")
print("=" * 70)
test_cases = [
("Aligned (Δθ=0)", 0, 0, 0, 0),
("Orthogonal (Δθ=4)", 0, 4, 0, 0),
("Opposite (Δθ=8)", 0, 8, 0, 0),
("45° offset (Δθ=2)", 0, 2, 0, 0),
("Chiral mismatch", 0, 0, 0, 1),
]
print(f"\n{'Scenario':<25} | {'θ₁':>3} | {'θ₂':>3} | {'χ₁':>3} | {'χ₂':>3} | {'Weight':>8} | {'Interpretation'}")
print("-" * 95)
for name, t1, t2, c1, c2 in test_cases:
f1 = Frame(theta=t1, phi=0, psi=0, chi=c1, a=5.0, x=0.0, y=0.0)
f2 = Frame(theta=t2, phi=0, psi=0, chi=c2, a=8.0, x=1.0, y=0.0)
w = compute_weight(f1, f2)
F = compute_force(f1, f2, w)
interp = ""
if abs(w - 1.0) < 0.1:
interp = "Strong attraction"
elif abs(w) < 0.1:
interp = "No coupling"
elif w < -0.5:
interp = "Repulsion"
elif c1 != c2:
interp = "Orthogonal channels"
else:
interp = f"Partial ({w:.2f})"
print(f"{name:<25} | {t1:>3} | {t2:>3} | {c1:>3} | {c2:>3} | {w:>8.3f} | {interp}")
def demo_convergence():
"""Demonstrate convergence to attractor."""
print("\n" + "=" * 70)
print("CONVERGENCE DEMONSTRATION")
print("=" * 70)
# Create a line of 5 nodes with varying initial activation
frames = []
for i in range(5):
f = Frame(
theta=0, # All aligned
phi=0,
psi=0,
chi=0, # All D-form
a=float([10, 2, 8, 3, 12][i]), # Varying activation
x=float(i),
y=0.0
)
frames.append(f)
print("\nInitial state (aligned, varying activation):")
print(f"{'Node':>6} | {'x':>6} | {'θ':>4} | {'a':>8} | {'Type'}")
print("-" * 45)
for i, f in enumerate(frames):
t = "High" if f.a > 8 else "Low" if f.a < 4 else "Med"
print(f"{i:>6} | {f.x:>6.1f} | {f.theta:>4} | {f.a:>8.2f} | {t}")
# Run simulation
sim = GPLSimulation(frames)
final = sim.run(steps=50)
print(f"\nFinal state (after {len(sim.history)-1} steps):")
print(f"{'Node':>6} | {'x':>6} | {'θ':>4} | {'a':>8} | {'Change'}")
print("-" * 50)
for i, f in enumerate(final):
init_a = [10, 2, 8, 3, 12][i]
change = f"{f.a - init_a:+.2f}"
print(f"{i:>6} | {f.x:>6.1f} | {f.theta:>4} | {f.a:>8.2f} | {change}")
avg_a = sum(f.a for f in final) / len(final)
print(f"\nAverage activation: {avg_a:.2f}")
print("Converged to smooth, shared activation (energy minimum)")
def demo_chiral_isolation():
"""Demonstrate D/L orthogonality."""
print("\n" + "=" * 70)
print("CHIRAL ISOLATION DEMONSTRATION")
print("=" * 70)
# Create D and L chains
frames = []
# D-chain (chirality=0)
for i in range(3):
frames.append(Frame(theta=0, phi=0, psi=0, chi=0, a=10.0, x=float(i), y=0.0))
# L-chain (chirality=1)
for i in range(3):
frames.append(Frame(theta=0, phi=0, psi=0, chi=1, a=2.0, x=float(i), y=1.0))
print("\nInitial state: Two chains (D-chain at y=0, L-chain at y=1)")
print(f"{'Node':>6} | {'x':>6} | {'y':>6} | {'χ':>4} | {'a':>8} | {'Chain'}")
print("-" * 60)
for i, f in enumerate(frames):
chain = "D-chain" if f.chi == 0 else "L-chain"
print(f"{i:>6} | {f.x:>6.1f} | {f.y:>6.1f} | {f.chi:>4} | {f.a:>8.2f} | {chain}")
# Check weights
print("\nCross-chain weights (D to L):")
for i in range(3):
for j in range(3, 6):
w = compute_weight(frames[i], frames[j])
print(f" w({i},{j}) = {w:.3f} (should be 0.000)")
# Run simulation
sim = GPLSimulation(frames)
final = sim.run(steps=30)
print(f"\nFinal state:")
d_avg = sum(f.a for f in final[:3]) / 3
l_avg = sum(f.a for f in final[3:]) / 3
print(f" D-chain average: {d_avg:.2f}")
print(f" L-chain average: {l_avg:.2f}")
print(" Chains evolved independently (no crosstalk)")
def demo_vortex_formation():
"""Demonstrate vortex as rotational attractor."""
print("\n" + "=" * 70)
print("VORTEX FORMATION")
print("=" * 70)
# Create nodes in circle with θ matching angular position
n = 8
frames = []
for i in range(n):
angle = 2 * math.pi * i / n
theta = int((i * 16 / n) % 16) # θ matches position
f = Frame(
theta=theta,
phi=0,
psi=0,
chi=0,
a=5.0,
x=math.cos(angle),
y=math.sin(angle)
)
frames.append(f)
print(f"\nCircular arrangement: θ matches angular position")
print(f"{'Node':>6} | {'θ':>4} | {'Angle°':>8} | {'x':>8} | {'y':>8}")
print("-" * 60)
for i, f in enumerate(frames):
angle_deg = math.degrees(math.atan2(f.y, f.x))
print(f"{i:>6} | {f.theta:>4} | {angle_deg:>8.1f} | {f.x:>8.3f} | {f.y:>8.3f}")
# Check weights (should be high for neighbors, forming vortex)
print("\nNeighbor weights (high = vortex stable):")
for i in range(n):
j = (i + 1) % n
w = compute_weight(frames[i], frames[j])
print(f" w({i},{j}) = {w:.3f}")
avg_w = sum(compute_weight(frames[i], frames[(i+1)%n]) for i in range(n)) / n
print(f"\nAverage neighbor weight: {avg_w:.3f}")
print("High alignment → Vortex is stable attractor")
def demo_summary():
"""Summary of interaction law."""
print("\n" + "=" * 70)
print("INTERACTION LAW SUMMARY")
print("=" * 70)
print("""
The GPL Local Interaction Law:
1. WEIGHT FUNCTION
w_ij = cos(Δθ) · cos(Δφ) · (1 - 2|Δχ|) · exp(-|Δp|²/2σ²)
- cos(Δθ): Azimuthal alignment
- cos(Δφ): Polar alignment
- (1 - 2|Δχ|): Chirality match
- exp(...): Distance decay
2. FORCE EQUATION
F_ij = w_ij · (a_j - a_i) · direction
- Activation flows from high to low
- Modulated by rotational compatibility
3. EVOLUTION
a_i(t+1) = a_i(t) + α · Σ_j F_ij
- Gradient descent on energy landscape
- Converges to attractor
Key Behaviors:
- Aligned frames (Δθ=0): Strong attraction
- Orthogonal frames (Δθ=4): No coupling
- Opposite frames (Δθ=8): Repulsion
- Chiral mismatch (Δχ=1): Complete isolation
- Smooth θ gradients: Stable vortices
Computation = Frame field convergence to energy minimum
""")
if __name__ == "__main__":
demo_two_node_interaction()
demo_convergence()
demo_chiral_isolation()
demo_vortex_formation()
demo_summary()