64 lines
1.8 KiB
Python
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),
|
|
)
|