Research-Stack/binding-site/BindingSiteCodec.lean
Allaun Silverfox 35ad40cfae binding-site: 3 outlines, 1,300 lines, Hachimoji × protein structures
BindingSiteHachimoji.lean (589 lines):
  - 50-token amino acid vocabulary (20 core + 30 modified)
  - 8-state Hachimoji classification for residues
  - Extended Fisher metric on 50-simplex (Chentsov unique)
  - Chaos game basin discovery with Sidon addressing
  - Typed BindingSiteReceipt compatible with PVGS-DQ

BindingSiteEntropy.lean (376 lines):
  - Information entropy per residue site (Void-X Eq. 3)
  - Entropy from B-factors (PDB) or cluster diversity
  - Bindability score B* normalized to 0-100
  - Sidon address generation from entropy profiles
  - Fisher-Rao approximate distance between sites

BindingSiteCodec.lean (335 lines):
  - pdbToReceipt : PDB ID → BindingSiteReceipt (one function)
  - screenDruggable : filter library for bindable pockets
  - rankByEnergy : sort by dual quaternion energy
  - Test cases: EGFR (1YY9), Tautomerase (9MUA), KIR2DL1 (9HML)

Architecture: PDB → entropy profile → Hachimoji state → PVGS params
  → dual quaternion energy → typed receipt → PVGS-DQ system

All three files compile as outlines with sorry for future proofs.
2026-06-21 04:16:34 -05:00

201 lines
8.5 KiB
Text
Raw Permalink 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.

