Research-Stack/2-Search-Space/simulations/Newtonian-Superfluid-Simulation/custom_stack/superfluid_semantic_adapter.py

231 lines
7.3 KiB
Python

"""
Semantic-mass adapter for the Newtonian Superfluid Simulation.
This module separates the particle-force simulation from the custom ontology stack.
It exports finite, auditable state summaries that can be consumed by:
- WebGPU / Three.js renderers
- JSON-LD graph exporters
- Lean/Wasm bridge prototypes
- FAMM / Inverted FAMM route-memory modules
Boundary:
These values are dimensionless semantic/diagnostic metrics. They are not SI mass.
"""
from __future__ import annotations
from dataclasses import asdict, dataclass
from typing import Any, Dict, Tuple
import numpy as np
Array = np.ndarray
Q16_ONE = 1 << 16
@dataclass(frozen=True)
class SuperfluidParams:
"""Simulation constants matching the repository's force model."""
box_size: float = 50.0
dt: float = 0.04
k_gravity: float = 100.0
k_repel: float = 100.0
softening: float = 1.2
r_max: float = 10.0
damping: float = 0.95
max_vel: float = 12.0
@dataclass(frozen=True)
class SemanticMassState:
"""Dimensionless state exported to the ontology/wiki/render stack."""
node_id: int
particle_count: int
mass_number: float
semantic_density: float
torsion: float
kinetic_pressure: float
basin_strength: float
receipt_coverage: float
gate: str
def to_json_dict(self) -> Dict[str, Any]:
return asdict(self)
def to_q16_dict(self) -> Dict[str, int]:
return {
"node_id": int(self.node_id),
"mass_number_q16": float_to_q16(self.mass_number),
"semantic_density_q16": float_to_q16(self.semantic_density),
"torsion_q16": float_to_q16(self.torsion),
"kinetic_pressure_q16": float_to_q16(self.kinetic_pressure),
"basin_strength_q16": float_to_q16(self.basin_strength),
"receipt_coverage_q16": float_to_q16(self.receipt_coverage),
"gate_scope": 1 if self.gate == "V_scope" else 0,
}
def clamp01(x: float) -> float:
if not np.isfinite(x):
return 0.0
return float(max(0.0, min(1.0, x)))
def float_to_q16(x: float) -> int:
"""Convert a [0, 1] float-like metric to unsigned Q16.16 integer form."""
return int(round(clamp01(x) * Q16_ONE))
def initialize_state(n_particles: int, params: SuperfluidParams, seed: int | None = None) -> Tuple[Array, Array]:
rng = np.random.default_rng(seed)
pos = rng.random((n_particles, 2), dtype=np.float64) * params.box_size
vel = np.zeros((n_particles, 2), dtype=np.float64)
return pos, vel
def compute_forces(pos: Array, params: SuperfluidParams) -> Array:
"""Vectorized local attraction/repulsion force field.
Force law:
attraction ~ +k_gravity / (r^2 + softening)
repulsion ~ -k_repel / (r^4 + 0.1)
The diagonal self-force is masked out.
"""
delta = pos[None, :, :] - pos[:, None, :]
dist_sq = np.sum(delta * delta, axis=2)
dist = np.sqrt(dist_sq) + 1.0e-9
mask = (dist > 1.0e-8) & (dist < params.r_max)
direction = delta / dist[:, :, None]
f_grav = params.k_gravity / (dist_sq + params.softening)
f_repel = -params.k_repel / ((dist_sq * dist_sq) + 0.1)
scalar = np.where(mask, f_grav + f_repel, 0.0)
return np.sum(direction * scalar[:, :, None], axis=1)
def step_superfluid(pos: Array, vel: Array, params: SuperfluidParams) -> Tuple[Array, Array, Array]:
"""Advance one finite simulation step and return pos, vel, forces."""
forces = compute_forces(pos, params)
vel = vel * params.damping + forces * params.dt
speed = np.linalg.norm(vel, axis=1, keepdims=True)
safe_speed = np.maximum(speed, 1.0e-9)
vel = np.where(speed > params.max_vel, vel * (params.max_vel / safe_speed), vel)
pos = pos + vel * params.dt
for d in range(2):
out_min = pos[:, d] < 0.0
out_max = pos[:, d] > params.box_size
if np.any(out_min):
pos[out_min, d] = 0.0
vel[out_min, d] = -vel[out_min, d] * 0.5
if np.any(out_max):
pos[out_max, d] = params.box_size
vel[out_max, d] = -vel[out_max, d] * 0.5
return pos, vel, forces
def pairwise_density(pos: Array, params: SuperfluidParams) -> float:
"""Fraction of local neighbor pairs within interaction range."""
n = pos.shape[0]
if n <= 1:
return 0.0
delta = pos[None, :, :] - pos[:, None, :]
dist_sq = np.sum(delta * delta, axis=2)
mask = (dist_sq > 1.0e-12) & (dist_sq < params.r_max * params.r_max)
return clamp01(float(np.count_nonzero(mask)) / float(n * (n - 1)))
def angular_torsion(pos: Array, vel: Array, params: SuperfluidParams) -> float:
"""Dimensionless swirl/torsion proxy around the center of mass."""
center = np.mean(pos, axis=0, keepdims=True)
rel = pos - center
radius = np.linalg.norm(rel, axis=1)
speed = np.linalg.norm(vel, axis=1)
cross_z = rel[:, 0] * vel[:, 1] - rel[:, 1] * vel[:, 0]
denom = np.maximum(radius * speed, 1.0e-9)
local_spin = np.abs(cross_z) / denom
weighted = local_spin * np.tanh(speed / max(params.max_vel, 1.0e-9))
return clamp01(float(np.mean(weighted)))
def kinetic_pressure(vel: Array, params: SuperfluidParams) -> float:
speed = np.linalg.norm(vel, axis=1)
return clamp01(float(np.mean(speed * speed) / max(params.max_vel * params.max_vel, 1.0e-9)))
def basin_strength_from_forces(forces: Array, params: SuperfluidParams) -> float:
"""Stable basins are low-force, low-residual regions in this first adapter."""
force_mag = np.linalg.norm(forces, axis=1)
force_scale = max(params.k_gravity / max(params.softening, 1.0e-9), 1.0e-9)
normalized_force = clamp01(float(np.mean(force_mag) / force_scale))
return clamp01(1.0 - normalized_force)
def summarize_semantic_state(
node_id: int,
pos: Array,
vel: Array,
forces: Array,
params: SuperfluidParams,
receipt_coverage: float = 0.25,
) -> SemanticMassState:
density = pairwise_density(pos, params)
torsion = angular_torsion(pos, vel, params)
pressure = kinetic_pressure(vel, params)
basin = basin_strength_from_forces(forces, params)
# First-pass semantic mass: density carries the node, pressure activates it,
# torsion discounts it when unresolved stress dominates.
mass_number = clamp01(0.55 * density + 0.35 * pressure + 0.10 * basin - 0.15 * torsion)
receipt_coverage = clamp01(receipt_coverage)
gate = "V_scope" if receipt_coverage >= 1.0 else "U_scope"
return SemanticMassState(
node_id=node_id,
particle_count=int(pos.shape[0]),
mass_number=mass_number,
semantic_density=density,
torsion=torsion,
kinetic_pressure=pressure,
basin_strength=basin,
receipt_coverage=receipt_coverage,
gate=gate,
)
def run_probe(
n_particles: int = 350,
steps: int = 200,
seed: int | None = 7,
params: SuperfluidParams | None = None,
) -> SemanticMassState:
"""Run a finite probe and return the final semantic summary."""
params = params or SuperfluidParams()
pos, vel = initialize_state(n_particles, params, seed=seed)
forces = np.zeros_like(pos)
for _ in range(steps):
pos, vel, forces = step_superfluid(pos, vel, params)
return summarize_semantic_state(1, pos, vel, forces, params)
if __name__ == "__main__":
state = run_probe()
print(state.to_json_dict())
print(state.to_q16_dict())