/- BindingSite.BindingSiteHachimoji — Amino acid encoding and Fisher metric. Defines the computable core of the binding site pipeline and the noncomputable Fisher metric used in the §5 theorems of BindingSiteEntropy.lean. Chentsov uniqueness (ChentsovFinite.lean) is quarantined — it has 15+ sorries and is a deep geometry result. This file provides the amino acid / state machinery WITHOUT depending on the Chentsov proof. -/ import BindingSite.BindingSiteTypes import Mathlib.Data.Real.Basic import Mathlib.Data.Fin.Basic namespace BindingSite open SilverSight.FixedPoint -- ═══════════════════════════════════════════════════════════════════════════ -- §1 Amino acid probability distribution (for math §5 only) -- ═══════════════════════════════════════════════════════════════════════════ /-- A probability distribution over 50 atom types (Void-X parameterization). Used only in noncomputable Fisher metric theorems; not in the compute path. -/ structure AminoAcidDistribution where val : Fin 50 → ℝ pos : ∀ i, 0 < val i sum_eq : ∑ i, val i = 1 -- ═══════════════════════════════════════════════════════════════════════════ -- §2 Fisher metric (noncomputable — appears only in math section) -- ═══════════════════════════════════════════════════════════════════════════ /-- Fisher information metric on Δ⁴⁹ (50-dimensional probability simplex). Diagonal form: g_ij(p) = δ_ij / p_i. Uniqueness up to positive scalar: Chentsov's theorem (quarantined). -/ noncomputable def fisherMetric50 (p : AminoAcidDistribution) (i j : Fin 50) : ℝ := if i = j then 1 / p.val i else 0 -- ═══════════════════════════════════════════════════════════════════════════ -- §3 Dominant state extractor -- ═══════════════════════════════════════════════════════════════════════════ /-- Return the most common BindingSiteState in a list of classified residues. Tie-breaking: earlier states win. -/ def dominantState (residues : List BindingSiteResidue) : BindingSiteState := let counts : List (BindingSiteState × Nat) := [ (.Phi, residues.countP (·.state == .Phi)) , (.Lambda, residues.countP (·.state == .Lambda)) , (.Rho, residues.countP (·.state == .Rho)) , (.Kappa, residues.countP (·.state == .Kappa)) , (.Omega, residues.countP (·.state == .Omega)) , (.Sigma, residues.countP (·.state == .Sigma)) , (.Pi, residues.countP (·.state == .Pi)) , (.Zeta, residues.countP (·.state == .Zeta)) ] (counts.foldl (fun (best : BindingSiteState × Nat) pair => if pair.2 > best.2 then pair else best) (.Zeta, 0)).1 -- ═══════════════════════════════════════════════════════════════════════════ -- §4 Profile builder -- ═══════════════════════════════════════════════════════════════════════════ /-- Build a BindingSiteProfile from a list of classified residues. All arithmetic is Q16_16; no Float. -/ def buildProfile (classified : List (AminoAcidToken × Q16_16 × BindingSiteState)) : BindingSiteProfile := let entropies := classified.map (·.2.1) let dominant := dominantState (classified.map fun (t, e, s) => { token := t, entropy := e, state := s , position := (Q16_16.zero, Q16_16.zero, Q16_16.zero) , bindability := Q16_16.ofNat 50 }) { residues := classified.map fun (t, e, s) => { token := t, entropy := e, state := s , position := (Q16_16.zero, Q16_16.zero, Q16_16.zero) , bindability := Q16_16.ofNat 50 } , totalEntropy := q16Mean entropies , maxEntropy := q16Max entropies , minEntropy := q16Min entropies , siteState := dominant , druggable := dominant == .Pi || dominant == .Lambda , receiptHash := "PENDING" } end BindingSite