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