Runs the ACTUAL nodal dynamics ∂EPI/∂t = νf·ΔNFR on the arithmetic network to settle the one dynamical question left open by Examples 100 and 101: does the flow make composites move TOWARD primes (the theory's §7.1 reading "primes act as sinks that attract nearby composites toward equilibrium")? The measured answer is a precise refinement: primes are the EQUILIBRIA of the flow (the §4 primality theorem in motion), but they are NOT dynamical attractors — composites do not flow toward them.
The nodal equation ∂EPI/∂t = νf·ΔNFR has fixed points exactly where the forcing vanishes: νf > 0 ⟹ a node is at rest ⟺ ΔNFR = 0. Two distinct ΔNFR's live on the arithmetic network (Example 101 kept them separate):
• ΔNFR_arith — the per-node arithmetic pressure from (Ω, τ, σ); = 0 ⟺ prime (the §4 theorem). It is a FIXED label, independent of EPI. • ΔNFR_graph — the canonical diffusion coupling (neighbour_mean − self) = −(L_rw·EPI); it evolves and relaxes (Example 99).
Running each flow answers the §7.1 question directly and measurably.
FLOW 1 (arithmetic pressure ∂EPI/∂t = νf·ΔNFR_arith): • mean|ΔNFR| is CONSTANT — there is no relaxation; it is a fixed forcing. • Every prime is FROZEN (drift = 0, exactly): primes are the zero-velocity rest points. This is the §4 primality theorem in motion: ΔNFR = 0 ⟺ EPI frozen ⟺ prime. • Composites DRIFT, at a rate graded by Ω: r(drift, Ω) ≈ 0.93. They move AWAY from — not toward — the prime rest state. • The prime fixed points are MARGINAL (neutral): ΔNFR_arith does not depend on EPI, so there is no restoring force and no basin. A prime is a rest point for ANY EPI value.
FLOW 2 (graph diffusion ∂EPI/∂t = νf·ΔNFR_graph): • This flow DOES relax — to the degree-weighted uniform state (the conserved attractor of Example 99). • That attractor is the high-degree = COMPOSITE bulk. Primes are pulled UP toward the composites (the OPPOSITE of §7.1's "attract composites"). • Isolated large primes (Example 101) are frozen at their seed — they do not participate at all.
#!/usr/bin/env python3
"""
Example 102 — The Nodal Flow on Numbers: Primes as Equilibria, Not Attractors
============================================================================
Runs the ACTUAL nodal dynamics ∂EPI/∂t = νf·ΔNFR on the arithmetic
network to settle the one dynamical question left open by Examples 100 and
101: does the flow make composites move TOWARD primes (the theory's §7.1
reading "primes act as sinks that attract nearby composites toward
equilibrium")? The measured answer is a precise refinement: primes are the
EQUILIBRIA of the flow (the §4 primality theorem in motion), but they are
NOT dynamical attractors — composites do not flow toward them.
Physics
-------
The nodal equation ∂EPI/∂t = νf·ΔNFR has fixed points exactly where the
forcing vanishes: νf > 0 ⟹ a node is at rest ⟺ ΔNFR = 0. Two distinct
ΔNFR's live on the arithmetic network (Example 101 kept them separate):
• ΔNFR_arith — the per-node arithmetic pressure from (Ω, τ, σ); = 0 ⟺
prime (the §4 theorem). It is a FIXED label, independent of EPI.
• ΔNFR_graph — the canonical diffusion coupling (neighbour_mean − self)
= −(L_rw·EPI); it evolves and relaxes (Example 99).
Running each flow answers the §7.1 question directly and measurably.
The measured result (all verified below, N = 140)
-------------------------------------------------
FLOW 1 (arithmetic pressure ∂EPI/∂t = νf·ΔNFR_arith):
• mean|ΔNFR| is CONSTANT — there is no relaxation; it is a fixed forcing.
• Every prime is FROZEN (drift = 0, exactly): primes are the zero-velocity
rest points. This is the §4 primality theorem in motion: ΔNFR = 0 ⟺
EPI frozen ⟺ prime.
• Composites DRIFT, at a rate graded by Ω: r(drift, Ω) ≈ 0.93. They move
AWAY from — not toward — the prime rest state.
• The prime fixed points are MARGINAL (neutral): ΔNFR_arith does not
depend on EPI, so there is no restoring force and no basin. A prime is
a rest point for ANY EPI value.
FLOW 2 (graph diffusion ∂EPI/∂t = νf·ΔNFR_graph):
• This flow DOES relax — to the degree-weighted uniform state (the
conserved attractor of Example 99).
• That attractor is the high-degree = COMPOSITE bulk. Primes are pulled
UP toward the composites (the OPPOSITE of §7.1's "attract composites").
• Isolated large primes (Example 101) are frozen at their seed — they do
not participate at all.
Honest scope
------------
- This REFINES theory §7.1. The STATIC half is correct (Example 101: primes
sit at ≈ 16× lower |Φ_s| — they are potential-field "sinks" in the
geometric sense). The DYNAMICAL half ("attract nearby composites toward
equilibrium") is NOT realized by the nodal flow: composites either drift
away (arithmetic flow) or pull primes up (diffusion flow). Low static
potential does not produce a dynamical basin here.
- The strong POSITIVE result is the §4 theorem made dynamical: primes are
exactly the equilibria (zero-forcing rest points) of the arithmetic
nodal flow. That is a faithful TNFR statement, not a new theorem — it is
ΔNFR = 0 read as ∂EPI/∂t = 0.
- ΔNFR_arith (per-node, from Ω,τ,σ) is NOT the graph-diffusion Laplacian;
the two flows are genuinely different (Example 101's caveat). Neither
produces attraction toward primes.
- This extends the Example 101 inversion ("primes are inert isolates, not
central attractors") from the static picture to the dynamical one.
References
----------
- theory/TNFR_NUMBER_THEORY.md §4 (ΔNFR=0 theorem), §7.1 (Φ_s sinks claim)
- examples/07_number_theory/101_numbers_as_coupled_network.py (static: Ω-graded periphery)
- examples/08_emergent_geometry/99_structural_diffusion.py (diffusion relaxes to degree-uniform)
- src/tnfr/dynamics/canonical.py (compute_canonical_nodal_derivative)
- src/tnfr/physics/structural_diffusion.py (structural_diffusion_operator)
- AGENTS.md §"Foundational Physics" (the nodal equation, fixed points)
"""
import os
import statistics
import sys
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..", "src"))
import networkx as nx
import numpy as np
from sympy import factorint, isprime
from tnfr.dynamics.canonical import compute_canonical_nodal_derivative
from tnfr.mathematics.number_theory import ArithmeticTNFRNetwork
from tnfr.physics.structural_diffusion import structural_diffusion_operator
N = 140
def _build():
"""Arithmetic network with seed EPI/νf and the fixed arithmetic ΔNFR."""
net = ArithmeticTNFRNetwork(max_number=N)
G = net.graph.to_undirected()
nodes = sorted(G.nodes())
props = {n: net.get_tnfr_properties(n) for n in nodes}
epi0 = {n: float(props[n]["EPI"]) for n in nodes}
vf = {n: float(props[n]["nu_f"]) for n in nodes}
dnfr = {n: float(props[n]["DELTA_NFR"]) for n in nodes}
omega = {n: sum(factorint(n).values()) for n in nodes}
return net, G, nodes, epi0, vf, dnfr, omega
# ============================================================================
# EXPERIMENT 1: Arithmetic flow — primes are the fixed points (§4 in motion)
# ============================================================================
def experiment_1_arithmetic_flow(nodes, epi0, vf, dnfr, omega):
"""∂EPI/∂t = νf·ΔNFR_arith: primes frozen, composites Ω-graded drift."""
print("=" * 72)
print("EXPERIMENT 1: Arithmetic Flow — Primes Are the Fixed Points")
print("=" * 72)
print()
print("∂EPI/∂t = νf·ΔNFR_arith. ΔNFR_arith is a FIXED per-node property")
print("(from Ω,τ,σ) and = 0 ⟺ prime (§4). Integrate and watch who moves.")
print()
primes = [n for n in nodes if isprime(n)]
comps = [n for n in nodes if not isprime(n)]
dt = 0.05
epi = dict(epi0)
p0 = statistics.mean(abs(dnfr[n]) for n in nodes)
for _ in range(40):
for n in nodes:
d = compute_canonical_nodal_derivative(
vf[n], dnfr[n], validate_units=False
).derivative
epi[n] += dt * d
p1 = statistics.mean(abs(dnfr[n]) for n in nodes)
drift = {n: abs(epi[n] - epi0[n]) for n in nodes}
frozen = sum(1 for p in primes if drift[p] < 1e-12)
om = np.array([omega[n] for n in nodes], float)
dr = np.array([drift[n] for n in nodes], float)
print(
f" mean|ΔNFR|: t=0 {p0:.4f} -> t=end {p1:.4f} "
f"(CONSTANT: a fixed forcing, no relaxation)"
)
print(f" primes FROZEN (drift < 1e-12): {frozen}/{len(primes)}")
print(
f" mean EPI drift: primes {statistics.mean(drift[p] for p in primes):.4f}"
f" composites {statistics.mean(drift[c] for c in comps):.4f}"
)
print(
f" r(drift, Ω) = {np.corrcoef(dr, om)[0, 1]:.3f} "
f"(composite drift is Ω-graded)"
)
print()
print("VERDICT: primes are EXACTLY the zero-velocity rest points — the §4")
print("theorem in motion (ΔNFR=0 ⟺ ∂EPI/∂t=0 ⟺ prime). Composites drift")
print("AWAY at an Ω-graded rate; they do NOT flow toward the primes.")
print()
# ============================================================================
# EXPERIMENT 2: Stability — the prime equilibria are marginal (no basin)
# ============================================================================
def experiment_2_marginal_stability(net, nodes, vf, dnfr):
"""ΔNFR_arith is independent of EPI ⟹ primes are neutral fixed points."""
print("=" * 72)
print("EXPERIMENT 2: The Prime Equilibria Are Marginal (No Restoring Force)")
print("=" * 72)
print()
print("Is a prime an ATTRACTOR? Linearize: ∂(δEPI)/∂t = νf·(∂ΔNFR/∂EPI)·δEPI.")
print("ΔNFR_arith depends on (Ω,τ,σ), NOT on EPI, so ∂ΔNFR/∂EPI = 0.")
print()
primes = [n for n in nodes if isprime(n)]
p = primes[3]
# perturb EPI of a prime; its ΔNFR (and hence velocity) is unchanged
base_v = compute_canonical_nodal_derivative(
vf[p], dnfr[p], validate_units=False
).derivative
print(f" prime {p}: ΔNFR_arith = {dnfr[p]:.2e} -> ∂EPI/∂t = {base_v:.2e}")
print(f" perturbing EPI({p}) by any amount leaves ΔNFR (and velocity) at 0")
print(" -> no restoring force, no basin: a continuum of rest states")
print()
print("VERDICT: the prime fixed points are MARGINAL (neutral), not")
print("attracting. There is no dynamical pull toward a prime — the flow")
print("simply VANISHES there. 'Equilibrium' ≠ 'attractor'.")
print()
# ============================================================================
# EXPERIMENT 3: Diffusion flow — the attractor is the composite bulk
# ============================================================================
def experiment_3_diffusion_flow(G, nodes, epi0, vf):
"""∂EPI/∂t = νf·ΔNFR_graph relaxes to degree-uniform; primes pulled up."""
print("=" * 72)
print("EXPERIMENT 3: Diffusion Flow — the Attractor Is the Composite Bulk")
print("=" * 72)
print()
print("∂EPI/∂t = νf·(neighbour_mean − self) = νf·(−L_rw·EPI). This flow")
print("DOES relax (Example 99) — to the degree-weighted uniform state.")
print()
giant = G.subgraph(max(nx.connected_components(G), key=len)).copy()
gnodes = sorted(giant.nodes())
gidx = {n: i for i, n in enumerate(gnodes)}
_, lrw = structural_diffusion_operator(giant)
epi_v = np.array([epi0[n] for n in gnodes])
vf_v = np.array([vf[n] for n in gnodes])
gp = [n for n in gnodes if isprime(n)]
gc = [n for n in gnodes if not isprime(n)]
deg = dict(giant.degree())
mp0 = epi_v[[gidx[p] for p in gp]].mean()
mc0 = epi_v[[gidx[c] for c in gc]].mean()
dt = 0.1
for _ in range(300):
epi_v = epi_v + dt * vf_v * (-(lrw @ epi_v))
mp1 = epi_v[[gidx[p] for p in gp]].mean()
mc1 = epi_v[[gidx[c] for c in gc]].mean()
degw = np.array([deg[n] for n in gnodes], float)
eq = (degw * np.array([epi0[n] for n in gnodes])).sum() / degw.sum()
iso = [n for n in nodes if G.degree(n) == 0]
print(f" giant core: {len(gnodes)} nodes ({len(gp)} primes, {len(gc)} comps)")
print(f" mean EPI prime: t0 {mp0:.3f} -> t_end {mp1:.3f}")
print(f" mean EPI composite: t0 {mc0:.3f} -> t_end {mc1:.3f}")
print(f" degree-weighted equilibrium (the attractor) = {eq:.3f}")
print(
f" isolated large primes (frozen at seed, never participate): " f"{len(iso)}"
)
print()
print(" -> primes are pulled UP toward the composite bulk (the high-")
print(" degree attractor), the OPPOSITE of §7.1's 'primes attract")
print(" composites'. The bulk wins; the primes are dragged along.")
print()
# ============================================================================
# EXPERIMENT 4: Synthesis — §7.1 refined
# ============================================================================
def experiment_4_synthesis():
"""Static sink: yes. Dynamical attractor: no."""
print("=" * 72)
print("EXPERIMENT 4: Synthesis — Refining the §7.1 'Sink' Reading")
print("=" * 72)
print()
print(" §7.1 claim: 'primes act as sinks in the potential field — they")
print(" attract nearby composites toward equilibrium.' Split it in two:")
print()
print(" STATIC half (potential geometry): CORRECT (Example 101)")
print(" primes sit at ≈ 16× lower |Φ_s| — sinks in the geometric sense.")
print(" DYNAMICAL half (attraction/basin): NOT REALIZED (this example)")
print(" arithmetic flow: composites drift AWAY (Ω-graded), primes are")
print(" marginal rest points — no basin;")
print(" diffusion flow: the attractor is the composite bulk; primes")
print(" are pulled UP toward it (the opposite direction).")
print()
print(" So the correct dynamical statement is:")
print(" primes are the EQUILIBRIA of the nodal flow (ΔNFR=0 = §4),")
print(" but NOT its attractors. Low static potential ≠ dynamical pull.")
print()
def main():
print()
print(" TNFR Example 102: The Nodal Flow on Numbers")
print(" Primes as equilibria, not attractors")
print(" ===========================================")
print()
net, G, nodes, epi0, vf, dnfr, omega = _build()
experiment_1_arithmetic_flow(nodes, epi0, vf, dnfr, omega)
experiment_2_marginal_stability(net, nodes, vf, dnfr)
experiment_3_diffusion_flow(G, nodes, epi0, vf)
experiment_4_synthesis()
print("=" * 72)
print("WHAT THIS ESTABLISHES")
print("=" * 72)
print()
print("Running the actual nodal equation ∂EPI/∂t = νf·ΔNFR on the")
print("arithmetic network settles the §7.1 question. The strong positive")
print("result is the §4 primality theorem made dynamical: primes are")
print("EXACTLY the equilibria (zero-forcing rest points) of the")
print("arithmetic flow — frozen, while composites drift away at an")
print("Ω-graded rate. But primes are NOT attractors: the arithmetic")
print("equilibria are marginal (no basin), and the diffusion flow")
print("relaxes to the composite bulk, pulling primes UP. The §7.1")
print("'primes attract composites' is a static potential-geometry fact")
print("(primes at low Φ_s, true) overstated as a dynamical attraction")
print("the flow does not produce — extending the Example 101 inversion")
print("to the dynamical level. Numbers reflect TNFR dynamics deeply, but")
print("the honest reflection is 'primes = inert equilibria', not")
print("'primes = attractors'.")
print()
if __name__ == "__main__":
main()