mirror of
https://github.com/allaunthefox/Research-Stack.git
synced 2026-07-31 03:05:21 +00:00
231 lines
7.3 KiB
Python
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())
|