core/field/propagate.py
Shay 15ed2cee89
Tighten hot-path backend consistency
- route SessionContext anchor CGA through algebra.backend
- move aspect-weight carry into FieldEnergyOperator.compute
- remove duplicated propagate_step threshold patch and per-step imports
- add carry_aspect_weight tests for parity, fallback, and propagation preservation
- preserve normalization, propagation, vault, Rust dispatch, and energy cadence semantics
2026-05-15 08:14:38 -07:00

72 lines
2.7 KiB
Python

"""
Field propagation — the generation heartbeat.
Each step: F <- versor_apply(V, F)
V is the rotor for the current node's outgoing edge in the vocab manifold.
No correction. No normalization. No conditional branching. The loop is tight.
Energy recomputation: after each versor step the EnergyProfile carried in
FieldState is refreshed. The refresh uses only the structural inputs that
are available without external context (activation_count tracks steps taken;
cycle is the new step index). Convergence density and morphology features
are not available inside propagate_step — they are set at the injection gate
and carried forward unchanged. Coherence residual is zero inside a clean
propagation path (no corrective pass is applied here). This is intentional:
propagation is not correction.
Hot path routes through algebra.backend, which dispatches to the Rust
extension (core_rs) when available and falls back to pure Python silently.
"""
from algebra.backend import versor_apply
from core.physics.energy import FieldEnergyOperator
from field.state import FieldState
_energy_op = FieldEnergyOperator()
def propagate_step(state: FieldState, V) -> FieldState:
"""
Apply one versor transition and refresh the energy profile.
V is the edge rotor from the current node.
Returns a new FieldState one step forward on the manifold.
Energy recomputation policy:
- activation_count increments by 1 per step (field is actively propagating).
- current_cycle = new step index (monotonic proxy for time).
- last_activation_cycle stays at the value set at injection (the gate
records when this region was first injected; propagation does not reset
that anchor).
- coherence_residual = 0.0 (propagation is not a corrective pass).
- convergence_density and morphology_features are inherited from the
existing EnergyProfile when one is present; otherwise defaults apply.
- anchor_adjacent is inherited unchanged.
"""
new_F = versor_apply(V, state.F)
new_step = state.step + 1
if state.energy is not None:
ep = state.energy
new_energy = _energy_op.compute(
convergence_density=ep.convergence_density,
activation_count=ep.activation_count + 1,
current_cycle=new_step,
last_activation_cycle=ep.last_activation_cycle,
coherence_residual=0.0,
morphology_features=None,
anchor_adjacent=ep.anchor_adjacent,
carry_aspect_weight=ep.aspect_weight,
)
else:
new_energy = None
return FieldState(
F=new_F,
node=state.node,
step=new_step,
holonomy=state.holonomy,
energy=new_energy,
valence=state.valence,
)