Research-Stack/4-Infrastructure/shim/solids_physics_kernel_probe.py
2026-05-11 22:18:31 -05:00

350 lines
13 KiB
Python

#!/usr/bin/env python3
"""Receipt-backed solids-physics kernel probe.
This probe adds a small solids-domain route surface. It admits exact local
linear-elastic algebra fixtures and keeps anisotropy, plasticity, fracture,
wave-speed square roots, geometry, and boundary-value claims in HOLD.
"""
from __future__ import annotations
import hashlib
import json
from datetime import datetime, timezone
from fractions import Fraction
from pathlib import Path
from typing import Any
REPO = Path(__file__).resolve().parents[2]
OUT_DIR = REPO / "shared-data" / "data" / "solids_physics_kernels"
REGISTRY = OUT_DIR / "solids_physics_kernel_registry.json"
RECEIPT = OUT_DIR / "solids_physics_kernel_receipt.json"
SUMMARY = OUT_DIR / "solids_physics_kernel.md"
SOURCE_REFS = [
REPO / "shared-data/data/mass_number_transform_registry/mass_number_transform_registry_receipt.json",
REPO / "shared-data/data/cross_domain_kernel_adapters/cross_domain_kernel_adapter_registry_receipt.json",
]
def stable_json(obj: Any) -> str:
return json.dumps(obj, sort_keys=True, separators=(",", ":"), ensure_ascii=True)
def sha256_bytes(data: bytes) -> str:
return hashlib.sha256(data).hexdigest()
def hash_obj(obj: Any) -> str:
return sha256_bytes(stable_json(obj).encode("utf-8"))
def rel(path: Path) -> str:
try:
return str(path.relative_to(REPO))
except ValueError:
return str(path)
def file_hash(path: Path) -> str | None:
return sha256_bytes(path.read_bytes()) if path.exists() else None
def source_ref(path: Path) -> dict[str, Any]:
return {"path": rel(path), "exists": path.exists(), "sha256": file_hash(path)}
def frac_payload(value: Fraction | tuple[Fraction, ...]) -> Any:
if isinstance(value, tuple):
return [frac_payload(item) for item in value]
return {"numerator": value.numerator, "denominator": value.denominator, "decimal": float(value)}
def mn(a: Fraction, b: Fraction) -> Fraction:
return (a - b) / (a + b)
def hooke_stress(E: Fraction, strain: Fraction) -> Fraction:
return E * strain
def elastic_energy_from_strain(E: Fraction, strain: Fraction) -> Fraction:
return E * strain * strain / 2
def elastic_energy_from_stress(stress: Fraction, E: Fraction) -> Fraction:
return stress * stress / (2 * E)
def d_energy_d_strain(E: Fraction, strain: Fraction) -> Fraction:
return E * strain
def isotropic_shear_modulus(E: Fraction, nu: Fraction) -> Fraction:
return E / (2 * (1 + nu))
def isotropic_bulk_modulus(E: Fraction, nu: Fraction) -> Fraction:
return E / (3 * (1 - 2 * nu))
def harmonic_equal_thickness_modulus(E1: Fraction, E2: Fraction) -> Fraction:
return 2 * E1 * E2 / (E1 + E2)
def harmonic_from_mn(total: Fraction, x: Fraction) -> Fraction:
return total * (1 - x * x) / 2
def check_equal(name: str, compressed: Any, direct: Any) -> dict[str, Any]:
return {
"name": name,
"compressed": frac_payload(compressed),
"direct": frac_payload(direct),
"pass": compressed == direct,
}
def entry(
*,
entry_id: str,
kernel_opcode: str,
solids_role: str,
compressed_form: str,
expanded_form: str,
checks: list[dict[str, Any]],
decision: str,
residual_policy: str,
) -> dict[str, Any]:
item = {
"entry_id": entry_id,
"kernel_opcode": kernel_opcode,
"solids_role": solids_role,
"compressed_form": compressed_form,
"expanded_form": expanded_form,
"checks": checks,
"all_checks_pass": all(check.get("pass", False) for check in checks) if checks else None,
"decision": decision,
"residual_policy": residual_policy,
"claim_boundary": "solids route fixture only; not a finite-element solver or material model",
}
item["entry_hash"] = hash_obj({k: v for k, v in item.items() if k != "entry_hash"})
return item
def build_registry() -> dict[str, Any]:
E = Fraction(12)
strain = Fraction(1, 4)
stress = hooke_stress(E, strain)
nu = Fraction(1, 4)
E1 = Fraction(5)
E2 = Fraction(3)
total = E1 + E2
x = mn(E1, E2)
entries = [
entry(
entry_id="hooke_1d_stress",
kernel_opcode="LINEAR_MAP",
solids_role="one-dimensional linear elastic stress-strain map",
compressed_form="sigma = E*epsilon",
expanded_form="Hooke 1D linear elasticity",
checks=[check_equal("sigma_E12_eps1_4", stress, Fraction(3))],
decision="ACCEPT_LINEAR_ELASTIC_FIXTURE",
residual_policy="small-strain 1D fixture only; tensor strain, boundary conditions, and material range require receipts",
),
entry(
entry_id="elastic_energy_density",
kernel_opcode="DERIV_QUADRATIC",
solids_role="quadratic elastic energy and conjugate stress derivative",
compressed_form="U = E*epsilon^2/2; dU/depsilon = sigma",
expanded_form="U = sigma*epsilon/2 = sigma^2/(2E)",
checks=[
check_equal("energy_strain_vs_stress", elastic_energy_from_strain(E, strain), elastic_energy_from_stress(stress, E)),
check_equal("dU_deps_equals_sigma", d_energy_d_strain(E, strain), stress),
],
decision="ACCEPT_DERIVATIVE_FIXTURE",
residual_policy="linear-elastic scalar fixture only; path dependence and plastic work stay residualized",
),
entry(
entry_id="isotropic_moduli_transform",
kernel_opcode="RATIONAL_MATERIAL_TRANSFORM",
solids_role="Young/Poisson to shear and bulk modulus transform",
compressed_form="G=E/(2*(1+nu)); K=E/(3*(1-2*nu))",
expanded_form="isotropic linear elastic moduli relations",
checks=[
check_equal("G_E12_nu1_4", isotropic_shear_modulus(E, nu), Fraction(24, 5)),
check_equal("K_E12_nu1_4", isotropic_bulk_modulus(E, nu), Fraction(8)),
],
decision="ACCEPT_ISOTROPIC_FIXTURE",
residual_policy="isotropic linear-elastic fixture only; anisotropy and near-incompressibility require domain receipts",
),
entry(
entry_id="equal_thickness_series_modulus",
kernel_opcode="MN_PAIR_HARMONIC",
solids_role="two-layer equal-thickness series effective modulus",
compressed_form="E_eff = S/2*(1-MN(E1,E2)^2)",
expanded_form="E_eff = 2*E1*E2/(E1+E2)",
checks=[check_equal("series_E5_3", harmonic_from_mn(total, x), harmonic_equal_thickness_modulus(E1, E2))],
decision="ACCEPT_KERNEL_ADAPTER",
residual_policy="equal-thickness 1D series fixture only; laminate orientation, shear coupling, and boundary conditions require receipts",
),
entry(
entry_id="elastic_impedance_contrast",
kernel_opcode="MN_REFLECT",
solids_role="elastic/acoustic impedance contrast candidate at a solid boundary",
compressed_form="Gamma_Z = MN(Z2,Z1)",
expanded_form="Gamma_Z = (Z2-Z1)/(Z2+Z1)",
checks=[check_equal("solid_impedance_mn_9_4", mn(Fraction(9), Fraction(4)), Fraction(5, 13))],
decision="ACCEPT_KERNEL_ADAPTER",
residual_policy="contrast identity only; wave mode, incidence angle, attenuation, and boundary conditions require receipts",
),
entry(
entry_id="longitudinal_wave_speed_route",
kernel_opcode="ANALYTIC_SQRT_RATIO",
solids_role="elastic wave-speed route",
compressed_form="c = sqrt(E/rho) or tensor generalization",
expanded_form="wave speed candidate",
checks=[],
decision="HOLD_ANALYTIC_ADAPTER",
residual_policy="requires square-root precision, density/source receipts, mode convention, and material assumptions",
),
entry(
entry_id="von_mises_yield_route",
kernel_opcode="STRESS_INVARIANT",
solids_role="distortional-energy yield criterion route",
compressed_form="sigma_vm = invariant(stress deviator)",
expanded_form="von Mises candidate",
checks=[],
decision="HOLD_PLASTICITY_ADAPTER",
residual_policy="requires tensor convention, yield surface, hardening law, loading path, and experimental/source receipt",
),
entry(
entry_id="fracture_toughness_route",
kernel_opcode="ANALYTIC_SQRT_GEOMETRY",
solids_role="crack-tip stress intensity route",
compressed_form="K = Y*sigma*sqrt(pi*a)",
expanded_form="linear elastic fracture mechanics candidate",
checks=[],
decision="HOLD_FRACTURE_ADAPTER",
residual_policy="requires crack geometry, plane stress/strain convention, units, boundary conditions, and source receipt",
),
entry(
entry_id="anisotropic_stiffness_tensor_route",
kernel_opcode="TENSOR_CONSTITUTIVE_ADAPTER",
solids_role="anisotropic stiffness/compliance tensor route",
compressed_form="sigma_ij = C_ijkl*epsilon_kl",
expanded_form="anisotropic Hooke law candidate",
checks=[],
decision="HOLD_TENSOR_ADAPTER",
residual_policy="requires tensor index convention, symmetry class, units, material source, and closure receipt",
),
]
return {
"schema": "solids_physics_kernel_registry_v1",
"claim_boundary": (
"Solids-physics route registry only. Exact local linear-elastic algebra "
"fixtures may be accepted, but anisotropic, plasticity, fracture, wave, "
"geometry, boundary-value, and material-model claims stay HOLD until "
"source data, units, conventions, and residual policies are receipted."
),
"canonical_statement": (
"Solids physics exposes reusable linear maps, quadratic derivative "
"kernels, rational modulus transforms, and MN boundary contrasts; "
"material truth lives behind adapter closure."
),
"entries": entries,
"entry_count": len(entries),
"status_counts": {
status: sum(1 for item in entries if item["decision"] == status)
for status in sorted({item["decision"] for item in entries})
},
}
def build_receipt(registry: dict[str, Any]) -> dict[str, Any]:
accepted_statuses = {
"ACCEPT_LINEAR_ELASTIC_FIXTURE",
"ACCEPT_DERIVATIVE_FIXTURE",
"ACCEPT_ISOTROPIC_FIXTURE",
"ACCEPT_KERNEL_ADAPTER",
}
accepted_checks_pass = all(
item["all_checks_pass"] is True
for item in registry["entries"]
if item["decision"] in accepted_statuses
)
receipt = {
"schema": "solids_physics_kernel_receipt_v1",
"generated_at_utc": datetime.now(timezone.utc).isoformat(),
"timestamp_role": "metadata_only",
"generated_at_utc_included_in_receipt_hash": False,
"registry_path": rel(REGISTRY),
"registry_hash": hash_obj(registry),
"source_refs": [source_ref(path) for path in SOURCE_REFS],
"entry_count": registry["entry_count"],
"status_counts": registry["status_counts"],
"accepted_checks_pass": accepted_checks_pass,
"decision": "HOLD_SOLIDS_DOMAIN_WITH_ACCEPTED_FIXTURES" if accepted_checks_pass else "HOLD_DIAGNOSTIC",
"claim_boundary": registry["claim_boundary"],
}
receipt["receipt_hash"] = sha256_bytes(
stable_json({k: v for k, v in receipt.items() if k not in {"receipt_hash", "generated_at_utc"}}).encode("utf-8")
)
return receipt
def write_summary(registry: dict[str, Any], receipt: dict[str, Any]) -> None:
lines = [
"# Solids Physics Kernel Probe",
"",
f"Decision: `{receipt['decision']}` ",
f"Receipt hash: `{receipt['receipt_hash']}`",
"",
registry["claim_boundary"],
"",
"## Canonical Statement",
"",
registry["canonical_statement"],
"",
"## Entry Table",
"",
"| Entry | Kernel | Decision | Role |",
"|---|---|---|---|",
]
for item in registry["entries"]:
lines.append(
f"| `{item['entry_id']}` | `{item['kernel_opcode']}` | "
f"`{item['decision']}` | {item['solids_role']} |"
)
lines.extend(["", "## Guardrail", "", registry["claim_boundary"]])
SUMMARY.write_text("\n".join(lines) + "\n", encoding="utf-8")
def main() -> int:
OUT_DIR.mkdir(parents=True, exist_ok=True)
registry = build_registry()
receipt = build_receipt(registry)
REGISTRY.write_text(json.dumps(registry, indent=2, sort_keys=True) + "\n", encoding="utf-8")
RECEIPT.write_text(json.dumps(receipt, indent=2, sort_keys=True) + "\n", encoding="utf-8")
write_summary(registry, receipt)
print(
json.dumps(
{
"registry": rel(REGISTRY),
"receipt": rel(RECEIPT),
"summary": rel(SUMMARY),
"receipt_hash": receipt["receipt_hash"],
"decision": receipt["decision"],
"status_counts": registry["status_counts"],
},
indent=2,
sort_keys=True,
)
)
return 0
if __name__ == "__main__":
raise SystemExit(main())