diff --git a/generate/problem_frame_contracts.py b/generate/problem_frame_contracts.py index cfd181a8..e1054ebc 100644 --- a/generate/problem_frame_contracts.py +++ b/generate/problem_frame_contracts.py @@ -12,7 +12,9 @@ Contract dispatch is deliberately narrow: from __future__ import annotations import re +import numpy as np from dataclasses import dataclass +from algebra.backend import versor_condition from generate.construction_affordances import ( ConstructionContract, @@ -69,14 +71,39 @@ def get_contract_family_id(candidate_organ: str) -> str | None: return contract.family.family_id if contract else None +@dataclass(frozen=True, slots=True) +class VersorBinding: + source_span: tuple[int, int] + semantic_identity: str + geometric_payload: np.ndarray + versor_error: float + + def __post_init__(self): + if not isinstance(self.geometric_payload, np.ndarray): + raise TypeError("geometric_payload must be a numpy.ndarray") + if self.geometric_payload.dtype != np.float64: + raise TypeError("geometric_payload must be float64") + if not self.geometric_payload.flags.c_contiguous: + raise ValueError("geometric_payload must be C-contiguous") + if self.geometric_payload.shape != (32,): + raise ValueError("geometric_payload must be of shape (32,)") + + # Mathematically assert that versor_error < 1e-6 + # Downcast strictly for the condition check since the core engine FFI expects f32 + actual_error = float(versor_condition(self.geometric_payload.astype(np.float32))) + if self.versor_error >= 1e-6 or actual_error >= 1e-6: + raise ValueError(f"versor_error {max(self.versor_error, actual_error)} exceeds 1e-6 threshold") + + @dataclass(frozen=True, slots=True) class ContractAssessment: - candidate_organ: str - missing_bindings: tuple[str, ...] - unresolved_hazards: tuple[str, ...] - runnable: bool - explanation: str - evidence_spans: tuple[SourceSpan, ...] + bindings: list[VersorBinding] + + @property + def is_runnable(self) -> bool: + if not self.bindings: + return False + return all(b.versor_error < 1e-6 for b in self.bindings) def _roles(frame: ProblemFrame, relation_type: str) -> set[str]: diff --git a/scripts/test_ffi_layout.py b/scripts/test_ffi_layout.py new file mode 100644 index 00000000..bac79464 --- /dev/null +++ b/scripts/test_ffi_layout.py @@ -0,0 +1,35 @@ +import os +import sys + +# Ensure backend uses Rust +os.environ["CORE_BACKEND"] = "rust" + +import numpy as np +from generate.problem_frame_contracts import VersorBinding +from algebra.backend import using_rust + +def test_ffi_layout(): + if not using_rust(): + print("Warning: Rust backend not loaded!") + + # A valid unit versor (scalar 1.0) + payload = np.zeros(32, dtype=np.float64) + payload[0] = 1.0 + + print(f"payload flags before binding:\n{payload.flags}") + + # Instantiate VersorBinding. + binding = VersorBinding( + source_span=(0, 10), + semantic_identity="test_identity", + geometric_payload=payload, + versor_error=0.0 + ) + + print("\nVersorBinding successfully instantiated!") + print(f"Versor Error Validation: passed") + print(f"Memory Layout is C-Contiguous: {binding.geometric_payload.flags.c_contiguous}") + print(f"Dtype: {binding.geometric_payload.dtype}") + +if __name__ == "__main__": + test_ffi_layout()