1#![allow(clippy::manual_try_fold)]
10
11use super::configs::{ModelTypes, ProteinMPNNConfig};
12use super::ligandfeaturesmodel::ProteinFeaturesLigand;
13use super::proteinfeatures::ProteinFeatures;
14use super::proteinfeaturesmodel::ProteinFeaturesModel;
15use crate::featurize::utilities::{cat_neighbors_nodes, gather_nodes, int_to_aa1, linear_last_dim};
16use crate::types::PseudoProbability;
17use candle_core::safetensors;
18use candle_core::{D, DType, Device, IndexOp, Module, Result, Tensor};
19use candle_nn::encoding::one_hot;
20use candle_nn::ops::{log_softmax, softmax};
21use candle_nn::{Dropout, Embedding, LayerNorm, Linear, VarBuilder, embedding, layer_norm, linear};
22use candle_transformers::generation::LogitsProcessor;
23use std::collections::HashMap;
24
25fn concat_node_tensors(h_v: &Tensor, h_e: &Tensor, e_idx: &Tensor) -> Result<Tensor> {
27 let h_ev = cat_neighbors_nodes(h_v, h_e, e_idx)?;
28 let h_v_expand = h_v.unsqueeze(D::Minus2)?;
29 let expand_shape = [
30 h_ev.dims()[0],
31 h_ev.dims()[1],
32 h_ev.dims()[2],
33 h_v_expand.dims()[3],
34 ];
35 let h_v_expand = h_v_expand.expand(&expand_shape)?.to_dtype(h_ev.dtype())?;
36 Tensor::cat(&[&h_v_expand, &h_ev], D::Minus1)?.contiguous()
37}
38fn apply_dropout_and_norm(
40 input: &Tensor,
41 delta: &Tensor,
42 dropout: &Dropout,
43 norm: &LayerNorm,
44 training: bool,
45) -> Result<Tensor> {
46 let delta_dropout = dropout.forward(delta, training)?;
47 norm.forward(&(input + delta_dropout)?)
48}
49
50pub fn multinomial_sample(probs: &Tensor, temperature: f64, seed: u64) -> Result<Tensor> {
51 let mut logits_processor = LogitsProcessor::new(
52 seed, Some(temperature), Some(0.95), );
57 let idx = logits_processor.sample(probs)?;
58 if idx >= 21 {
60 println!("WARNING: Invalid index {} selected", idx);
61 }
62 Tensor::new(&[idx], probs.device())
63}
64
65#[derive(Clone, Debug)]
67pub struct ScoreOutput {
68 pub(crate) s: Tensor,
70 pub(crate) log_probs: Tensor,
71 pub(crate) logits: Tensor,
72 pub(crate) decoding_order: Tensor,
73}
74impl ScoreOutput {
76 pub fn get_sequences(&self) -> Result<Vec<String>> {
77 let (b, l) = self.s.dims2()?;
78 let mut sequences = Vec::with_capacity(b);
79 for batch_idx in 0..b {
80 let batch = self.s.get(batch_idx)?;
81 let mut sequence = String::with_capacity(l);
82 for pos in 0..l {
83 let aa_idx = batch.get(pos)?.to_vec0::<u32>()?;
84 sequence.push(int_to_aa1(aa_idx));
85 }
86 sequences.push(sequence);
87 }
88 Ok(sequences)
89 }
90 pub fn get_decoding_order(&self) -> Result<Vec<u32>> {
91 let values = self.decoding_order.flatten_all()?.to_vec1::<u32>()?;
92 Ok(values)
93 }
94 pub fn get_log_probs(&self) -> &Tensor {
95 &self.log_probs
96 }
97 pub fn get_pseudo_probabilities(&self) -> Result<Vec<PseudoProbability>> {
98 let (batch_size, seq_len, _vocab_size) = self.logits.dims3()?;
99 let mut all_probabilities = Vec::with_capacity(batch_size * seq_len);
100
101 for batch_idx in 0..batch_size {
102 let batch_logits = self.logits.get(batch_idx)?;
103
104 for pos in 0..seq_len {
105 let pos_logits = batch_logits.get(pos)?;
106 let probs = softmax(&pos_logits, 0)?;
107 let probs = probs.to_vec1::<f32>()?;
108
109 let actual_pos = if let Ok(order) = self
111 .decoding_order
112 .get(batch_idx)?
113 .get(pos)?
114 .to_scalar::<u32>()
115 {
116 order as usize
117 } else {
118 pos
119 };
120
121 #[allow(clippy::needless_range_loop)] for aa_idx in 0..probs.len() {
123 if probs[aa_idx] > 0.01 {
124 all_probabilities.push(PseudoProbability {
126 position: actual_pos,
127 pseudo_prob: probs[aa_idx],
128 amino_acid: int_to_aa1(aa_idx as u32),
129 });
130 }
131 }
132 }
133 }
134 all_probabilities.sort_by(|a, b| {
136 a.position.cmp(&b.position).then(
137 b.pseudo_prob
138 .partial_cmp(&a.pseudo_prob)
139 .unwrap_or(std::cmp::Ordering::Equal),
140 )
141 });
142 Ok(all_probabilities)
143 }
144 pub fn save_as_safetensors(&self, filename: String) -> Result<()> {
145 let mut tensors = HashMap::new();
146 tensors.insert("S".to_string(), self.s.clone());
147 tensors.insert("log_probs".to_string(), self.log_probs.clone());
148 tensors.insert("logits".to_string(), self.logits.clone());
149 tensors.insert("decoding_order".to_string(), self.decoding_order.clone());
150 if let Some(parent) = std::path::Path::new(&filename).parent() {
152 std::fs::create_dir_all(parent)?;
153 }
154 let _ = safetensors::save(&tensors, &filename);
155 Ok(())
156 }
157}
158
159#[derive(Clone, Debug)]
160struct PositionWiseFeedForward {
161 w1: Linear,
162 w2: Linear,
163}
164
165impl PositionWiseFeedForward {
166 fn new(vb: VarBuilder, dim_input: usize, dim_feedforward: usize) -> Result<Self> {
167 let w1 = linear::linear(dim_input, dim_feedforward, vb.pp("W_in"))?;
168 let w2 = linear::linear(dim_feedforward, dim_input, vb.pp("W_out"))?;
169 Ok(Self { w1, w2 })
170 }
171}
172
173impl PositionWiseFeedForward {
174 fn forward_any_rank(&self, x: &Tensor) -> Result<Tensor> {
179 let h = linear_last_dim(&self.w1, x)?.gelu_erf()?;
180 linear_last_dim(&self.w2, &h)
181 }
182}
183
184impl Module for PositionWiseFeedForward {
185 fn forward(&self, x: &Tensor) -> Result<Tensor> {
192 self.w1
193 .forward(x)?
194 .gelu_erf()
195 .and_then(|x| self.w2.forward(&x))
196 }
197}
198
199#[derive(Clone, Debug)]
200#[allow(dead_code)]
201pub struct EncLayer {
202 num_hidden: usize,
203 num_in: usize,
204 scale: f64,
205 dropout1: Dropout,
206 dropout2: Dropout,
207 dropout3: Dropout,
208 norm1: LayerNorm,
209 norm2: LayerNorm,
210 norm3: LayerNorm,
211 w1: Linear,
212 w2: Linear,
213 w3: Linear,
214 w11: Linear,
215 w12: Linear,
216 w13: Linear,
217 dense: PositionWiseFeedForward,
218}
219
220impl EncLayer {
221 pub fn load(vb: VarBuilder, config: &ProteinMPNNConfig, layer: i32) -> Result<Self> {
222 let vb = vb.pp(layer); let num_hidden = config.hidden_dim as usize;
224 let augment_eps = 1e-5f64;
225 let num_in = (config.hidden_dim * 2) as usize;
226 let dropout_ratio = config.dropout_ratio;
227
228 let norm1 = layer_norm(num_hidden, augment_eps, vb.pp("norm1"))?;
230 let norm2 = layer_norm(num_hidden, augment_eps, vb.pp("norm2"))?;
231 let norm3 = layer_norm(num_hidden, augment_eps, vb.pp("norm3"))?;
232
233 let w1 = linear(num_hidden + num_in, num_hidden, vb.pp("W1"))?;
235 let w2 = linear(num_hidden, num_hidden, vb.pp("W2"))?;
236 let w3 = linear(num_hidden, num_hidden, vb.pp("W3"))?;
237 let w11 = linear(num_hidden + num_in, num_hidden, vb.pp("W11"))?;
238 let w12 = linear(num_hidden, num_hidden, vb.pp("W12"))?;
239 let w13 = linear(num_hidden, num_hidden, vb.pp("W13"))?;
240
241 let dropout1 = Dropout::new(dropout_ratio);
243 let dropout2 = Dropout::new(dropout_ratio);
244 let dropout3 = Dropout::new(dropout_ratio);
245
246 let dense = PositionWiseFeedForward::new(vb.pp("dense"), num_hidden, num_hidden * 4)?;
247
248 Ok(Self {
249 num_hidden,
250 num_in,
251 scale: config.scale_factor,
252 dropout1,
253 dropout2,
254 dropout3,
255 norm1,
256 norm2,
257 norm3,
258 w1,
259 w2,
260 w3,
261 w11,
262 w12,
263 w13,
264 dense,
265 })
266 }
267 fn forward(
268 &self,
269 h_v: &Tensor,
270 h_e: &Tensor,
271 e_idx: &Tensor,
272 mask_v: Option<&Tensor>,
273 mask_attend: Option<&Tensor>,
274 training: Option<bool>,
275 ) -> Result<(Tensor, Tensor)> {
276 let training = training.unwrap_or(false);
277 let h_v = h_v.to_dtype(DType::F32)?;
278 let h_ev = concat_node_tensors(&h_v, h_e, e_idx)?;
279 let h_message = self
280 .w1
281 .forward(&h_ev)?
282 .gelu_erf()?
283 .apply(&self.w2)?
284 .gelu_erf()?
285 .apply(&self.w3)?;
286
287 let h_message = mask_attend
288 .map(|mask| mask.unsqueeze(D::Minus1)?.broadcast_mul(&h_message))
289 .transpose()?
290 .unwrap_or(h_message);
291
292 let scale = if self.scale == 0.0 { 1.0 } else { self.scale };
294 let dh = (h_message.sum(D::Minus2)? / scale)?;
295
296 let h_v = apply_dropout_and_norm(&h_v, &dh, &self.dropout1, &self.norm1, training)?;
297 let dense_output = self.dense.forward(&h_v)?;
298 let h_v =
299 apply_dropout_and_norm(&h_v, &dense_output, &self.dropout2, &self.norm2, training)?;
300
301 let h_v = mask_v
303 .map(|mask| mask.unsqueeze(D::Minus1)?.broadcast_mul(&h_v))
304 .transpose()?
305 .unwrap_or(h_v);
306
307 let h_ev = concat_node_tensors(&h_v, h_e, e_idx)?;
308 let h_message = self
309 .w11
310 .forward(&h_ev)?
311 .gelu_erf()?
312 .apply(&self.w12)?
313 .gelu_erf()?
314 .apply(&self.w13)?;
315
316 let h_e = apply_dropout_and_norm(h_e, &h_message, &self.dropout3, &self.norm3, training)?;
317 Ok((h_v, h_e))
318 }
319}
320
321#[derive(Clone, Debug)]
322#[allow(dead_code)]
323pub struct DecLayer {
324 num_hidden: usize,
325 num_in: usize,
326 scale: f64,
327 dropout1: Dropout,
328 dropout2: Dropout,
329 norm1: LayerNorm,
330 norm2: LayerNorm,
331 w1: Linear,
332 w2: Linear,
333 w3: Linear,
334 dense: PositionWiseFeedForward,
335}
336
337impl DecLayer {
338 pub fn load(vb: VarBuilder, config: &ProteinMPNNConfig, layer: i32) -> Result<Self> {
340 Self::load_with_num_in(vb, config, layer, (config.hidden_dim * 3) as usize)
341 }
342
343 pub fn load_with_num_in(
350 vb: VarBuilder,
351 config: &ProteinMPNNConfig,
352 layer: i32,
353 num_in: usize,
354 ) -> Result<Self> {
355 let vb = vb.pp(layer); let num_hidden = config.hidden_dim as usize;
357 let augment_eps = 1e-5f64;
358 let dropout_ratio = config.dropout_ratio;
359
360 let norm1 = layer_norm::layer_norm(num_hidden, augment_eps, vb.pp("norm1"))?;
361 let norm2 = layer_norm::layer_norm(num_hidden, augment_eps, vb.pp("norm2"))?;
362
363 let w1 = linear::linear(num_hidden + num_in, num_hidden, vb.pp("W1"))?;
364 let w2 = linear::linear(num_hidden, num_hidden, vb.pp("W2"))?;
365 let w3 = linear::linear(num_hidden, num_hidden, vb.pp("W3"))?;
366 let dropout1 = Dropout::new(dropout_ratio);
367 let dropout2 = Dropout::new(dropout_ratio);
368
369 let dense = PositionWiseFeedForward::new(vb.pp("dense"), num_hidden, num_hidden * 4)?;
370
371 Ok(Self {
372 num_hidden,
373 num_in,
374 scale: config.scale_factor,
375 dropout1,
376 dropout2,
377 norm1,
378 norm2,
379 w1,
380 w2,
381 w3,
382 dense,
383 })
384 }
385 pub fn forward(
386 &self,
387 h_v: &Tensor,
388 h_e: &Tensor,
389 mask_v: Option<&Tensor>,
390 mask_attend: Option<&Tensor>,
391 training: Option<bool>,
392 ) -> Result<Tensor> {
393 let training_bool = training.unwrap_or(false);
394
395 let expand_shape = [
397 h_e.dims()[0], h_e.dims()[1], h_e.dims()[2], h_v.dims()[2], ];
402
403 let h_v_expand = h_v.unsqueeze(D::Minus2)?.expand(&expand_shape)?;
404 let h_ev = Tensor::cat(&[&h_v_expand, h_e], D::Minus1)?.contiguous()?;
405
406 let h_message = self
407 .w1
408 .forward(&h_ev)?
409 .gelu_erf()?
410 .apply(&self.w2)?
411 .gelu_erf()?
412 .apply(&self.w3)?;
413
414 let h_message = self.dropout1.forward(&h_message, training_bool)?;
415
416 let h_message = mask_attend
417 .map(|mask| mask.unsqueeze(D::Minus1)?.broadcast_mul(&h_message))
418 .transpose()?
419 .unwrap_or(h_message);
420
421 let dh = (h_message.sum(D::Minus2)? / self.scale)?;
422 let h_v = self.norm1.forward(&(h_v + dh)?)?;
423 let dh = self.dense.forward(&h_v)?;
424 let dh_dropout = self.dropout2.forward(&dh, training_bool)?;
425 let h_v = self.norm2.forward(&(h_v + dh_dropout)?)?;
426
427 let h_v = mask_v
429 .map(|mask| mask.unsqueeze(D::Minus1)?.broadcast_mul(&h_v))
430 .transpose()?
431 .unwrap_or(h_v);
432
433 Ok(h_v)
434 }
435}
436
437const LIGAND_CONTEXT_LAYERS: i32 = 2;
443
444#[derive(Clone, Debug)]
458pub struct DecLayerJ {
459 scale: f64,
460 norm1: LayerNorm,
461 norm2: LayerNorm,
462 w1: Linear,
463 w2: Linear,
464 w3: Linear,
465 dense: PositionWiseFeedForward,
466}
467
468impl DecLayerJ {
469 pub fn load(vb: VarBuilder, config: &ProteinMPNNConfig, layer: i32) -> Result<Self> {
470 let vb = vb.pp(layer);
471 let num_hidden = config.hidden_dim as usize;
472 let num_in = num_hidden;
475 let eps = 1e-5f64;
476
477 Ok(Self {
478 scale: config.scale_factor,
479 norm1: layer_norm::layer_norm(num_hidden, eps, vb.pp("norm1"))?,
480 norm2: layer_norm::layer_norm(num_hidden, eps, vb.pp("norm2"))?,
481 w1: linear::linear(num_hidden + num_in, num_hidden, vb.pp("W1"))?,
482 w2: linear::linear(num_hidden, num_hidden, vb.pp("W2"))?,
483 w3: linear::linear(num_hidden, num_hidden, vb.pp("W3"))?,
484 dense: PositionWiseFeedForward::new(vb.pp("dense"), num_hidden, num_hidden * 4)?,
485 })
486 }
487
488 pub fn forward(
490 &self,
491 h_v: &Tensor,
492 h_e: &Tensor,
493 mask_v: Option<&Tensor>,
494 mask_attend: Option<&Tensor>,
495 ) -> Result<Tensor> {
496 let (b, l, m, _, _) = h_e.dims5()?;
497 let c = h_v.dim(D::Minus1)?;
498 let h_v_expand = h_v.unsqueeze(D::Minus2)?.broadcast_as((b, l, m, m, c))?;
499 let h_ev = Tensor::cat(&[&h_v_expand, h_e], D::Minus1)?.contiguous()?;
500
501 let h_message = linear_last_dim(&self.w1, &h_ev)?.gelu_erf()?;
504 let h_message = linear_last_dim(&self.w2, &h_message)?.gelu_erf()?;
505 let h_message = linear_last_dim(&self.w3, &h_message)?;
506 let h_message = mask_attend
507 .map(|mask| mask.unsqueeze(D::Minus1)?.broadcast_mul(&h_message))
508 .transpose()?
509 .unwrap_or(h_message);
510
511 let dh = (h_message.sum(D::Minus2)? / self.scale)?;
512 let h_v = self.norm1.forward(&(h_v + dh)?)?;
513 let dh = self.dense.forward_any_rank(&h_v)?;
514 let h_v = self.norm2.forward(&(h_v + dh)?)?;
515
516 mask_v
517 .map(|mask| mask.unsqueeze(D::Minus1)?.broadcast_mul(&h_v))
518 .transpose()?
519 .map_or(Ok(h_v), Ok)
520 }
521}
522
523pub struct ProteinMPNN {
526 pub(crate) config: ProteinMPNNConfig,
527 pub(crate) decoder_layers: Vec<DecLayer>,
528 pub(crate) device: Device,
529 pub(crate) encoder_layers: Vec<EncLayer>,
530 pub(crate) features: Option<ProteinFeaturesModel>,
532 pub(crate) ligand: Option<LigandModules>,
534 pub(crate) w_e: Linear,
535 pub(crate) w_out: Linear,
536 pub(crate) w_s: Embedding,
537}
538
539pub(crate) struct LigandModules {
545 features: ProteinFeaturesLigand,
546 w_v: Linear,
548 w_c: Linear,
550 w_nodes_y: Linear,
551 w_edges_y: Linear,
552 v_c: Linear,
554 v_c_norm: LayerNorm,
555 context_encoder_layers: Vec<DecLayer>,
556 y_context_encoder_layers: Vec<DecLayerJ>,
557}
558
559impl ProteinMPNN {
560 pub fn load(vb: VarBuilder, config: &ProteinMPNNConfig) -> Result<Self> {
561 let hidden_dim = config.hidden_dim as usize;
562 let edge_features = config.edge_features as usize;
563 let num_letters = config.num_letters as usize;
564 let node_features = config.node_features as usize;
565 let vocab_size = config.vocab as usize;
566
567 let encoder_layers = (0..config.num_encoder_layers)
569 .map(|i| EncLayer::load(vb.pp("encoder_layers"), config, i as i32))
570 .collect::<Result<Vec<_>>>()?;
571
572 let decoder_layers = (0..config.num_decoder_layers)
573 .map(|i| DecLayer::load(vb.pp("decoder_layers"), config, i as i32))
574 .collect::<Result<Vec<_>>>()?;
575
576 let w_e = linear::linear(edge_features, hidden_dim, vb.pp("W_e"))?;
578 let w_out = linear::linear(hidden_dim, num_letters, vb.pp("W_out"))?;
579 let w_s = embedding(vocab_size, hidden_dim, vb.pp("W_s"))?;
580
581 let (features, ligand) = match config.model_type {
589 ModelTypes::ProteinMPNN => (
590 Some(ProteinFeaturesModel::load(
591 vb.pp("features"),
592 config.clone(),
593 )?),
594 None,
595 ),
596 ModelTypes::LigandMPNN => (
597 None,
598 Some(LigandModules {
599 features: ProteinFeaturesLigand::load(vb.pp("features"), config)?,
600 w_v: linear::linear(node_features, hidden_dim, vb.pp("W_v"))?,
601 w_c: linear::linear(hidden_dim, hidden_dim, vb.pp("W_c"))?,
602 w_nodes_y: linear::linear(hidden_dim, hidden_dim, vb.pp("W_nodes_y"))?,
603 w_edges_y: linear::linear(hidden_dim, hidden_dim, vb.pp("W_edges_y"))?,
604 v_c: linear::linear_no_bias(hidden_dim, hidden_dim, vb.pp("V_C"))?,
605 v_c_norm: layer_norm::layer_norm(hidden_dim, 1e-5f64, vb.pp("V_C_norm"))?,
606 context_encoder_layers: (0..LIGAND_CONTEXT_LAYERS)
609 .map(|i| {
610 DecLayer::load_with_num_in(
611 vb.pp("context_encoder_layers"),
612 config,
613 i,
614 hidden_dim * 2,
615 )
616 })
617 .collect::<Result<Vec<_>>>()?,
618 y_context_encoder_layers: (0..LIGAND_CONTEXT_LAYERS)
619 .map(|i| DecLayerJ::load(vb.pp("y_context_encoder_layers"), config, i))
620 .collect::<Result<Vec<_>>>()?,
621 }),
622 ),
623 };
624
625 Ok(Self {
626 config: config.clone(),
627 decoder_layers,
628 device: vb.device().clone(),
629 encoder_layers,
630 features,
631 ligand,
632 w_e,
633 w_out,
634 w_s,
635 })
636 }
637 pub fn encode(&self, features: &ProteinFeatures) -> Result<(Tensor, Tensor, Tensor)> {
644 let mask = self.sequence_mask(features)?;
645
646 let (v, e, e_idx, ligand_graph) = match (&self.features, &self.ligand) {
647 (Some(protein), None) => {
648 let (e, e_idx) = protein.forward(features, &self.device)?;
649 (None, e, e_idx, None)
650 }
651 (None, Some(ligand)) => {
652 let f = ligand.features.forward(features)?;
653 (Some(f.v), f.e, f.e_idx, Some((f.y_nodes, f.y_edges, f.y_m)))
654 }
655 _ => candle_core::bail!(
657 "{:?}: built with no featurizer, or with both",
658 self.config.model_type
659 ),
660 };
661
662 let h_v = Tensor::zeros(
663 (e.dim(0)?, e.dim(1)?, e.dim(D::Minus1)?),
664 DType::F32,
665 &self.device,
666 )?;
667 let h_e = self.w_e.forward(&e)?;
668
669 let mask_gathered =
671 gather_nodes(&mask.unsqueeze(D::Minus1)?, &e_idx)?.squeeze(D::Minus1)?;
672 let mask_attend = mask.unsqueeze(D::Minus1)?.broadcast_mul(&mask_gathered)?;
673
674 let (h_v, h_e) = self
675 .encoder_layers
676 .iter()
677 .try_fold((h_v, h_e), |(h_v, h_e), layer| {
678 layer.forward(
679 &h_v,
680 &h_e,
681 &e_idx,
682 Some(&mask),
683 Some(&mask_attend),
684 Some(false),
685 )
686 })?;
687
688 let h_v = match (&self.ligand, v, ligand_graph) {
689 (Some(ligand), Some(v), Some((y_nodes, y_edges, y_m))) => {
690 self.encode_ligand_context(ligand, &h_v, &v, &y_nodes, &y_edges, &y_m, &mask)?
691 }
692 _ => h_v,
693 };
694
695 Ok((h_v, h_e, e_idx))
696 }
697
698 fn sequence_mask(&self, features: &ProteinFeatures) -> Result<Tensor> {
700 match features.get_sequence_mask() {
701 Some(m) => m.to_dtype(DType::F32),
702 None => Tensor::ones_like(features.get_sequence())?.to_dtype(DType::F32),
703 }
704 }
705
706 #[allow(clippy::too_many_arguments)] fn encode_ligand_context(
724 &self,
725 ligand: &LigandModules,
726 h_v: &Tensor,
727 v: &Tensor,
728 y_nodes: &Tensor,
729 y_edges: &Tensor,
730 y_m: &Tensor,
731 mask: &Tensor,
732 ) -> Result<Tensor> {
733 let h_e_context = linear_last_dim(&ligand.w_v, v)?;
735 let mut h_v_c = ligand.w_c.forward(h_v)?;
736
737 let y_m = y_m.to_dtype(DType::F32)?;
738 let y_m_edges = y_m
740 .unsqueeze(D::Minus1)?
741 .broadcast_mul(&y_m.unsqueeze(D::Minus2)?)?;
742
743 let mut y_nodes = linear_last_dim(&ligand.w_nodes_y, y_nodes)?;
745 let y_edges = linear_last_dim(&ligand.w_edges_y, y_edges)?;
747
748 for (y_layer, context_layer) in ligand
749 .y_context_encoder_layers
750 .iter()
751 .zip(&ligand.context_encoder_layers)
752 {
753 y_nodes = y_layer.forward(&y_nodes, &y_edges, Some(&y_m), Some(&y_m_edges))?;
754 let h_e_context_cat = Tensor::cat(&[&h_e_context, &y_nodes], D::Minus1)?;
755 h_v_c = context_layer.forward(
756 &h_v_c,
757 &h_e_context_cat,
758 Some(mask),
759 Some(&y_m),
760 Some(false),
761 )?;
762 }
763
764 let h_v_c = ligand.v_c.forward(&h_v_c)?;
765 h_v + ligand.v_c_norm.forward(&h_v_c)?
766 }
767
768 pub fn simple_decode(&self, features: &ProteinFeatures) -> Result<ScoreOutput> {
770 let b_decoder = 1;
772
773 let ProteinFeatures { s, x_mask, .. } = features;
775 let device = s.device();
776 let (_, l) = s.dims2()?;
777
778 let (h_v_enc, h_e_enc, e_idx_enc) = self.encode(features)?;
780
781 let s_true = s.clone();
783 let mask = x_mask.clone().unwrap();
784
785 let zeros = Tensor::zeros((b_decoder, l, h_v_enc.dim(D::Minus1)?), DType::F32, device)?;
787 let h_v = h_v_enc.clone();
788 let h_e = h_e_enc.clone();
789 let e_idx = e_idx_enc.clone();
790
791 let h_ex_encoder = cat_neighbors_nodes(&zeros, &h_e, &e_idx)?;
793 let h_exv_encoder = cat_neighbors_nodes(&h_v, &h_ex_encoder, &e_idx)?;
794
795 let h_v_final = self.decoder_layers.iter().fold(Ok(h_v), |acc, layer| {
797 layer.forward(&acc?, &h_exv_encoder, Some(&mask), None, None)
798 })?;
799
800 let logits = self.w_out.forward(&h_v_final)?;
802 let log_probs = log_softmax(&logits, D::Minus1)?;
803
804 let decoding_order = Tensor::arange(0, l as i64, device)?
806 .reshape((1, l))?
807 .broadcast_as((b_decoder, l))?
808 .to_dtype(DType::F32)?;
809
810 Ok(ScoreOutput {
812 s: s_true,
813 log_probs,
814 logits,
815 decoding_order,
816 })
817 }
818 pub fn sample(
819 &self,
820 features: &ProteinFeatures,
821 temperature: f64,
822 seed: u64,
823 ) -> Result<ScoreOutput> {
824 let sample_dtype = DType::F32;
825 let ProteinFeatures {
826 s,
827 x_mask,
828 ..
831 } = features;
832 let s_true = s.to_dtype(sample_dtype)?;
833 let device = s.device();
834 let (b, l) = s.dims2()?;
835 let chain_mask = x_mask.as_ref().unwrap().to_dtype(sample_dtype)?;
839 let (h_v, h_e, e_idx) = self.encode(features)?;
840 let rand_tensor = Tensor::randn(0f32, 0.25f32, (b, l), device)?.to_dtype(sample_dtype)?;
841 let decoding_order = (&chain_mask + 0.0001)?
846 .mul(&rand_tensor.abs()?)?
847 .arg_sort_last_dim(true)?;
848 let bias = Tensor::ones((b, l, 21), sample_dtype, device)?;
851 let symmetry_residues: Option<Vec<i32>> = None;
852 match symmetry_residues {
853 None => {
854 let e_idx = e_idx.repeat(&[b, 1, 1])?;
855 let permutation_matrix_reverse = one_hot(decoding_order.clone(), l, 1f32, 0f32)?
856 .to_dtype(sample_dtype)?
857 .contiguous()?;
858 let tril = Tensor::tril2(l, sample_dtype, device)?;
859 let tril = tril.unsqueeze(0)?;
860 let temp = tril
861 .matmul(&permutation_matrix_reverse.transpose(1, 2)?)?
862 .contiguous()?; let order_mask_backward = temp
864 .matmul(&permutation_matrix_reverse.transpose(1, 2)?)?
865 .contiguous()?; let mask_attend = order_mask_backward
867 .gather(&e_idx, 2)?
868 .unsqueeze(D::Minus1)?;
869 let mask_1d = x_mask.as_ref().unwrap().reshape((b, l, 1, 1))?;
870 let mask_1d = mask_1d
872 .broadcast_as(mask_attend.shape())?
873 .to_dtype(sample_dtype)?;
874 let mask_bw = mask_1d.mul(&mask_attend)?;
875 let mask_fw = mask_1d.mul(&(Tensor::ones_like(&mask_attend)? - mask_attend)?)?;
876 let s_true = s_true.repeat((b, 1))?;
879 let h_v = h_v.repeat((b, 1, 1))?;
880 let h_e = h_e.repeat((b, 1, 1, 1))?;
881 let mask = x_mask.as_ref().unwrap().repeat((b, 1))?.contiguous()?;
882 let chain_mask = &chain_mask.repeat((b, 1))?;
883 let bias = bias.repeat((b, 1, 1))?;
884 let mut all_probs = Tensor::zeros((b, l, 20), sample_dtype, device)?;
885 let mut all_log_probs = Tensor::zeros((b, l, 21), sample_dtype, device)?;
887 let mut h_s = Tensor::zeros_like(&h_v)?;
888 let mut s = Tensor::full(20u32, (b, l), device)?;
890 let mut h_v_stack = vec![h_v.clone()];
891
892 for _ in 0..self.decoder_layers.len() {
893 let zeros = Tensor::zeros_like(&h_v)?;
894 h_v_stack.push(zeros);
895 }
896 let h_ex_encoder = cat_neighbors_nodes(&Tensor::zeros_like(&h_s)?, &h_e, &e_idx)?;
897 let h_exv_encoder = cat_neighbors_nodes(&h_v, &h_ex_encoder, &e_idx)?;
898 let mask_fw = mask_fw
899 .broadcast_as(h_exv_encoder.shape())?
900 .to_dtype(h_exv_encoder.dtype())?;
901 let h_exv_encoder_fw = mask_fw.mul(&h_exv_encoder)?;
902 for t_ in 0..l {
903 let t = decoding_order.i((.., t_))?;
904 let t_gather = t.unsqueeze(1)?; let chain_mask_t = chain_mask.gather(&t_gather, 1)?.squeeze(1)?;
907 let mask_t = mask.gather(&t_gather, 1)?.squeeze(1)?.contiguous()?;
908 let bias_t = bias
909 .gather(&t_gather.unsqueeze(2)?.expand((b, 1, 21))?.contiguous()?, 1)?
910 .squeeze(1)?;
911 let e_idx_t = e_idx
913 .gather(
914 &t_gather
915 .unsqueeze(2)?
916 .expand((b, 1, e_idx.dim(2)?))?
917 .contiguous()?,
918 1,
919 )?
920 .contiguous()?;
921 let h_e_t = h_e.gather(
922 &t_gather
923 .unsqueeze(2)?
924 .unsqueeze(3)?
925 .expand((b, 1, h_e.dim(2)?, h_e.dim(3)?))?
926 .contiguous()?,
927 1,
928 )?;
929 let n = e_idx_t.dim(2)?; let c = h_s.dim(2)?; let h_e_t = h_e_t
932 .squeeze(1)? .unsqueeze(1)? .expand((b, l, n, c))? .contiguous()?;
936 let e_idx_t = e_idx_t
937 .expand((b, l, n))? .contiguous()?;
939 let h_es_t = cat_neighbors_nodes(&h_s, &h_e_t, &e_idx_t)?;
940 let h_exv_encoder_t = h_exv_encoder_fw.gather(
941 &t_gather
942 .unsqueeze(2)?
943 .unsqueeze(3)?
944 .expand((b, 1, h_exv_encoder_fw.dim(2)?, h_exv_encoder_fw.dim(3)?))?
945 .contiguous()?,
946 1,
947 )?;
948 let mask_bw_t = mask_bw.gather(
949 &t_gather
950 .unsqueeze(2)?
951 .unsqueeze(3)?
952 .expand((b, 1, mask_bw.dim(2)?, mask_bw.dim(3)?))?
953 .contiguous()?,
954 1,
955 )?;
956
957 for l in 0..self.decoder_layers.len() {
959 let h_v_stack_l = &h_v_stack[l];
960 let h_esv_decoder_t = cat_neighbors_nodes(h_v_stack_l, &h_es_t, &e_idx_t)?;
961 let h_v_t = h_v_stack_l.gather(
962 &t_gather
963 .unsqueeze(2)?
964 .expand((b, 1, h_v_stack_l.dim(2)?))?
965 .contiguous()?,
966 1,
967 )?;
968 let mask_bw_t = mask_bw_t.expand(h_esv_decoder_t.dims())?.contiguous()?;
969 let h_exv_encoder_t = h_exv_encoder_t
970 .expand(h_esv_decoder_t.dims())?
971 .contiguous()?
972 .to_dtype(sample_dtype)?;
973 let h_esv_t = mask_bw_t
974 .mul(&h_esv_decoder_t.to_dtype(sample_dtype)?)?
975 .add(&h_exv_encoder_t)?
976 .to_dtype(sample_dtype)?
977 .contiguous()?;
978 let h_v_t = h_v_t
979 .expand((
980 h_esv_t.dim(0)?, h_esv_t.dim(1)?, h_v_t.dim(2)?, ))?
984 .contiguous()?;
985 let decoder_output = self.decoder_layers[l].forward(
986 &h_v_t,
987 &h_esv_t,
988 Some(&mask_t),
989 None,
990 None,
991 )?;
992 let t_expanded = t_gather.reshape(&[b])?; let decoder_output = decoder_output
994 .narrow(1, 0, 1)?
995 .squeeze(1)? .unsqueeze(1)?; h_v_stack[l + 1] =
998 h_v_stack[l + 1].index_add(&t_expanded, &decoder_output, 1)?;
999 }
1002 let h_v_t = h_v_stack
1003 .last()
1004 .unwrap()
1005 .gather(
1006 &t_gather
1007 .unsqueeze(2)?
1008 .expand((b, 1, h_v_stack.last().unwrap().dim(2)?))?
1009 .contiguous()?,
1010 1,
1011 )?
1012 .squeeze(1)?;
1013 let logits = self.w_out.forward(&h_v_t)?;
1015 let log_probs = log_softmax(&logits, D::Minus1)?;
1016
1017 let probs = {
1019 let biased_logits = logits.add(&bias_t)?; let scaled_logits = (biased_logits / temperature)?; softmax(&scaled_logits, D::Minus1)? };
1023
1024 let probs_sample = probs
1025 .narrow(1, 0, 20)?
1026 .div(&probs.narrow(1, 0, 20)?.sum_keepdim(1)?.expand((b, 20))?)?;
1027 let sum = probs_sample.sum(1)?;
1029 let probs_sample_1d = probs_sample
1030 .squeeze(0)? .clamp(1e-10, 1.0)?
1032 .broadcast_div(&sum)?
1033 .contiguous()?;
1034
1035 let s_t = multinomial_sample(&probs_sample_1d, temperature, seed)?;
1036 let s_t = s_t.to_dtype(sample_dtype)?;
1037 let s_true = s_true.to_dtype(sample_dtype)?;
1038 let s_true_t = s_true.gather(&t_gather, 1)?.squeeze(1)?;
1039 let s_t = s_t
1040 .mul(&chain_mask_t)?
1041 .add(&s_true_t.mul(&(&chain_mask_t.neg()? + 1.0)?)?)?
1042 .to_dtype(DType::U32)?;
1043
1044 let s_t_idx = s_t.to_dtype(DType::U32)?;
1045 let s_t_idx = s_t_idx.reshape(&[s_t_idx.dim(0)?])?;
1046 let h_s_update = self.w_s.forward(&s_t_idx)?.unsqueeze(1)?;
1047 let t_gather_expanded = t_gather.reshape(&[b])?;
1048 let h_s_update = h_s_update.squeeze(0)?.unsqueeze(1)?;
1049 h_s =
1050 h_s.index_add(&t_gather_expanded, &Tensor::zeros_like(&h_s_update)?, 1)?;
1051 h_s = h_s.index_add(&t_gather_expanded, &h_s_update, 1)?;
1052
1053 s = {
1054 let dim = 1;
1055 let start = t_gather.squeeze(0)?.squeeze(0)?.to_scalar::<u32>()? as usize;
1056 let s_t_expanded = s_t.unsqueeze(1)?;
1057 s.slice_scatter(&s_t_expanded, dim, start)?
1058 };
1059
1060 let probs_update = chain_mask_t
1061 .unsqueeze(1)?
1062 .unsqueeze(2)?
1063 .expand((b, 1, 20))?
1064 .mul(&probs_sample.unsqueeze(1)?)?;
1065 let t_expanded = t_gather.reshape(&[b])?;
1066 let probs_update = probs_update
1067 .squeeze(1)? .unsqueeze(1)?;
1069 all_probs =
1070 all_probs.index_add(&t_expanded, &Tensor::zeros_like(&probs_update)?, 1)?;
1071 all_probs = all_probs.index_add(&t_expanded, &probs_update, 1)?;
1072 let log_probs_update = chain_mask_t
1073 .unsqueeze(1)?
1074 .unsqueeze(2)?
1075 .expand((b, 1, 21))?
1076 .mul(&log_probs.unsqueeze(1)?)?
1077 .squeeze(1)?
1078 .unsqueeze(1)?;
1079
1080 all_log_probs = all_log_probs.index_add(
1081 &t_expanded,
1082 &Tensor::zeros_like(&log_probs_update)?,
1083 1,
1084 )?;
1085 all_log_probs = all_log_probs.index_add(&t_expanded, &log_probs_update, 1)?;
1086 }
1087 Ok(ScoreOutput {
1088 s,
1089 log_probs: all_probs,
1090 logits: all_log_probs,
1091 decoding_order,
1092 })
1093 }
1094 Some(_symmetry_residues) => {
1095 candle_core::bail!("symmetry residues in sample() not yet implemented")
1096 }
1097 }
1098 }
1099
1100 pub fn score(&self, features: &ProteinFeatures, use_sequence: bool) -> Result<ScoreOutput> {
1101 let ProteinFeatures { s, x_mask, .. } = &features;
1102 let sample_dtype = DType::F32;
1103 let s_true = &s.clone();
1104 let device = s_true.device();
1105 let (b, l) = s_true.dims2()?;
1106 let mask = &x_mask.as_ref().clone();
1107 let b_decoder: usize = b;
1108
1109 let chain_mask = Tensor::zeros_like(mask.unwrap())?.to_dtype(sample_dtype)?;
1112 let chain_mask = mask.unwrap().mul(&chain_mask)?; let (h_v, h_e, e_idx) = self.encode(features)?;
1116 let rand_tensor = Tensor::randn(0f32, 1f32, (b, l), device)?.to_dtype(sample_dtype)?;
1117 let decoding_order = (chain_mask + 0.001)?
1120 .mul(&rand_tensor.abs()?)?
1121 .arg_sort_last_dim(true)?;
1122
1123 let symmetry_residues: Option<Vec<i32>> = None;
1124
1125 let (mask_fw, mask_bw, e_idx, decoding_order) = match symmetry_residues {
1126 Some(_symmetry_residues) => {
1127 candle_core::bail!("symmetry residues in score() not yet implemented")
1128 }
1129 None => {
1130 let e_idx = e_idx.repeat(&[b_decoder, 1, 1])?;
1131 let permutation_matrix_reverse = one_hot(decoding_order.clone(), l, 1f32, 0f32)?
1132 .to_dtype(sample_dtype)?
1133 .contiguous()?;
1134
1135 let tril = Tensor::tril2(l, sample_dtype, device)?.unsqueeze(0)?;
1136 let temp = tril
1137 .matmul(&permutation_matrix_reverse.transpose(1, 2)?)?
1138 .contiguous()?; let order_mask_backward = temp
1140 .matmul(&permutation_matrix_reverse.transpose(1, 2)?)?
1141 .contiguous()?; let mask_attend = order_mask_backward
1143 .gather(&e_idx, 2)?
1144 .unsqueeze(D::Minus1)?;
1145
1146 let mask_1d = mask
1148 .unwrap()
1149 .reshape((b, l, 1, 1))?
1150 .broadcast_as(mask_attend.shape())?
1151 .to_dtype(sample_dtype)?;
1152
1153 let mask_bw = mask_1d.mul(&mask_attend)?;
1154 let mask_fw = mask_1d.mul(&(mask_attend - 1.0)?.neg()?)?;
1155 (mask_fw, mask_bw, e_idx, decoding_order)
1156 }
1157 };
1158
1159 let s_true = s_true.repeat(&[b_decoder, 1])?;
1160 let h_v = h_v.repeat(&[b_decoder, 1, 1])?;
1161 let h_e = h_e.repeat(&[b_decoder, 1, 1, 1])?;
1162 let mask = mask.as_ref().unwrap().repeat(&[b_decoder, 1])?;
1163
1164 let h_s = self.w_s.forward(&s_true)?; let h_es = cat_neighbors_nodes(&h_s, &h_e, &e_idx)?;
1166
1167 let h_ex_encoder = cat_neighbors_nodes(&Tensor::zeros_like(&h_s)?, &h_e, &e_idx)?;
1169 let h_exv_encoder = cat_neighbors_nodes(&h_v, &h_ex_encoder, &e_idx)?;
1170 let h_exv_encoder_fw = mask_fw
1171 .broadcast_as(h_exv_encoder.shape())?
1172 .to_dtype(h_exv_encoder.dtype())?
1173 .mul(&h_exv_encoder)?;
1174
1175 let h_v = if !use_sequence {
1177 self.decoder_layers.iter().fold(Ok(h_v), |acc, layer| {
1179 layer.forward(&acc?, &h_exv_encoder_fw, Some(&mask), None, None)
1180 })?
1181 } else {
1182 self.decoder_layers.iter().fold(Ok(h_v), |acc, layer| {
1184 let current_h_v = acc?;
1185 let h_esv = cat_neighbors_nodes(¤t_h_v, &h_es, &e_idx)?
1186 .mul(&mask_bw)?
1187 .add(&h_exv_encoder_fw)?;
1188 layer.forward(¤t_h_v, &h_esv, Some(&mask), None, None)
1189 })?
1190 };
1191
1192 let logits = self.w_out.forward(&h_v)?;
1193 let log_probs = log_softmax(&logits, D::Minus1)?;
1194
1195 Ok(ScoreOutput {
1196 s: s_true,
1197 log_probs,
1198 logits,
1199 decoding_order,
1200 })
1201 }
1202}