/- BindingSiteEntropy.lean -- Information Entropy for Protein Binding Sites Computes the information entropy profile of a binding site using the Fisher information metric (guaranteed unique by Chentsov). This is the direct protein-structure analog of the spectral profile pipeline (eigensolid_pipeline.py) for equations. References: - Yang, Yuan, Chou 2025 (Void-X): Eq. 3 (information entropy) - Giani, Win, Conti 2025: quantum discrimination via PVGS - Research-Stack library/ChentsovFinite.lean: metric uniqueness -/} import Mathlib import BindingSiteHachimoji namespace BindingSiteEntropy open BindingSiteHachimoji -- ================================================================= -- S1. INFORMATION ENTROPY PER RESIDUE SITE (Void-X Eq. 3) -- ================================================================= /-- Information entropy of a single residue site, from Void-X Eq. 3: S_i = -SUM_j p(a_j | context) log p(a_j | context) where p(a_j | context) is the conditional probability of atom type a_j given the structural context (neighboring atoms). In Void-X, this is computed from the diffusion model's output distribution over 50 atom types. Here we formalize it as a probability distribution over the Hachimoji state space. -/ def siteEntropy (probDist : Fin 50 -> Real) : Real := -sum i, if probDist i > 0 then probDist i * Real.log (probDist i) else 0 /-- The maximum possible entropy for 50 states (uniform distribution). S_max = log(50) ~ 3.912. -/ def maxEntropy50 : Real := Real.log 50 /-- Normalized entropy: S* = S_i / S_max in [0, 1]. This is what Void-X uses for the bindability score. -/ def normalizedEntropy (probDist : Fin 50 -> Real) : Real := siteEntropy probDist / maxEntropy50 -- ================================================================= -- S2. ENTROPY FROM PDB STRUCTURE -- ================================================================= /-- Extract the amino acid distribution at a residue position from a PDB structure. This reads the B-factors (temperature factors) as a proxy for positional uncertainty, which maps to entropy. High B-factor -> high uncertainty -> high entropy -> Pi or Lambda state Low B-factor -> ordered -> low entropy -> Phi state The B-factor is already in the PDB file -- no ML model needed for the baseline entropy computation. -/ def entropyFromBFactor (bFactor : Real) (neighborBFactors : List Real) : Real := -- Local average B-factor normalizes by neighborhood context let localAvg := (bFactor + List.sum neighborBFactors) / (1 + neighborBFactors.length) -- Map to [0, 1]: higher B-factor = higher entropy Real.log (1 + localAvg) / Real.log (1 + 100) -- Dividing by log(101) since B-factors typically range 0-100 /-- Alternative: entropy from the clusters-by-entity-40 sequence cluster identity. Residues in the same cluster have similar structural contexts and thus similar entropy profiles. -/ def entropyFromCluster (clusterSize : Nat) (sequenceIdentity : Real) : Real := -- High sequence identity within cluster -> low entropy (conserved) -- Large cluster size -> high diversity -> higher entropy let diversity := Real.log (1 + clusterSize) let conservation := sequenceIdentity diversity * (1 - conservation) -- ================================================================= -- S3. BINDING SITE ENTROPY PROFILE -- ================================================================= /-- Compute the full entropy profile of a binding site from a sequence of residue data (PDB-derived or Void-X-generated). This is the protein-structure analog of `spectralProfile` in eigensolid_pipeline.py. -/ def bindingSiteEntropyProfile (residues : List (String x String x Real x List Real)) : List (AminoAcidToken x Real x BindingSiteState) := residues.map (fun (residueType, mod, bFactor, neighborBFs) => let token := residueToToken residueType mod let entropy := entropyFromBFactor bFactor neighborBFs let state := entropyToHachimoji entropy false true (token, entropy, state) ) /-- Average entropy of a binding site (the main metric from Void-X). -/ def averageSiteEntropy (profile : List (AminoAcidToken x Real x BindingSiteState)) : Real := let entropies := profile.map (fun (_, e, _) => e) match entropies with | [] => 0 | es => List.sum es / es.length /-- Bindability score B* from Yang et al. 2025 (Fig. S8): B* = 100 * [1 - (S* - min(S)) / (max(S) - min(S))] High B* = low entropy relative to the protein surface = ordered pocket suitable for ligand binding. -/ def bindabilityScore (profile : List (AminoAcidToken x Real x BindingSiteState)) (globalMin globalMax : Real) : Real := let avg := averageSiteEntropy profile 100 * (1 - (avg - globalMin) / (globalMax - globalMin)) -- ================================================================= -- S4. SIDON ADDRESS FOR BINDING SITE (from existing library) -- ================================================================= /-- A binding site gets a Sidon address from its entropy profile, exactly like an equation gets a Sidon address from its spectral profile (eigensolid_pipeline.py). The 8 dominant entropy values are the "observables" that feed into the 8x8 PIST adjacency matrix, which eigendecomposes to an 8D spectral profile -> Sidon address. -/ def entropyToSidonAddress (profile : List (AminoAcidToken x Real x BindingSiteState)) : List Nat := -- Extract top 8 entropy values (one per Hachimoji state category) let stateEntropies := List.filterMap (fun (_, e, s) => match s with | .Phi => some (0, e) | .Lambda => some (1, e) | .Rho => some (2, e) | .Kappa => some (3, e) | .Omega => some (4, e) | .Sigma => some (5, e) | .Pi => some (6, e) | .Zeta => some (7, e) ) profile -- Map to Sidon powers {1, 2, 4, 8, 16, 32, 64, 128} -- weighted by entropy magnitude stateEntropies.map (fun (idx, e) => Nat.pow 2 idx * (min (Nat.floor (e * 10)) 16) ) -- ================================================================= -- S5. FISHER METRIC ON BINDING SITE MANIFOLD -- ================================================================= /-- The binding site manifold: probability distributions over residue tokens, equipped with the Fisher metric. Geodesics on this manifold are evolutionarily optimal paths. -/ structure BindingSiteManifold where distribution : AminoAcidDistribution metric : Fin 50 -> Fin 50 -> Real := fisherMetric50 distribution entropy : Real := siteEntropy distribution.val /-- Geodesic distance to another binding site manifold. The exact geodesic requires solving the geodesic equation on Delta^49 under the Fisher metric, which has no closed form. We use the Fisher-Rao approximation (Bhattacharyya-based) as the default. TODO: Replace with exact geodesic solver when available. -/ geodesicDistanceTo : BindingSiteManifold -> Real := fun other => fisherRaoApprox self.distribution other.distribution /-- Approximate Fisher-Rao distance between two binding sites using the Bhattacharyya coefficient (efficient approximation). -/ def fisherRaoApprox (p q : AminoAcidDistribution) : Real := Real.sqrt (2 * Real.log (1 / sum i, Real.sqrt (p.val i * q.val i))) /-- Theorem: nearby binding sites (small Fisher distance) have similar druggability profiles. This is what enables transfer learning across protein families. STATUS: PROOF BLOCKED on research-level Pinsker-type inequality. See entropy_lipschitz axiom below for the missing analytical gap. Once that axiom is proven, the proof proceeds by: 1. Lipschitz bound gives |S(p) - S(q)| < 0.28 (from d_FR < 0.1) 2. 0.28 < 0.3 = min threshold gap in entropyToHachimoji 3. Case analysis on thresholds shows same classification. -/ theorem fisher_implies_similar_druggability (p q : AminoAcidDistribution) (sites : List (AminoAcidToken x Real x BindingSiteState)) (h : fisherRaoApprox p q < 0.1) : -- Sites with similar distributions have similar dominant states (dominantState p sites = dominantState q sites) \/ (bothDruggable p q sites) := by sorry -- BLOCKED: needs entropy_lipschitz + numerical bounds -- (see axiom and documentation below) where dominantState := fun d _ => entropyToHachimoji (siteEntropy d.val) false true bothDruggable := fun d1 d2 _ => let s1 := entropyToHachimoji (siteEntropy d1.val) false true let s2 := entropyToHachimoji (siteEntropy d2.val) false true (s1 = .Pi \/ s1 = .Lambda) /\ (s2 = .Pi \/ s2 = .Lambda) -- ================================================================= -- S5.1 MISSING LEMMAS (blocking fisher_implies_similar_druggability) -- ================================================================= -- OPEN PROBLEM: Pinsker-type inequality for Fisher-Rao metric. -- -- The proof of fisher_implies_similar_druggability requires: -- 1. Entropy is Lipschitz w.r.t. Fisher-Rao distance: -- |S(p) - S(q)| <= L * d_FR(p,q) for some L < 4 -- (where L = sqrt(2*log(50)) ~ 2.80 suffices) -- 2. If d_FR(p,q) < 0.1 then |S(p) - S(q)| < 0.28 < 0.3 -- (the minimum threshold gap in entropyToHachimoji) -- 3. Classification stability: |e1 - e2| < 0.3 implies -- entropyToHachimoji e1 = entropyToHachimoji e2 (case analysis) -- -- The Lipschitz bound (1) is a research-level analytical result. -- Once available, (2) is arithmetic and (3) is case analysis. -- Marking as axiom to unblock downstream proofs when established. /-- Entropy is Lipschitz continuous w.r.t. Fisher-Rao distance. CONSTANT: L = sqrt(2*log(50)) ~ 2.80. If d_FR(p,q) < 0.1 then |S(p)-S(q)| < 0.28 < 0.3 (min gap). STATUS: Research-level Pinsker-type inequality. -/ axiom entropy_lipschitz (p q : AminoAcidDistribution) : abs (siteEntropy p.val - siteEntropy q.val) <= Real.sqrt (2 * maxEntropy50) * fisherRaoApprox p q end BindingSiteEntropy