SilverSight/formal/BindingSite/BindingSiteEntropy.lean
allaun 1794299a6c chore(quality): native_decide migration, docs, and phi pipeline cleanup
Systematic native_decide → dec_trivial/rfl migration across all Lean modules
to comply with AGENTS.md rule 5 (no native_decide unless only option):
- CoreFormalism: BraidEigensolid, BraidField, ChentsovFinite, HachimojiBase,
  HachimojiBridging, HachimojiCodec, HachimojiLUT, HachimojiManifoldAxiom,
  Q16_16Numerics
- BindingSite: BindingSiteCodec, BindingSiteEntropy, BindingSiteHachimoji
- SilverSight: ProductSchema, ProductWireFormat, PolyFactorIdentity, Schema, WireFormat
- PVGS_DQ_Bridge: all three files (native_decide->dec_trivial)
- UniversalEncoding/ChiralitySpace

Additional changes:
- gemma4_mcp.py: upgraded to two-tier routing (local Gemma4 + FreeLLMAPI proxy)
- ChentsovFinite: added traceability map and Chentsov (1972) citation
- HachimojiBase: renamed Σ→Sig, Π→Pi to avoid non-ASCII issues
- Import path fixes for Mathlib 4.30.0-rc2 compatibility
- Doc updates: PURE_FORMULAS, SOS_CERTIFICATE, fundamental math derivations
- Build log: 2026-06-26 session findings
- BRKGLASS_NR_BRACKET_PROPOSAL: updated to REAL-DATA VALIDATED status
- New docs: FOUNDATIONAL_GUIDANCE, PURE_EQUATION_MAP, CHENTSOV_FINITE_MATH,
  BREAKGLASS_FUSION_REVIEW_SPEC, COLD_REVIEWER_FORMULA
- New python: phi pipeline (equation_dna_encoder, ast_parse, charclass,
  consistency, embed, output), nr_bracket_validation with receipt

Build: lake build SilverSightRRC — passes on all committed modules.
  Excluded: HachimojiN8Bridge, HachimojiCharClass (missing
  CoreFormalism.HachimojiManifoldAxiom olean — WIP)
2026-06-27 01:56:54 -05:00

