Initial commit

This commit is contained in:
redclawsystems
2026-03-04 00:08:42 +00:00
commit 4d88dc0584
4449 changed files with 1556714 additions and 0 deletions
+220
View File
@@ -0,0 +1,220 @@
//! Evoformer module - attention over sequences and residue pairs.
//!
//! The Evoformer processes sequence and pair representations through
//! alternating attention mechanisms to capture evolutionary relationships.
use crate::encoder::{EMBEDDING_DIM, PairEmbedding, SequenceEmbedding};
/// Number of attention heads.
pub const NUM_HEADS: usize = 8;
/// Evoformer block for processing sequence and pair representations.
#[derive(Debug, Clone)]
pub struct EvoformerBlock {
/// Attention head dimension
head_dim: usize,
/// Number of attention heads
num_heads: usize,
/// Query projection weights
wq: Vec<Vec<f32>>,
/// Key projection weights
wk: Vec<Vec<f32>>,
/// Value projection weights
wv: Vec<Vec<f32>>,
/// Output projection weights
wo: Vec<Vec<f32>>,
}
impl Default for EvoformerBlock {
fn default() -> Self {
Self::new(NUM_HEADS)
}
}
impl EvoformerBlock {
/// Create a new Evoformer block.
#[must_use]
pub fn new(num_heads: usize) -> Self {
use rand::SeedableRng;
use rand_distr::{Distribution, Normal};
let head_dim = EMBEDDING_DIM / num_heads;
let mut rng = rand::rngs::StdRng::seed_from_u64(123);
let normal = Normal::new(0.0_f32, 0.02).unwrap();
let mut init_weights = |rows: usize, cols: usize| -> Vec<Vec<f32>> {
(0..rows)
.map(|_| (0..cols).map(|_| normal.sample(&mut rng)).collect())
.collect()
};
Self {
head_dim,
num_heads,
wq: init_weights(EMBEDDING_DIM, EMBEDDING_DIM),
wk: init_weights(EMBEDDING_DIM, EMBEDDING_DIM),
wv: init_weights(EMBEDDING_DIM, EMBEDDING_DIM),
wo: init_weights(EMBEDDING_DIM, EMBEDDING_DIM),
}
}
/// Apply row-wise (sequence) attention.
pub fn row_attention(&self, seq_emb: &mut SequenceEmbedding, pair_emb: &PairEmbedding) {
let seq_len = seq_emb.sequence_length;
// Compute Q, K, V projections
let queries = self.project(&seq_emb.embeddings, &self.wq);
let keys = self.project(&seq_emb.embeddings, &self.wk);
let values = self.project(&seq_emb.embeddings, &self.wv);
// Multi-head attention
let scale = 1.0 / (self.head_dim as f32).sqrt();
for i in 0..seq_len {
let mut output = vec![0.0_f32; EMBEDDING_DIM];
for head in 0..self.num_heads {
let head_start = head * self.head_dim;
let head_end = head_start + self.head_dim;
// Compute attention scores
let mut scores = Vec::with_capacity(seq_len);
for j in 0..seq_len {
let mut score = 0.0_f32;
for k in head_start..head_end {
score += queries[i][k] * keys[j][k];
}
// Add pair bias from pair representation
if let Some(pair) = pair_emb.get(i, j) {
score += pair.iter().take(self.head_dim).sum::<f32>() * 0.1;
}
scores.push(score * scale);
}
// Softmax
let max_score = scores.iter().copied().fold(f32::NEG_INFINITY, f32::max);
let exp_scores: Vec<f32> = scores.iter().map(|s| (s - max_score).exp()).collect();
let sum_exp: f32 = exp_scores.iter().sum();
let attention: Vec<f32> = exp_scores.iter().map(|e| e / sum_exp).collect();
// Weighted sum of values
for j in 0..seq_len {
for k in head_start..head_end {
output[k] += attention[j] * values[j][k];
}
}
}
// Output projection and residual
let projected = self.project_single(&output, &self.wo);
for (k, &p) in projected.iter().enumerate() {
seq_emb.embeddings[i][k] += p * 0.1; // Scaled residual
}
}
}
/// Apply column-wise attention (for MSA, simplified here).
pub fn column_attention(&self, seq_emb: &mut SequenceEmbedding) {
// Simplified: just apply layer norm
seq_emb.layer_norm(1e-5);
}
/// Apply triangle attention for pair updates.
pub fn triangle_attention(&self, pair_emb: &mut PairEmbedding) {
let seq_len = pair_emb.sequence_length;
// Simplified triangle multiplication
for i in 0..seq_len {
for j in 0..seq_len {
let mut update = vec![0.0_f32; pair_emb.pair_dim];
// Outgoing edges
for k in 0..seq_len {
if let (Some(ik), Some(kj)) = (pair_emb.get(i, k), pair_emb.get(k, j)) {
for d in 0..pair_emb.pair_dim {
update[d] += ik[d] * kj[d] * 0.01;
}
}
}
// Apply update
for d in 0..pair_emb.pair_dim {
pair_emb.embeddings[i][j][d] += update[d];
}
}
}
}
/// Project embeddings through weight matrix.
fn project(&self, embeddings: &[Vec<f32>], weights: &[Vec<f32>]) -> Vec<Vec<f32>> {
embeddings
.iter()
.map(|emb| self.project_single(emb, weights))
.collect()
}
/// Project a single embedding.
fn project_single(&self, embedding: &[f32], weights: &[Vec<f32>]) -> Vec<f32> {
let mut output = vec![0.0_f32; weights.len()];
for (i, row) in weights.iter().enumerate() {
for (j, &w) in row.iter().enumerate() {
if j < embedding.len() {
output[i] += embedding[j] * w;
}
}
}
output
}
}
/// Evoformer stack with multiple blocks.
#[derive(Debug, Clone)]
pub struct Evoformer {
/// Stack of Evoformer blocks
blocks: Vec<EvoformerBlock>,
}
impl Evoformer {
/// Create a new Evoformer with specified number of blocks.
#[must_use]
pub fn new(num_blocks: usize) -> Self {
let blocks = (0..num_blocks).map(|_| EvoformerBlock::default()).collect();
Self { blocks }
}
/// Process sequence and pair representations through all blocks.
pub fn forward(&self, seq_emb: &mut SequenceEmbedding, pair_emb: &mut PairEmbedding) {
for block in &self.blocks {
block.row_attention(seq_emb, pair_emb);
block.column_attention(seq_emb);
block.triangle_attention(pair_emb);
pair_emb.outer_product_update(seq_emb);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::encoder::ProteinEncoder;
#[test]
fn test_evoformer_block() {
let block = EvoformerBlock::default();
assert_eq!(block.num_heads, NUM_HEADS);
assert_eq!(block.head_dim, EMBEDDING_DIM / NUM_HEADS);
}
#[test]
fn test_evoformer_forward() {
let encoder = ProteinEncoder::new();
let mut seq_emb = encoder.encode("ACDEF");
let mut pair_emb = encoder.encode_pairs(5);
let evoformer = Evoformer::new(2);
evoformer.forward(&mut seq_emb, &mut pair_emb);
// Check that embeddings were modified
assert_eq!(seq_emb.sequence_length, 5);
}
}