Research-Stack/5-Applications/scripts/v4_cotranslational_simulator.py

414 lines
13 KiB
Python
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.

#!/usr/bin/env python3
"""
V4 Minimal Cotranslational Simulator
Implements the minimal V4 simulator with:
- Exposed prefix (Eₜ)
- Pause field (P(t))
- Contact probability term (Πᵢⱼ(t))
- Transient bias field (Bₖ(t))
Based on V4 Full Cotranslational Codon-Peptide Equation Set
"""
import numpy as np
from typing import List, Tuple, Dict, Optional
from dataclasses import dataclass
from enum import Enum
class ExpertType(Enum):
HELIX = "helix"
SHEET = "sheet"
LOOP = "loop"
@dataclass
class Codon:
"""Represents a codon with its properties"""
index: int
nucleotides: str
amino_acid: str
translation_speed: float # v(c)
folding_delay: float # τ(c) = 1/v(c)
structural_bias: Dict[ExpertType, float] # bₖ(c)
bias_lifetime: float # τ_b(c)
@dataclass
class Residue:
"""Represents a single amino acid residue"""
index: int
amino_acid: str
position_3d: np.ndarray # 3D coordinates
translation_time: float # Tₘ
@dataclass
class Expert:
"""Represents an MoE expert"""
type: ExpertType
logit: float # zₖ
advice: np.ndarray # Advice vector
class V4CotranslationalSimulator:
"""
Minimal V4 Cotranslational Simulator
Implements:
- Time-indexed translation state (Tₘ, m(t), Sₜ)
- Ribosome pausing field (p(cᵢ), P(t))
- Nascent-chain exposure window (Eₜ)
- Contact formation kinetics (Πᵢⱼ(t), χᵢⱼ(t))
- Transient codon bias (Bₖ(t))
"""
def __init__(
self,
sequence: List[Codon],
exposed_length: int = 10, # L_exp
alpha_p: float = 1.0, # pause speed coefficient
beta_p: float = 0.1, # pause rarity coefficient
kappa_p: float = 1.0, # pause accessibility coefficient
kappa_e: float = 1.0, # exposure accessibility coefficient
sigma_d: float = 1.0, # spatial scale
sigma_g: float = 1.0, # geometric scale
eta: float = 0.1, # energy sensitivity
):
self.sequence = sequence
self.exposed_length = exposed_length
# Parameters
self.alpha_p = alpha_p
self.beta_p = beta_p
self.kappa_p = kappa_p
self.kappa_e = kappa_e
self.sigma_d = sigma_d
self.sigma_g = sigma_g
self.eta = eta
# State
self.time = 0.0
self.translated_residues: List[Residue] = []
self.expert_logits: Dict[ExpertType, float] = {
ExpertType.HELIX: 0.0,
ExpertType.SHEET: 0.0,
ExpertType.LOOP: 0.0
}
# Precompute cumulative translation times
self.cumulative_times = self._compute_cumulative_times()
def _compute_cumulative_times(self) -> List[float]:
"""Compute Tₘ = Σ_{i=1}^{m} 1/v(cᵢ)"""
times = []
total = 0.0
for codon in self.sequence:
total += codon.folding_delay # τ(c) = 1/v(c)
times.append(total)
return times
def pause_intensity(self, codon: Codon) -> float:
"""Compute pause intensity p(cᵢ) = αₚ/v(cᵢ) + βₚ·σ_rare(cᵢ)"""
# For simplicity, assume σ_rare = 0 for now
return self.alpha_p / codon.translation_speed
def pause_field(self, t: float) -> float:
"""Compute local pause field P(t) = p(c_{m(t)})"""
m = self.visible_prefix_length(t)
if m == 0:
return 0.0
codon = self.sequence[m - 1]
return self.pause_intensity(codon)
def visible_prefix_length(self, t: float) -> int:
"""Compute m(t) = max{m : Tₘ ≤ t}"""
m = 0
for i, T in enumerate(self.cumulative_times):
if T <= t:
m = i + 1
else:
break
return m
def exposed_segment(self, t: float) -> List[Residue]:
"""Compute exposed segment Eₜ = (a_{m(t)-L_exp+1}, ..., a_{m(t)})"""
m = self.visible_prefix_length(t)
start = max(0, m - self.exposed_length)
return self.translated_residues[start:m]
def kinetic_accessibility(self, i: int, j: int, t: float) -> float:
"""Compute χᵢⱼ(t) = (1 - e^{-κₚ P(t)}) (1 - e^{-κₑ τ_exp(i,j,t)})"""
P = self.pause_field(t)
tau_exp = self.exposure_time(i, j, t)
pause_factor = 1 - np.exp(-self.kappa_p * P)
exposure_factor = 1 - np.exp(-self.kappa_e * tau_exp)
return pause_factor * exposure_factor
def exposure_time(self, i: int, j: int, t: float) -> float:
"""Compute time both residues have been exposed together"""
# Simplified: assume exposure time is proportional to time since translation
if i >= len(self.translated_residues) or j >= len(self.translated_residues):
return 0.0
T_i = self.cumulative_times[i]
T_j = self.cumulative_times[j]
T_max = max(T_i, T_j)
return max(0.0, t - T_max)
def contact_probability(self, i: int, j: int, t: float) -> float:
"""Compute Πᵢⱼ(t) = 1_{i,j∈Eₜ}·exp(-dᵢⱼ²/2σ_d²)·exp(-Δᵢⱼ_geom²/2σ_g²)·χᵢⱼ(t)"""
E = self.exposed_segment(t)
exposed_indices = {r.index for r in E}
if i not in exposed_indices or j not in exposed_indices:
return 0.0
if i >= len(self.translated_residues) or j >= len(self.translated_residues):
return 0.0
# Spatial separation
d_ij = np.linalg.norm(
self.translated_residues[i].position_3d -
self.translated_residues[j].position_3d
)
# Geometric compatibility (simplified as 1 for now)
Delta_ij_geom = 1.0
# Kinetic accessibility
chi_ij = self.kinetic_accessibility(i, j, t)
spatial_factor = np.exp(-d_ij**2 / (2 * self.sigma_d**2))
geometric_factor = np.exp(-Delta_ij_geom**2 / (2 * self.sigma_g**2))
return spatial_factor * geometric_factor * chi_ij
def transient_bias(self, expert_type: ExpertType, t: float, window_size: int = 5) -> float:
"""Compute Bₖ(t) = Σ_{i∈Wₜ} bₖ(cᵢ)·exp(-(t-Tᵢ)/τ_b(cᵢ))"""
m = self.visible_prefix_length(t)
W = list(range(max(0, m - window_size), m))
B = 0.0
for i in W:
codon = self.sequence[i]
T_i = self.cumulative_times[i]
tau_b = codon.bias_lifetime
bias = codon.structural_bias.get(expert_type, 0.0)
decay = np.exp(-(t - T_i) / tau_b)
B += bias * decay
return B
def expert_weights(self, t: float) -> Dict[ExpertType, float]:
"""Compute gₖ(t) ∝ exp(zₖ(t) + Bₖ(t) + Dₖ(t) - ηEₖ(Θₜ))"""
weights = {}
for expert_type in ExpertType:
z = self.expert_logits[expert_type]
B = self.transient_bias(expert_type, t)
D = self.delay_bias(t, expert_type)
E = self.expert_energy_incompatibility(expert_type, t)
logit = z + B + D - self.eta * E
weights[expert_type] = np.exp(logit)
# Normalize
total = sum(weights.values())
if total > 0:
for expert_type in weights:
weights[expert_type] /= total
return weights
def delay_bias(self, t: float, expert_type: ExpertType) -> float:
"""Compute Dₖ(t) based on pause field"""
P = self.pause_field(t)
# Simplified: different experts respond differently to pause
if expert_type == ExpertType.HELIX:
return -0.5 * P # helix formation decreases with pause
elif expert_type == ExpertType.SHEET:
return -0.3 * P # sheet formation decreases with pause
else: # LOOP
return 0.8 * P # loop formation increases with pause
def expert_energy_incompatibility(self, expert_type: ExpertType, t: float) -> float:
"""Compute Eₖ(Θₜ) - simplified as 0 for minimal simulator"""
return 0.0
def step(self, dt: float = 0.1):
"""Advance simulation by dt"""
self.time += dt
# Translate new residues
m = self.visible_prefix_length(self.time)
while len(self.translated_residues) < m:
i = len(self.translated_residues)
codon = self.sequence[i]
# Create new residue with random 3D position
position = np.random.randn(3) # Simplified
residue = Residue(
index=i,
amino_acid=codon.amino_acid,
position_3d=position,
translation_time=self.cumulative_times[i]
)
self.translated_residues.append(residue)
# Update expert logits based on RL (simplified)
weights = self.expert_weights(self.time)
for expert_type in ExpertType:
# Simple RL update: increase logit for high-weight experts
if weights[expert_type] > 0.5:
self.expert_logits[expert_type] += 0.01 * dt
def get_contact_average(self, t: float) -> float:
"""Compute average contact probability Π̄(t)"""
E = self.exposed_segment(t)
n = len(E)
if n < 2:
return 0.0
total = 0.0
count = 0
for i in range(n):
for j in range(i + 1, n):
total += self.contact_probability(E[i].index, E[j].index, t)
count += 1
if count == 0:
return 0.0
return total / count
def get_cds_score(self, t: float, alpha: float = 0.5, beta: float = 0.3, chi: float = 0.2) -> float:
"""Compute Φ_CDS(v4)(t) = α·Φ̄_codon + β·Φ_peptide(Θₜ,t) + χ·Π̄(t)"""
m = self.visible_prefix_length(t)
# Average codon fitness (simplified as 1.0)
Phi_codon_avg = 1.0
# Peptide efficiency (simplified as 1.0 / (1 + pause))
P = self.pause_field(t)
Phi_peptide = 1.0 / (1.0 + P)
# Average contact probability
Pi_avg = self.get_contact_average(t)
return alpha * Phi_codon_avg + beta * Phi_peptide + chi * Pi_avg
def create_test_sequence() -> List[Codon]:
"""Create a test sequence for simulation"""
amino_acids = ["A", "R", "N", "D", "C", "Q", "E", "G", "H", "I"]
sequence = []
for i in range(20):
aa = amino_acids[i % len(amino_acids)]
# Vary translation speed to test pause effects
if i % 3 == 0:
speed = 0.5 # Slow codon
elif i % 3 == 1:
speed = 1.0 # Medium codon
else:
speed = 2.0 # Fast codon
codon = Codon(
index=i,
nucleotides=f"ABC", # Simplified
amino_acid=aa,
translation_speed=speed,
folding_delay=1.0 / speed,
structural_bias={
ExpertType.HELIX: np.random.randn() * 0.1,
ExpertType.SHEET: np.random.randn() * 0.1,
ExpertType.LOOP: np.random.randn() * 0.1
},
bias_lifetime=1.0
)
sequence.append(codon)
return sequence
def run_simulation():
"""Run the V4 simulation"""
print("=" * 70)
print("V4 COTRANSLATIONAL SIMULATOR")
print("=" * 70)
# Create test sequence
sequence = create_test_sequence()
print(f"\nCreated test sequence with {len(sequence)} codons")
# Initialize simulator
sim = V4CotranslationalSimulator(sequence, exposed_length=5)
# Run simulation
print("\nRunning simulation...")
print(f"{'Time':>8} | {'Translated':>10} | {'Exposed':>7} | {'Pause':>6} | {'Contact':>7} | {'Score':>6}")
print("-" * 70)
t = 0.0
dt = 0.5
max_time = 10.0
results = []
while t < max_time:
sim.step(dt)
m = sim.visible_prefix_length(t)
E = sim.exposed_segment(t)
P = sim.pause_field(t)
Pi = sim.get_contact_average(t)
score = sim.get_cds_score(t)
print(f"{t:8.2f} | {m:10d} | {len(E):7d} | {P:6.3f} | {Pi:7.3f} | {score:6.3f}")
results.append({
"time": t,
"translated": m,
"exposed": len(E),
"pause": P,
"contact": Pi,
"score": score
})
t += dt
print("\n" + "=" * 70)
print("SIMULATION COMPLETE")
print("=" * 70)
# Summary statistics
final_score = results[-1]["score"]
avg_pause = np.mean([r["pause"] for r in results])
avg_contact = np.mean([r["contact"] for r in results])
print(f"\nFinal CDS Score: {final_score:.4f}")
print(f"Average Pause Field: {avg_pause:.4f}")
print(f"Average Contact Probability: {avg_contact:.4f}")
print("\nKey V4 Features Demonstrated:")
print("- Time-indexed translation: residues become available at different times")
print("- Pause field: slow codons create stronger pauses")
print("- Exposure window: only tail of chain is structurally active")
print("- Contact kinetics: pause affects contact formation probability")
print("- Transient bias: codon bias decays over time")
return results
if __name__ == "__main__":
results = run_simulation()