227 lines
14 KiB
Text
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/-
BindingSite.BindingSiteEntropy — Information entropy for protein binding sites.
§1-§4: computable, Q16_16, no Float, no Real.
§5: noncomputable theorems using Real — legitimate math, not IO compute path.
fisher_implies_similar_druggability is BLOCKED on entropy_lipschitz axiom
(research-level Pinsker-type inequality; see inline documentation).
References:
- Yang, Yuan, Chou 2025 (Void-X): Eq. 3 (information entropy)
- Giani, Win, Conti 2025: quantum discrimination via PVGS
-/
import BindingSite.BindingSiteHachimoji
import Mathlib.Data.Real.Basic
import Mathlib.Topology.Basic
import Mathlib.Analysis.SpecialFunctions.Log.Basic
import Mathlib.Analysis.Complex.ExponentialBounds
namespace BindingSite
open SilverSight.FixedPoint
-- ═══════════════════════════════════════════════════════════════════════════
-- §1 Information entropy over a probability distribution (noncomputable/math)
-- ═══════════════════════════════════════════════════════════════════════════
/-- Shannon entropy of a probability distribution over 50 atom types.
Void-X Eq. 3: S_i = -∑_j p(a_j|context) log p(a_j|context).
Noncomputable: uses and Real.log; for math section only. -/
noncomputable def siteEntropy (p : AminoAcidDistribution) : :=
-∑ i : Fin 50, if p.val i > 0 then p.val i * Real.log (p.val i) else 0
/-- Maximum possible entropy for 50 states (uniform distribution).
S_max = log(50) ≈ 3.912. -/
noncomputable def maxEntropy50 : := Real.log 50
/-- Normalized entropy: S* = S / S_max ∈ [0, 1]. -/
noncomputable def normalizedEntropy (p : AminoAcidDistribution) : :=
siteEntropy p / maxEntropy50
-- ═══════════════════════════════════════════════════════════════════════════
-- §2 B-factor → entropy (computable, Q16_16)
-- ═══════════════════════════════════════════════════════════════════════════
-- Re-exported from BindingSiteTypes; provided here for namespace convenience.
/-- Compute average entropy for a residue from its B-factor and neighbors.
Uses the linear proxy entropy = clamp(avg_bFactor, 0, 100) / 100.
Monotone, deterministic, Q16_16. -/
def siteEntropyQ (bFactor : Nat) (neighborBFactors : List Nat) : Q16_16 :=
entropyFromBFactor bFactor neighborBFactors
/-- Entropy from sequence cluster membership.
Higher cluster diversity → higher entropy; higher conservation → lower entropy.
Q16_16 arithmetic: diversity in [0,1], conservation in [0,1]. -/
def entropyFromCluster (clusterSize : Nat) (sequenceIdentityQ : Q16_16) : Q16_16 :=
-- diversity = clusterSize / maxClusterSize, clamp to [0,1]
let maxCluster : Nat := 10000
let diversityQ := Q16_16.ofRawInt ((min clusterSize maxCluster : Int) * 65536 / (maxCluster : Int))
-- conservation term: (1 - sequenceIdentity)
let oneMinusConserv := Q16_16.sub Q16_16.one sequenceIdentityQ
Q16_16.mul diversityQ oneMinusConserv
-- ═══════════════════════════════════════════════════════════════════════════
-- §3 Binding site entropy profile (computable, Q16_16)
-- ═══════════════════════════════════════════════════════════════════════════
/-- Compute the full entropy profile of a binding site from raw PDB residue data.
Input: (residue_type, modification, b_factor_nat, neighbor_b_factors_nat)
Output: (AminoAcidToken × Q16_16 × BindingSiteState) list -/
def bindingSiteEntropyProfile
(residues : List (String × String × Nat × List Nat))
: List (AminoAcidToken × Q16_16 × BindingSiteState) :=
residues.map fun (resType, mod, bFactor, neighborBFs) =>
let token := residueToToken resType mod
let entropy := entropyFromBFactor bFactor neighborBFs
let state := entropyToHachimoji entropy false (mod == "ZN" || mod == "CA")
(token, entropy, state)
/-- Average entropy of a classified site profile (Q16_16). -/
def averageSiteEntropyQ (profile : List (AminoAcidToken × Q16_16 × BindingSiteState)) : Q16_16 :=
q16Mean (profile.map (·.2.1))
/-- Bindability score B* ∈ [0, 100] in Q16_16.
B* = 100 × (1 - (avgEntropy - globalMin) / (globalMax - globalMin)).
High B* ↔ low entropy relative to protein surface ↔ ordered pocket. -/
def bindabilityScore
(profile : List (AminoAcidToken × Q16_16 × BindingSiteState))
(globalMin globalMax : Q16_16)
: Q16_16 :=
let avg := averageSiteEntropyQ profile
let range := Q16_16.sub globalMax globalMin
if range.val ≤ 0 then Q16_16.ofNat 50 -- degenerate: return mid-score
else
let normalized := Q16_16.div (Q16_16.sub avg globalMin) range
Q16_16.mul (Q16_16.ofNat 100) (Q16_16.sub Q16_16.one normalized)
-- ═══════════════════════════════════════════════════════════════════════════
-- §4 Sidon address from entropy profile (computable, Q16_16/Nat)
-- ═══════════════════════════════════════════════════════════════════════════
/-- Map a BindingSiteState to its Sidon index ∈ {0,…,7}. -/
def stateToSidonIdx : BindingSiteState → Nat
| .Phi => 0 | .Lambda => 1 | .Rho => 2 | .Kappa => 3
| .Omega => 4 | .Sigma => 5 | .Pi => 6 | .Zeta => 7
/-- Compute the Sidon address of a binding site from its entropy profile.
The 8 dominant entropy values map to Sidon powers {2⁰,…,2⁷},
weighted by entropy magnitude (clamped to [0,16]). -/
def entropyToSidonAddress
(profile : List (AminoAcidToken × Q16_16 × BindingSiteState))
: List Nat :=
profile.filterMap fun (_, entropy, state) =>
let idx := stateToSidonIdx state
-- entropy.val ∈ [0, 65536]; scale to [0, 16]
let scale := (entropy.val * 16 / 65536).toNat
some (Nat.pow 2 idx * scale)
-- ═══════════════════════════════════════════════════════════════════════════
-- §5 Fisher metric on binding site manifold (noncomputable math)
-- ═══════════════════════════════════════════════════════════════════════════
/-- Approximate Fisher-Rao distance via Bhattacharyya coefficient.
d_FR(p,q) ≈ sqrt(2 * log(1 / Σ_i sqrt(p_i * q_i))).
Noncomputable: uses Real arithmetic. -/
noncomputable def fisherRaoApprox (p q : AminoAcidDistribution) : :=
Real.sqrt (2 * Real.log (1 / ∑ i : Fin 50, Real.sqrt (p.val i * q.val i)))
/-- The binding site manifold: probability distributions over residue tokens
with the Fisher metric. Geodesics are evolutionarily optimal paths. -/
structure BindingSiteManifold where
distribution : AminoAcidDistribution
metric : Fin 50 → Fin 50 → := fisherMetric50 distribution
entropy : := siteEntropy distribution
-- ─────────────────────────────────────────────────────────────────────────
-- §5.1 Entropy Lipschitz axiom (research-level; unblocks §5.2)
-- ─────────────────────────────────────────────────────────────────────────
/-- Shannon entropy is Lipschitz w.r.t. Fisher-Rao distance, constant L = sqrt(2·log 50).
Pinsker-type inequality; research-level analytical result.
Informally: nearby distributions on the statistical manifold have nearby entropies. -/
axiom entropy_lipschitz (p q : AminoAcidDistribution) :
|siteEntropy p - siteEntropy q| ≤ Real.sqrt (2 * maxEntropy50) * fisherRaoApprox p q
-- ─────────────────────────────────────────────────────────────────────────
-- §5.2 Classification stability theorem
-- ─────────────────────────────────────────────────────────────────────────
/-- Nearby binding sites (Fisher-Rao distance < 0.1) have compatible druggability.
Uses NORMALIZED entropy N = S/S_max ∈ [0,1] so Q16_16-derived thresholds apply:
T₁ = 39321/65536 ≈ 0.600 (druggable-pocket boundary)
T₂ = 26214/65536 ≈ 0.400 (moderate-entropy floor)
Proof sketch:
1. log(50) > 2 (since exp(2) < 9 < 50)
2. sqrt(2/M) < 1 (since M > 2)
3. |N(p) - N(q)| ≤ sqrt(2/M)·d_FR < 1·0.1 = 0.1
4. threshold gap T₁ - T₂ = 13107/65536 ≈ 0.200 > 0.1
5. If sites straddle T₁, the lower one is still > T₁ - 0.1 > T₂ -/
theorem fisher_implies_similar_druggability (p q : AminoAcidDistribution)
(h : fisherRaoApprox p q < 0.1)
: let s1 := if normalizedEntropy p ≥ 39321 / 65536 then true else false
let s2 := if normalizedEntropy q ≥ 39321 / 65536 then true else false
s1 = s2 (normalizedEntropy p > 26214 / 65536 ∧ normalizedEntropy q > 26214 / 65536) := by
-- 1. log(50) > 2 ← exp(2) < 9 < 50
have hM2 : (2 : ) < maxEntropy50 := by
show (2 : ) < Real.log 50
have h1 : Real.exp 1 < 3 := Real.exp_one_lt_three
have h2 : Real.exp 2 = Real.exp 1 * Real.exp 1 := by
rw [show (2 : ) = 1 + 1 from by norm_num, Real.exp_add]
have hexp2 : Real.exp 2 < 50 := by nlinarith [Real.exp_pos (1 : )]
calc (2 : ) = Real.log (Real.exp 2) := (Real.log_exp 2).symm
_ < Real.log 50 := Real.log_lt_log (Real.exp_pos 2) hexp2
have hM : (0 : ) < maxEntropy50 := by linarith
-- 2. sqrt(2·M) ≤ M ← 2·M ≤ M² ← M ≥ 2
have hsqrt_le : Real.sqrt (2 * maxEntropy50) ≤ maxEntropy50 := by
calc Real.sqrt (2 * maxEntropy50)
≤ Real.sqrt (maxEntropy50 ^ 2) := Real.sqrt_le_sqrt (by nlinarith)
_ = maxEntropy50 := Real.sqrt_sq hM.le
-- 3. |N(p) - N(q)| < 1/10
have hNL := entropy_lipschitz p q
have hbound : |siteEntropy p - siteEntropy q| < 1 / 10 * maxEntropy50 :=
calc |siteEntropy p - siteEntropy q|
≤ Real.sqrt (2 * maxEntropy50) * fisherRaoApprox p q := hNL
_ ≤ maxEntropy50 * fisherRaoApprox p q :=
mul_le_mul_of_nonneg_right hsqrt_le (Real.sqrt_nonneg _)
_ < maxEntropy50 * (1 / 10) := mul_lt_mul_of_pos_left (by linarith) hM
_ = 1 / 10 * maxEntropy50 := by ring
have hNdiff : |normalizedEntropy p - normalizedEntropy q| < 1 / 10 := by
have heq : normalizedEntropy p - normalizedEntropy q =
(siteEntropy p - siteEntropy q) / maxEntropy50 := by
simp [normalizedEntropy, sub_div]
-- |N|·M = |S| (by dividing), then nlinarith from |S| < (1/10)·M
have hmul : |normalizedEntropy p - normalizedEntropy q| * maxEntropy50 =
|siteEntropy p - siteEntropy q| := by
rw [heq, abs_div, abs_of_pos hM]; field_simp [hM.ne']
nlinarith [abs_nonneg (normalizedEntropy p - normalizedEntropy q)]
-- 4. Case analysis
show (if normalizedEntropy p ≥ 39321 / 65536 then true else false) =
(if normalizedEntropy q ≥ 39321 / 65536 then true else false)
(normalizedEntropy p > 26214 / 65536 ∧ normalizedEntropy q > 26214 / 65536)
split_ifs with h1 h2
· left; rfl
· -- p ≥ T₁, q < T₁ → N(q) > N(p) - 0.1 ≥ T₁ - 0.1 > T₂
right
have h2' : normalizedEntropy q < 39321 / 65536 := not_le.mp h2
have hnn : (0 : ) ≤ normalizedEntropy p - normalizedEntropy q := by linarith
have hd : normalizedEntropy p - normalizedEntropy q < 1 / 10 := by
rwa [abs_of_nonneg hnn] at hNdiff
constructor
· linarith [show (39321 : ) / 65536 > 26214 / 65536 from by norm_num]
· linarith [show (39321 : ) / 65536 - 1 / 10 > 26214 / 65536 from by norm_num]
· -- p < T₁, q ≥ T₁ → N(p) > N(q) - 0.1 ≥ T₁ - 0.1 > T₂
right
have h1' : normalizedEntropy p < 39321 / 65536 := not_le.mp h1
have hneg : normalizedEntropy p - normalizedEntropy q ≤ 0 := by linarith
have habsform : |normalizedEntropy p - normalizedEntropy q| =
normalizedEntropy q - normalizedEntropy p := by
rw [abs_of_nonpos hneg]; ring
have hd : normalizedEntropy q - normalizedEntropy p < 1 / 10 := habsform ▸ hNdiff
constructor
· linarith [show (39321 : ) / 65536 - 1 / 10 > 26214 / 65536 from by norm_num]
· linarith [show (39321 : ) / 65536 > 26214 / 65536 from by norm_num]
· left; rfl
end BindingSite