core/packs/evidence.py

64 lines
1.8 KiB
Python

"""Measured holonomy-resonance evidence helpers for ADR-0015."""
from __future__ import annotations
from dataclasses import dataclass
import numpy as np
from algebra.cga import cga_inner
from algebra.holonomy import holonomy_encode
UNDETERMINED_SCORE: float = float("nan")
"""Numeric sentinel for evidence that could not be computed.
An empty evidence-pair set is not neutral evidence. Returning ``0.0``
made "no evidence" indistinguishable from a real measured zero. ``NaN``
keeps the return type stable while forcing callers to treat the score as
UNDETERMINED rather than as weak/negative evidence.
"""
@dataclass(frozen=True, slots=True)
class ResonanceEvidence:
case_id: str
aligned_score: float
contrast_score: float
@property
def passes(self) -> bool:
if not np.isfinite(self.aligned_score) or not np.isfinite(self.contrast_score):
return False
return self.aligned_score > self.contrast_score
def encode_clause(manifold, tokens: tuple[str, ...] | list[str]) -> np.ndarray:
return holonomy_encode([manifold.get_versor(token) for token in tokens])
def mean_pair_score(manifold, pairs: tuple[tuple[str, str], ...]) -> float:
if not pairs:
return UNDETERMINED_SCORE
return float(
np.mean(
[
cga_inner(manifold.get_versor(left), manifold.get_versor(right))
for left, right in pairs
]
)
)
def resonance_evidence(
*,
case_id: str,
manifold,
aligned_pairs: tuple[tuple[str, str], ...],
contrast_pairs: tuple[tuple[str, str], ...],
) -> ResonanceEvidence:
return ResonanceEvidence(
case_id=case_id,
aligned_score=mean_pair_score(manifold, aligned_pairs),
contrast_score=mean_pair_score(manifold, contrast_pairs),
)