//! Shared IPC types for AlphaFold-Lite protein structure prediction demo. //! //! This crate provides the data structures used for communication between //! the Tauri frontend and the Rust backend for protein structure prediction. use serde::{Deserialize, Serialize}; // ============================================================================ // Input Types // ============================================================================ /// Request to predict protein structure from amino acid sequence. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PredictStructureRequest { /// Amino acid sequence (single-letter codes: A, C, D, E, F, G, H, I, K, L, M, N, P, Q, R, S, T, V, W, Y) pub sequence: String, /// Optional name/identifier for the protein pub name: Option, /// Model configuration pub config: PredictionConfig, } /// Configuration for structure prediction. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PredictionConfig { /// Number of recycles (refinement iterations) pub num_recycles: u32, /// Whether to use templates if available pub use_templates: bool, /// Number of structure samples to generate pub num_samples: u32, /// Random seed for reproducibility pub seed: Option, /// Model variant to use pub model_variant: ModelVariant, } impl Default for PredictionConfig { fn default() -> Self { Self { num_recycles: 3, use_templates: false, num_samples: 1, seed: None, model_variant: ModelVariant::Lite, } } } /// Model variant for structure prediction. #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum ModelVariant { /// Lightweight model for fast predictions Lite, /// Standard model with balanced speed/accuracy Standard, /// High-accuracy model (slower) Accurate, } // ============================================================================ // Output Types // ============================================================================ /// Predicted protein structure. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ProteinStructure { /// Protein name/identifier pub name: String, /// Input amino acid sequence pub sequence: String, /// Number of residues pub num_residues: usize, /// 3D coordinates for each atom (N, CA, C, O, CB for each residue) pub atom_coords: Vec, /// Per-residue confidence scores (pLDDT: 0-100) pub plddt_scores: Vec, /// Predicted aligned error (PAE) matrix pub pae_matrix: Option>>, /// Overall model confidence pub model_confidence: ModelConfidence, /// Secondary structure assignment pub secondary_structure: Vec, /// Chain information pub chains: Vec, } /// 3D coordinates for a single atom. #[derive(Debug, Clone, Copy, Serialize, Deserialize)] pub struct AtomCoord { /// Residue index (0-based) pub residue_idx: usize, /// Atom name (N, CA, C, O, CB, etc.) pub atom_name: AtomName, /// X coordinate (Angstroms) pub x: f32, /// Y coordinate (Angstroms) pub y: f32, /// Z coordinate (Angstroms) pub z: f32, /// B-factor (temperature factor / confidence) pub b_factor: f32, } /// Standard backbone and CB atom names. #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "UPPERCASE")] pub enum AtomName { /// Backbone nitrogen N, /// Alpha carbon Ca, /// Backbone carbonyl carbon C, /// Backbone oxygen O, /// Beta carbon (not present in glycine) Cb, } impl AtomName { /// Get the standard PDB atom name string. pub fn as_pdb_str(&self) -> &'static str { match self { AtomName::N => "N", AtomName::Ca => "CA", AtomName::C => "C", AtomName::O => "O", AtomName::Cb => "CB", } } } /// Overall model confidence metrics. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ModelConfidence { /// Average pLDDT score (0-100) pub avg_plddt: f32, /// Predicted template modeling score (pTM: 0-1) pub ptm_score: f32, /// Interface predicted template modeling score (ipTM: 0-1, for multimers) pub iptm_score: Option, /// Confidence category pub category: ConfidenceCategory, } /// Confidence category based on pLDDT scores. #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum ConfidenceCategory { /// Very high confidence (pLDDT > 90) VeryHigh, /// High confidence (70 < pLDDT <= 90) High, /// Low confidence (50 < pLDDT <= 70) Low, /// Very low confidence (pLDDT <= 50) VeryLow, } impl ConfidenceCategory { /// Determine category from average pLDDT score. pub fn from_plddt(plddt: f32) -> Self { if plddt > 90.0 { ConfidenceCategory::VeryHigh } else if plddt > 70.0 { ConfidenceCategory::High } else if plddt > 50.0 { ConfidenceCategory::Low } else { ConfidenceCategory::VeryLow } } /// Get the color for visualization (RGB hex). pub fn color(&self) -> &'static str { match self { ConfidenceCategory::VeryHigh => "#0053D6", // Blue ConfidenceCategory::High => "#65CBF3", // Cyan ConfidenceCategory::Low => "#FFDB13", // Yellow ConfidenceCategory::VeryLow => "#FF7D45", // Orange } } } /// Secondary structure assignment for a residue. #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum SecondaryStructure { /// Alpha helix (H) Helix, /// Beta strand/sheet (E) Strand, /// Coil/loop (C) Coil, /// Turn (T) Turn, } impl SecondaryStructure { /// Get the DSSP single-letter code. pub fn dssp_code(&self) -> char { match self { SecondaryStructure::Helix => 'H', SecondaryStructure::Strand => 'E', SecondaryStructure::Coil => 'C', SecondaryStructure::Turn => 'T', } } } /// Information about a protein chain. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ChainInfo { /// Chain identifier (A, B, C, etc.) pub chain_id: char, /// Starting residue index (0-based) pub start_residue: usize, /// Ending residue index (exclusive) pub end_residue: usize, /// Sequence for this chain pub sequence: String, } // ============================================================================ // Amino Acid Types // ============================================================================ /// Standard amino acid. #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub enum AminoAcid { Ala, // A - Alanine Cys, // C - Cysteine Asp, // D - Aspartic acid Glu, // E - Glutamic acid Phe, // F - Phenylalanine Gly, // G - Glycine His, // H - Histidine Ile, // I - Isoleucine Lys, // K - Lysine Leu, // L - Leucine Met, // M - Methionine Asn, // N - Asparagine Pro, // P - Proline Gln, // Q - Glutamine Arg, // R - Arginine Ser, // S - Serine Thr, // T - Threonine Val, // V - Valine Trp, // W - Tryptophan Tyr, // Y - Tyrosine Unk, // X - Unknown } impl AminoAcid { /// Parse from single-letter code. pub fn from_code(c: char) -> Option { match c.to_ascii_uppercase() { 'A' => Some(AminoAcid::Ala), 'C' => Some(AminoAcid::Cys), 'D' => Some(AminoAcid::Asp), 'E' => Some(AminoAcid::Glu), 'F' => Some(AminoAcid::Phe), 'G' => Some(AminoAcid::Gly), 'H' => Some(AminoAcid::His), 'I' => Some(AminoAcid::Ile), 'K' => Some(AminoAcid::Lys), 'L' => Some(AminoAcid::Leu), 'M' => Some(AminoAcid::Met), 'N' => Some(AminoAcid::Asn), 'P' => Some(AminoAcid::Pro), 'Q' => Some(AminoAcid::Gln), 'R' => Some(AminoAcid::Arg), 'S' => Some(AminoAcid::Ser), 'T' => Some(AminoAcid::Thr), 'V' => Some(AminoAcid::Val), 'W' => Some(AminoAcid::Trp), 'Y' => Some(AminoAcid::Tyr), 'X' => Some(AminoAcid::Unk), _ => None, } } /// Get single-letter code. pub fn code(&self) -> char { match self { AminoAcid::Ala => 'A', AminoAcid::Cys => 'C', AminoAcid::Asp => 'D', AminoAcid::Glu => 'E', AminoAcid::Phe => 'F', AminoAcid::Gly => 'G', AminoAcid::His => 'H', AminoAcid::Ile => 'I', AminoAcid::Lys => 'K', AminoAcid::Leu => 'L', AminoAcid::Met => 'M', AminoAcid::Asn => 'N', AminoAcid::Pro => 'P', AminoAcid::Gln => 'Q', AminoAcid::Arg => 'R', AminoAcid::Ser => 'S', AminoAcid::Thr => 'T', AminoAcid::Val => 'V', AminoAcid::Trp => 'W', AminoAcid::Tyr => 'Y', AminoAcid::Unk => 'X', } } /// Get three-letter code. pub fn code3(&self) -> &'static str { match self { AminoAcid::Ala => "ALA", AminoAcid::Cys => "CYS", AminoAcid::Asp => "ASP", AminoAcid::Glu => "GLU", AminoAcid::Phe => "PHE", AminoAcid::Gly => "GLY", AminoAcid::His => "HIS", AminoAcid::Ile => "ILE", AminoAcid::Lys => "LYS", AminoAcid::Leu => "LEU", AminoAcid::Met => "MET", AminoAcid::Asn => "ASN", AminoAcid::Pro => "PRO", AminoAcid::Gln => "GLN", AminoAcid::Arg => "ARG", AminoAcid::Ser => "SER", AminoAcid::Thr => "THR", AminoAcid::Val => "VAL", AminoAcid::Trp => "TRP", AminoAcid::Tyr => "TYR", AminoAcid::Unk => "UNK", } } /// Get embedding index (0-20). pub fn embedding_idx(&self) -> usize { match self { AminoAcid::Ala => 0, AminoAcid::Cys => 1, AminoAcid::Asp => 2, AminoAcid::Glu => 3, AminoAcid::Phe => 4, AminoAcid::Gly => 5, AminoAcid::His => 6, AminoAcid::Ile => 7, AminoAcid::Lys => 8, AminoAcid::Leu => 9, AminoAcid::Met => 10, AminoAcid::Asn => 11, AminoAcid::Pro => 12, AminoAcid::Gln => 13, AminoAcid::Arg => 14, AminoAcid::Ser => 15, AminoAcid::Thr => 16, AminoAcid::Val => 17, AminoAcid::Trp => 18, AminoAcid::Tyr => 19, AminoAcid::Unk => 20, } } } // ============================================================================ // Export Types // ============================================================================ /// Request to export structure to a file format. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ExportRequest { /// Structure to export pub structure: ProteinStructure, /// Output format pub format: ExportFormat, } /// Supported export formats. #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "lowercase")] pub enum ExportFormat { /// Protein Data Bank format Pdb, /// MacroMolecular Crystallographic Information File Mmcif, /// JSON format Json, } /// Export result containing the formatted data. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ExportResult { /// Formatted structure data pub data: String, /// Suggested filename pub filename: String, /// MIME type pub mime_type: String, } // ============================================================================ // Validation // ============================================================================ /// Validate an amino acid sequence. pub fn validate_sequence(sequence: &str) -> Result, ValidationError> { if sequence.is_empty() { return Err(ValidationError::EmptySequence); } if sequence.len() > 2500 { return Err(ValidationError::SequenceTooLong { length: sequence.len(), max: 2500, }); } let mut amino_acids = Vec::with_capacity(sequence.len()); for (i, c) in sequence.chars().enumerate() { match AminoAcid::from_code(c) { Some(aa) => amino_acids.push(aa), None => { return Err(ValidationError::InvalidResidue { position: i, char: c, }); } } } Ok(amino_acids) } /// Sequence validation error. #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(tag = "type", rename_all = "snake_case")] pub enum ValidationError { /// Empty sequence provided EmptySequence, /// Sequence exceeds maximum length SequenceTooLong { length: usize, max: usize }, /// Invalid residue character InvalidResidue { position: usize, char: char }, } impl std::fmt::Display for ValidationError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { ValidationError::EmptySequence => write!(f, "Sequence cannot be empty"), ValidationError::SequenceTooLong { length, max } => { write!(f, "Sequence too long: {} residues (max {})", length, max) } ValidationError::InvalidResidue { position, char } => { write!(f, "Invalid residue '{}' at position {}", char, position) } } } } impl std::error::Error for ValidationError {} // ============================================================================ // Sample Proteins // ============================================================================ /// Sample protein for demonstration. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct SampleProtein { /// Protein name pub name: String, /// UniProt ID or PDB ID pub id: String, /// Amino acid sequence pub sequence: String, /// Description pub description: String, /// Organism pub organism: String, /// Sequence length pub length: usize, } /// Get list of sample proteins for the demo. pub fn get_sample_proteins() -> Vec { vec![ SampleProtein { name: "Insulin".to_string(), id: "P01308".to_string(), sequence: "MALWMRLLPLLALLALWGPDPAAAFVNQHLCGSHLVEALYLVCGERGFFYTPKT".to_string(), description: "Insulin precursor - regulates glucose metabolism".to_string(), organism: "Homo sapiens".to_string(), length: 54, }, SampleProtein { name: "Green Fluorescent Protein (GFP)".to_string(), id: "1EMA".to_string(), sequence: "MSKGEELFTGVVPILVELDGDVNGHKFSVSGEGEGDATYGKLTLKFICTTGKLPVPWPTLVTTFSYGVQCFSRYPDHMKQHDFFKSAMPEGYVQERTIFFKDDGNYKTRAEVKFEGDTLVNRIELKGIDFKEDGNILGHKLEYNYNSHNVYIMADKQKNGIKVNFKIRHNIEDGSVQLADHYQQNTPIGDGPVLLPDNHYLSTQSALSKDPNEKRDHMVLLEFVTAAGITHGMDELYK".to_string(), description: "Fluorescent protein used as a reporter in biology".to_string(), organism: "Aequorea victoria".to_string(), length: 238, }, SampleProtein { name: "Ubiquitin".to_string(), id: "P0CG48".to_string(), sequence: "MQIFVKTLTGKTITLEVEPSDTIENVKAKIQDKEGIPPDQQRLIFAGKQLEDGRTLSDYNIQKESTLHLVLRLRGG".to_string(), description: "Highly conserved protein involved in protein degradation".to_string(), organism: "Homo sapiens".to_string(), length: 76, }, SampleProtein { name: "Lysozyme".to_string(), id: "P00698".to_string(), sequence: "KVFGRCELAAAMKRHGLDNYRGYSLGNWVCAAKFESNFNTQATNRNTDGSTDYGILQINSRWWCNDGRTPGSRNLCNIPCSALLSSDITASVNCAKKIVSDGNGMNAWVAWRNRCKGTDVQAWIRGCRL".to_string(), description: "Enzyme that damages bacterial cell walls".to_string(), organism: "Gallus gallus".to_string(), length: 129, }, SampleProtein { name: "Myoglobin".to_string(), id: "P02144".to_string(), sequence: "MGLSDGEWQLVLNVWGKVEADIPGHGQEVLIRLFKGHPETLEKFDKFKHLKSEDEMKASEDLKKHGATVLTALGGILKKKGHHEAEIKPLAQSHATKHKIPVKYLEFISECIIQVLQSKHPGDFGADAQGAMNKALELFRKDMASNYKELGFQG".to_string(), description: "Oxygen-binding protein in muscle tissue".to_string(), organism: "Homo sapiens".to_string(), length: 154, }, ] } // ============================================================================ // Tests // ============================================================================ #[cfg(test)] mod tests { use super::*; #[test] fn test_amino_acid_from_code() { assert_eq!(AminoAcid::from_code('A'), Some(AminoAcid::Ala)); assert_eq!(AminoAcid::from_code('a'), Some(AminoAcid::Ala)); assert_eq!(AminoAcid::from_code('Z'), None); } #[test] fn test_amino_acid_codes() { let aa = AminoAcid::Met; assert_eq!(aa.code(), 'M'); assert_eq!(aa.code3(), "MET"); assert_eq!(aa.embedding_idx(), 10); } #[test] fn test_validate_sequence_valid() { let result = validate_sequence("ACDEFGHIKLMNPQRSTVWY"); assert!(result.is_ok()); assert_eq!(result.unwrap().len(), 20); } #[test] fn test_validate_sequence_empty() { let result = validate_sequence(""); assert!(matches!(result, Err(ValidationError::EmptySequence))); } #[test] fn test_validate_sequence_invalid_char() { let result = validate_sequence("ACBDE"); assert!(matches!( result, Err(ValidationError::InvalidResidue { position: 2, char: 'B' }) )); } #[test] fn test_confidence_category() { assert_eq!( ConfidenceCategory::from_plddt(95.0), ConfidenceCategory::VeryHigh ); assert_eq!( ConfidenceCategory::from_plddt(80.0), ConfidenceCategory::High ); assert_eq!( ConfidenceCategory::from_plddt(60.0), ConfidenceCategory::Low ); assert_eq!( ConfidenceCategory::from_plddt(40.0), ConfidenceCategory::VeryLow ); } #[test] fn test_prediction_config_default() { let config = PredictionConfig::default(); assert_eq!(config.num_recycles, 3); assert!(!config.use_templates); assert_eq!(config.num_samples, 1); } #[test] fn test_sample_proteins() { let samples = get_sample_proteins(); assert!(!samples.is_empty()); for sample in &samples { assert_eq!(sample.sequence.len(), sample.length); assert!(validate_sequence(&sample.sequence).is_ok()); } } #[test] fn test_atom_name_pdb_str() { assert_eq!(AtomName::N.as_pdb_str(), "N"); assert_eq!(AtomName::Ca.as_pdb_str(), "CA"); assert_eq!(AtomName::C.as_pdb_str(), "C"); assert_eq!(AtomName::O.as_pdb_str(), "O"); assert_eq!(AtomName::Cb.as_pdb_str(), "CB"); } #[test] fn test_secondary_structure_dssp() { assert_eq!(SecondaryStructure::Helix.dssp_code(), 'H'); assert_eq!(SecondaryStructure::Strand.dssp_code(), 'E'); assert_eq!(SecondaryStructure::Coil.dssp_code(), 'C'); assert_eq!(SecondaryStructure::Turn.dssp_code(), 'T'); } #[test] fn test_export_format_serialization() { let format = ExportFormat::Pdb; let json = serde_json::to_string(&format).unwrap(); assert_eq!(json, "\"pdb\""); } #[test] fn test_model_variant_serialization() { let variant = ModelVariant::Accurate; let json = serde_json::to_string(&variant).unwrap(); assert_eq!(json, "\"accurate\""); } }