"""Scientific metrics, conservative screening, and sequence-diverse selection.""" from __future__ import annotations import math from collections import defaultdict import numpy as np from .schema import Measurement, Requirement def p_affinity(kd_nm: float) -> float: """Return minus log10 of a dissociation constant in molar units.""" if kd_nm <= 0: raise ValueError("Kd must be positive") return 9.0 - math.log10(kd_nm) def selectivity(kd_target_nm: float, kd_counter_nm: float) -> float: """Log10 selectivity, positive for preferential binding to the target.""" return p_affinity(kd_target_nm) - p_affinity(kd_counter_nm) def normalized_margin(m: Measurement, r: Requirement, conservative: bool = True) -> float: if m.endpoint != r.endpoint or m.unit != r.unit or m.assay != r.assay: raise ValueError("endpoint, unit, or assay mismatch") value = m.value if conservative: bound = m.lower if r.direction == "ge" else m.upper if bound is None: raise ValueError("conservative ranking requires an interval") value = bound return (value - r.threshold) / r.scale if r.direction == "ge" else (r.threshold - value) / r.scale def candidate_score(measurements: list[Measurement], requirements: list[Requirement]) -> float: """Worst margin over the supplied computational ranking requirements.""" values = [] for r in requirements: if not r.required: continue matched = [m for m in measurements if m.endpoint == r.endpoint] if len(matched) != 1: raise ValueError(f"expected one declared estimate for {r.endpoint}") values.append(normalized_margin(matched[0], r)) if not values: raise ValueError("no required endpoints") return min(values) def identity(a: str, b: str) -> float: """One minus normalized Levenshtein distance, including unequal lengths.""" row = list(range(len(b) + 1)) for i, ca in enumerate(a, 1): nxt = [i] for j, cb in enumerate(b, 1): nxt.append(min(nxt[-1] + 1, row[j] + 1, row[j-1] + (ca != cb))) row = nxt return 1.0 - row[-1] / max(len(a), len(b), 1) def diverse_select(sequences: dict[str, str], scores: dict[str, float], k: int, penalty: float = 0.25, max_identity: float = 0.8) -> list[str]: """Greedy quality/diversity selection with an explicit redundancy ceiling.""" if k < 0 or penalty < 0 or not 0 <= max_identity <= 1: raise ValueError("invalid selection settings") chosen = [] remaining = set(scores) & set(sequences) while remaining and len(chosen) < k: utilities = {} for cid in remaining: sim = max((identity(sequences[cid], sequences[c]) for c in chosen), default=0.) if sim <= max_identity and math.isfinite(scores[cid]): utilities[cid] = scores[cid] - penalty * sim if not utilities: break best = min(utilities, key=lambda x: (-utilities[x], x)) chosen.append(best) remaining.remove(best) return chosen def conformal_radius(y_true, y_pred, scales, alpha=0.1) -> float: """Finite-sample split-conformal radius for normalized absolute residuals. Calibration rows must be independent of fitting and final selection. Returns infinity when the requested quantile exceeds the finite calibration sample. """ y, p, s = map(lambda x: np.asarray(x, dtype=float), (y_true, y_pred, scales)) if y.shape != p.shape or y.shape != s.shape or y.ndim != 1 or len(y) == 0: raise ValueError("expected equal nonempty one-dimensional arrays") if not 0 < alpha < 1 or np.any(s <= 0) or not np.all(np.isfinite([y,p,s])): raise ValueError("invalid calibration values") errors = np.abs(y-p)/s rank = math.ceil((len(errors)+1)*(1-alpha)) return float(np.sort(errors)[rank-1]) if rank <= len(errors) else math.inf def grouped_bootstrap(rows: list[dict], value: str, group: str, seed=2027, n=2000) -> dict: """Equal-weight group mean and percentile interval across independent tasks.""" groups = defaultdict(list) for r in rows: groups[r[group]].append(float(r[value])) x = np.array([np.mean(v) for v in groups.values()]) if len(x) < 2: raise ValueError("at least two independent groups are required") rng = np.random.default_rng(seed) draws = x[rng.integers(len(x), size=(n,len(x)))].mean(axis=1) return {"mean": float(x.mean()), "lower": float(np.quantile(draws,.025)), "upper": float(np.quantile(draws,.975)), "groups": len(x)} def joint_success(measurements: list[Measurement], requirements: list[Requirement]) -> bool: """Require every registered endpoint to pass on experimental observations.""" for r in requirements: if not r.required: continue rows = [m for m in measurements if m.endpoint == r.endpoint and m.kind == "experiment"] if len(rows) != 1 or rows[0].censor != "none": return False if normalized_margin(rows[0],r,conservative=False) < 0: return False return True