Foundation for L10 resume-as-same-life persistence. Adds:
- core/array_codec.py: a leaf (numpy+base64) codec encoding arrays as
{dtype, shape, b64(raw bytes)} — BIT-EXACT, never decimal. Float round-trips
lose zero precision, so a restored versor keeps versor_condition < 1e-6 and a
replayed turn keeps its trace_hash. dtype carries byte order; float32 is never
conflated with float64.
- field/state.py: FieldState.to_dict/from_dict. Multivector arrays (F, holonomy)
go through the byte codec; energy/valence round-trip exactly via JSON-safe
helpers (lazy physics imports keep field/ cycle-free).
Exit gate (the scope's #1 risk, de-risked first): bit-exact round-trip AND
closure preserved — versor_condition(restored.F) == versor_condition(fs.F)
exactly. 10 codec/FieldState tests + 55 architectural-invariant/runtime tests
pass. Purely additive; no existing behavior changed.
Part of docs/analysis/L10-shapeBplus-persistence-scope-2026-06-05.md (Phase A).
54 lines
2.1 KiB
Python
54 lines
2.1 KiB
Python
"""Bit-exact (de)serialization of numpy arrays for deterministic persistence.
|
|
|
|
A numpy array encodes to ``{"dtype", "shape", "b64"}`` where ``b64`` is base64 of
|
|
the array's raw bytes. This is **bit-exact**: every float round-trips with zero
|
|
precision loss, so a restored versor keeps ``versor_condition < 1e-6`` and a
|
|
replayed turn keeps its ``trace_hash``.
|
|
|
|
NEVER serialize field arrays as decimal/JSON floats. Decimal truncates the
|
|
mantissa and silently breaks both closure and deterministic replay — the Cl(4,1)
|
|
float-truncation pitfall. ``dtype`` carries byte order (``'<f4'``/``'<f8'``), so
|
|
the encoding is portable, and ``float32`` is never conflated with ``float64``.
|
|
|
|
This module is a leaf: it imports only numpy + base64, so every layer (field,
|
|
vault, session, engine_state) can use it without an import cycle.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import base64
|
|
from typing import Any
|
|
|
|
import numpy as np
|
|
|
|
|
|
def encode_array(arr: np.ndarray) -> dict[str, Any]:
|
|
"""Encode a numpy array to a bit-exact, JSON-safe dict."""
|
|
contiguous = np.ascontiguousarray(arr)
|
|
return {
|
|
"dtype": contiguous.dtype.str, # byte-order-aware, e.g. '<f4', '<f8', '<i4'
|
|
"shape": list(contiguous.shape),
|
|
"b64": base64.b64encode(contiguous.tobytes()).decode("ascii"),
|
|
}
|
|
|
|
|
|
def decode_array(payload: dict[str, Any]) -> np.ndarray:
|
|
"""Decode a payload produced by ``encode_array`` back to an exact array.
|
|
|
|
Returns a writable copy (``np.frombuffer`` is read-only) so the restored
|
|
array can be composed and mutated like a freshly-built one.
|
|
"""
|
|
dtype = np.dtype(payload["dtype"])
|
|
raw = base64.b64decode(payload["b64"])
|
|
flat = np.frombuffer(raw, dtype=dtype)
|
|
return flat.reshape(payload["shape"]).copy()
|
|
|
|
|
|
def encode_optional_array(arr: np.ndarray | None) -> dict[str, Any] | None:
|
|
"""Encode an array, or return ``None`` for ``None`` (e.g. optional holonomy)."""
|
|
return None if arr is None else encode_array(arr)
|
|
|
|
|
|
def decode_optional_array(payload: dict[str, Any] | None) -> np.ndarray | None:
|
|
"""Decode an optional-array payload, or return ``None`` for ``None``."""
|
|
return None if payload is None else decode_array(payload)
|