/-
BindingSiteCodec.lean — Deterministic Pipeline: PDB → Binding Site Receipt
The protein-structure analog of HachimojiCodec.lean.
Takes a PDB identifier, fetches the structure (or uses local file),
computes the entropy profile, classifies residues via the 8-state
Hachimoji system, and emits a typed receipt compatible with the
PVGS-DQ receipt system.
This is NOT a machine learning model. It is a library function
that deterministically maps protein structure → classification.
The ML (Void-X) is only needed for the entropy computation step;
everything else is deterministic geometry.
Pipeline:
PDB ID → fetch structure → extract residues → compute entropy
→ classify via Hachimoji → Sidon address → PVGS params
→ DQ energy → receipt
-/}
import Mathlib
import BindingSiteHachimoji
import BindingSiteEntropy
import pvgs.PVGS_DQ_Bridge_fixed
namespace BindingSiteCodec
open BindingSiteHachimoji
open BindingSiteEntropy
open Semantics.PVGS_DQ_Bridge
-- =================================================================
-- §1. PDB DATA INTERFACE (placeholders for RCSB API)
-- =================================================================
/-- Fetch a PDB structure from the RCSB database.
In production, this calls the RCSB REST API:
https://data.rcsb.org/rest/v1/core/entry/{pdbId}
For now, placeholder that returns empty data. -/
def fetchPDB (pdbId : String) : IO (List (String × String × × List )) := do
-- In production:
-- 1. Download mmCIF from https://files.wwpdb.org/pub/pdb/data/structures/
-- 2. Parse with Bio.PDB or similar
-- 3. Extract: (residue_type, modification, b_factor, neighbor_b_factors)
-- 4. Return list ordered by residue sequence number
IO.println s!"[BindingSiteCodec] Fetching PDB {pdbId}..."
-- Placeholder: return empty (caller must handle)
pure []
/-- Fetch sequence cluster membership from RCSB.
https://cdn.rcsb.org/resources/sequence/clusters/clusters-by-entity-40.txt
Tells us which proteins are structurally similar (same cluster). -/
def fetchClusterMembership (pdbId : String) : IO (Option ( × )) := do
-- Returns (entity_id, cluster_id) or none if not found
IO.println s!"[BindingSiteCodec] Fetching cluster for {pdbId}..."
pure none
-- =================================================================
-- §2. THE PIPELINE (deterministic, no ML except entropy step)
-- =================================================================
/-- Step 1: Extract residue data from PDB structure. -/
def extractResidues (pdbData : List (String × String × × List ))
: List (AminoAcidToken × × BindingSiteState) :=
pdbData.map (λ (residueType, mod, bFactor, neighborBFs) =>
let token := residueToToken residueType mod
let entropy := entropyFromBFactor bFactor neighborBFs
let state := entropyToHachimoji entropy false true
(token, entropy, state)
)
/-- Step 2: Build the binding site profile. -/
def buildProfile (residues : List (AminoAcidToken × × BindingSiteState))
: BindingSiteProfile :=
let entropies := residues.map (λ (_, e, _) => e)
let states := residues.map (λ (_, _, s) => s)
let dominant := states.headD .Ζ
{ residues := residues.map (λ (t, e, s) =>
{ token := t, entropy := e, state := s
, position := (0, 0, 0) -- placeholder: actual coords from PDB
, bindability := 50.0 })
, totalEntropy := match entropies with | [] => 0 | es => List.sum es / es.length
, maxEntropy := match entropies with | [] => 0 | es => es.maximumD 0
, minEntropy := match entropies with | [] => 0 | es => es.minimumD 0
, siteState := dominant
, druggable := dominant = .Π dominant = .Λ
, receiptHash := "PENDING" }
/-- Step 3: Build PVGS parameters from the profile.
The stellar rank k = number of distinct Hachimoji states present.
The displacement μ = average entropy (real part), entropy variance (imag).
The squeezing ζ = 0 (no squeezing in protein context, placeholder).
The sign t = +1 if druggable, -1 otherwise. -/
def profileToPVGS (profile : BindingSiteProfile) : PVGSParams :=
let distinctStates := profile.residues.map (λ r => r.state) |>.eraseDups |>.length
let avgEntropy := profile.totalEntropy
let varEntropy := 0.0 -- placeholder: compute variance
{ φ := Q16_16.zero
, μ_re := Q16_16.ofFloat avgEntropy.toFloat
, μ_im := Q16_16.ofFloat varEntropy.toFloat
, ζ_mag := Q16_16.zero
, ζ_angle := Q16_16.zero
, k := distinctStates
, t := if profile.druggable then 1 else -1 }
/-- Step 4: Emit the receipt. -/
def emitReceipt (pdbId : String) (profile : BindingSiteProfile)
: BindingSiteReceipt :=
let pvgs := profileToPVGS profile
let dq := pvgsToDQ pvgs
let energy := (dualQuatEnergy dq).toInt
{ version := "BindingSite:v1"
, pdbId := pdbId
, entityId := 0
, clusterId := 0
, profile := profile
, pvgsParams := pvgs
, dqEnergy := energy
, stellarRank := pvgs.k
, helstromBound := 0.0 -- computed from pairwise discrimination
, sha256 := "TBD" }
-- =================================================================
-- §3. THE ONE-FUNCTION API
-- =================================================================
/-- `pdb_to_receipt : PDB ID → BindingSiteReceipt`
The complete pipeline in one call. This is the protein-structure
analog of `equation_to_emit` from HachimojiCodec.lean.
Usage:
let receipt ← pdbToReceipt "1YY9"
IO.println receipt.profile.siteState
-- prints: Π (potential binding site)
The receipt plugs directly into the PVGS-DQ system:
- receipt.dqEnergy links to dual quaternion energy
- receipt.stellarRank links to stellar rank / photon variation count
- receipt.sha256 links to the hash-chained receipt system -/
def pdbToReceipt (pdbId : String) : IO BindingSiteReceipt := do
let pdbData ← fetchPDB pdbId
let classified := extractResidues pdbData
let profile := buildProfile classified
let cluster ← fetchClusterMembership pdbId
let receipt := emitReceipt pdbId profile
-- Update cluster info if available
match cluster with
| some (entity, clusterId) =>
pure { receipt with entityId := entity, clusterId := clusterId }
| none => pure receipt
-- =================================================================
-- §4. BATCH PROCESSING (for screening libraries)
-- =================================================================
/-- Process a list of PDB IDs and return only the druggable ones.
This is the screening workflow: given a library of protein
structures, find which have bindable pockets.
Analog: `equation_to_emit` filtered for ADMIT results. -/
def screenDruggable (pdbIds : List String) : IO (List BindingSiteReceipt) := do
let receipts ← pdbIds.mapM pdbToReceipt
pure (receipts.filter (λ r => r.profile.druggable))
/-- Rank binding sites by DQ energy (lower = more ordered = better pocket).
This uses the dual quaternion energy as a scoring function,
exactly like spectral binning ranks equations by profile energy. -/
def rankByEnergy (receipts : List BindingSiteReceipt) : List BindingSiteReceipt :=
receipts.insertionSort (λ r1 r2 => r1.dqEnergy < r2.dqEnergy)
-- =================================================================
-- §5. TEST CASES (from Void-X paper)
-- =================================================================
/-- Test: EGFR (PDB 1YY9) — the example from Yang et al. 2025 Fig. S8.
Known epitopes: antibody/nanobody binding sites circled in red.
Expected result: siteState = Π (potential), druggable = true. -/
def testEGFR : IO Unit := do
let receipt ← pdbToReceipt "1YY9"
IO.println s!"EGFR: siteState = {receipt.profile.siteState}"
IO.println s!"EGFR: druggable = {receipt.profile.druggable}"
IO.println s!"EGFR: DQ energy = {receipt.dqEnergy}"
IO.println s!"EGFR: stellar rank = {receipt.stellarRank}"
/-- Test: Tautomerase (PDB 9MUA) — from Void-X Fig. S4A.
Generated atoms reconstruct GKL, TV, FL fragments.
Expected: moderate entropy, Λ or Π state. -/
def testTautomerase : IO Unit := do
let receipt ← pdbToReceipt "9MUA"
IO.println s!"Tautomerase: siteState = {receipt.profile.siteState}"
/-- Test: KIR2DL1/nanobody (PDB 9HML) — from Void-X Fig. S9A.
Ground truth entropy: 1.18. AF3 predictions: 2.08-2.66.
Expected: ground truth = Π, AF3 = Ω (collision, unmodelable). -/
def testKIR2DL1 : IO Unit := do
let gt ← pdbToReceipt "9HML"
IO.println s!"KIR2DL1 ground truth: entropy = {gt.profile.totalEntropy}"
IO.println s!"KIR2DL1 ground truth: state = {gt.profile.siteState}"
end BindingSiteCodec