quantrs2_anneal/applications/
protein_folding.rs

1//! Protein Folding Optimization with Quantum Error Correction
2//!
3//! This module implements advanced protein folding optimization using quantum annealing
4//! with integrated quantum error correction and cutting-edge algorithms. It addresses
5//! the fundamental protein folding problem using the HP (Hydrophobic-Polar) model and
6//! advanced lattice models with quantum-enhanced optimization.
7//!
8//! Key Features:
9//! - HP model and extended lattice formulations
10//! - Quantum error correction for noise-resilient folding predictions
11//! - Advanced algorithms (∞-QAOA, Zeno annealing, adiabatic shortcuts)
12//! - Neural network guided optimization schedules
13//! - Bayesian hyperparameter optimization
14//! - Multi-objective optimization (energy, compactness, stability)
15
16use std::collections::{HashMap, VecDeque};
17use std::fmt;
18
19use crate::advanced_quantum_algorithms::{
20    AdvancedAlgorithmConfig, AdvancedQuantumAlgorithms, AlgorithmSelectionStrategy,
21    InfiniteDepthQAOA, InfiniteQAOAConfig, QuantumZenoAnnealer, ZenoConfig,
22};
23use crate::applications::{
24    ApplicationError, ApplicationResult, IndustrySolution, OptimizationProblem,
25};
26use crate::bayesian_hyperopt::{optimize_annealing_parameters, BayesianHyperoptimizer};
27use crate::ising::{IsingModel, QuboModel};
28use crate::neural_annealing_schedules::{NeuralAnnealingScheduler, NeuralSchedulerConfig};
29use crate::quantum_error_correction::{
30    ErrorCorrectionCode, ErrorMitigationConfig, ErrorMitigationManager, LogicalAnnealingEncoder,
31    NoiseResilientAnnealingProtocol, SyndromeDetector,
32};
33use crate::simulator::{AnnealingParams, QuantumAnnealingSimulator};
34use std::fmt::Write;
35
36/// Amino acid types in the HP model
37#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
38pub enum AminoAcidType {
39    /// Hydrophobic amino acid
40    Hydrophobic,
41    /// Polar amino acid
42    Polar,
43}
44
45impl fmt::Display for AminoAcidType {
46    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
47        match self {
48            Self::Hydrophobic => write!(f, "H"),
49            Self::Polar => write!(f, "P"),
50        }
51    }
52}
53
54/// Protein sequence representation
55#[derive(Debug, Clone)]
56pub struct ProteinSequence {
57    /// Sequence of amino acids
58    pub sequence: Vec<AminoAcidType>,
59    /// Sequence identifier
60    pub id: String,
61    /// Additional metadata
62    pub metadata: HashMap<String, String>,
63}
64
65impl ProteinSequence {
66    /// Create new protein sequence
67    #[must_use]
68    pub fn new(sequence: Vec<AminoAcidType>, id: String) -> Self {
69        Self {
70            sequence,
71            id,
72            metadata: HashMap::new(),
73        }
74    }
75
76    /// Create sequence from string representation
77    pub fn from_string(sequence: &str, id: String) -> ApplicationResult<Self> {
78        let mut amino_acids = Vec::new();
79
80        for ch in sequence.chars() {
81            match ch.to_ascii_uppercase() {
82                'H' => amino_acids.push(AminoAcidType::Hydrophobic),
83                'P' => amino_acids.push(AminoAcidType::Polar),
84                _ => {
85                    return Err(ApplicationError::DataValidationError(format!(
86                        "Invalid amino acid character: {ch}"
87                    )))
88                }
89            }
90        }
91
92        Ok(Self::new(amino_acids, id))
93    }
94
95    /// Get sequence length
96    #[must_use]
97    pub fn length(&self) -> usize {
98        self.sequence.len()
99    }
100
101    /// Count hydrophobic residues
102    #[must_use]
103    pub fn hydrophobic_count(&self) -> usize {
104        self.sequence
105            .iter()
106            .filter(|&&aa| aa == AminoAcidType::Hydrophobic)
107            .count()
108    }
109
110    /// Count polar residues
111    #[must_use]
112    pub fn polar_count(&self) -> usize {
113        self.sequence
114            .iter()
115            .filter(|&&aa| aa == AminoAcidType::Polar)
116            .count()
117    }
118}
119
120/// Lattice types for protein folding
121#[derive(Debug, Clone, Copy, PartialEq, Eq)]
122pub enum LatticeType {
123    /// 2D square lattice
124    Square2D,
125    /// 2D triangular lattice
126    Triangular2D,
127    /// 3D cubic lattice
128    Cubic3D,
129    /// 3D face-centered cubic lattice
130    FCC3D,
131}
132
133/// Position on a lattice
134#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
135pub struct LatticePosition {
136    pub x: i32,
137    pub y: i32,
138    pub z: i32,
139}
140
141impl LatticePosition {
142    #[must_use]
143    pub const fn new(x: i32, y: i32, z: i32) -> Self {
144        Self { x, y, z }
145    }
146
147    #[must_use]
148    pub fn distance(&self, other: &Self) -> f64 {
149        let dx = f64::from(self.x - other.x);
150        let dy = f64::from(self.y - other.y);
151        let dz = f64::from(self.z - other.z);
152        dz.mul_add(dz, dx.mul_add(dx, dy * dy)).sqrt()
153    }
154
155    #[must_use]
156    pub const fn manhattan_distance(&self, other: &Self) -> i32 {
157        (self.x - other.x).abs() + (self.y - other.y).abs() + (self.z - other.z).abs()
158    }
159}
160
161/// Protein folding configuration on a lattice
162#[derive(Debug, Clone)]
163pub struct ProteinFolding {
164    /// The protein sequence
165    pub sequence: ProteinSequence,
166    /// Lattice type
167    pub lattice_type: LatticeType,
168    /// Positions of amino acids on the lattice
169    pub positions: Vec<LatticePosition>,
170}
171
172impl ProteinFolding {
173    /// Create new protein folding
174    #[must_use]
175    pub fn new(sequence: ProteinSequence, lattice_type: LatticeType) -> Self {
176        let positions = vec![LatticePosition::new(0, 0, 0); sequence.length()];
177        Self {
178            sequence,
179            lattice_type,
180            positions,
181        }
182    }
183
184    /// Set position for amino acid at index
185    pub fn set_position(
186        &mut self,
187        index: usize,
188        position: LatticePosition,
189    ) -> ApplicationResult<()> {
190        if index >= self.sequence.length() {
191            return Err(ApplicationError::InvalidConfiguration(format!(
192                "Index {} out of bounds for sequence length {}",
193                index,
194                self.sequence.length()
195            )));
196        }
197        self.positions[index] = position;
198        Ok(())
199    }
200
201    /// Check if folding is valid (no overlaps, connected chain)
202    #[must_use]
203    pub fn is_valid(&self) -> bool {
204        // Check for position overlaps
205        let mut position_set = std::collections::HashSet::new();
206        for &pos in &self.positions {
207            if !position_set.insert(pos) {
208                return false; // Overlap found
209            }
210        }
211
212        // Check chain connectivity
213        for i in 1..self.positions.len() {
214            let dist = self.positions[i - 1].manhattan_distance(&self.positions[i]);
215            if dist != 1 {
216                return false; // Not connected
217            }
218        }
219
220        true
221    }
222
223    /// Calculate hydrophobic-hydrophobic contacts
224    #[must_use]
225    pub fn hydrophobic_contacts(&self) -> i32 {
226        let mut contacts = 0;
227
228        for i in 0..self.sequence.length() {
229            if self.sequence.sequence[i] != AminoAcidType::Hydrophobic {
230                continue;
231            }
232
233            for j in (i + 2)..self.sequence.length() {
234                // Skip adjacent in sequence
235                if self.sequence.sequence[j] != AminoAcidType::Hydrophobic {
236                    continue;
237                }
238
239                if self.positions[i].manhattan_distance(&self.positions[j]) == 1 {
240                    contacts += 1;
241                }
242            }
243        }
244
245        contacts
246    }
247
248    /// Calculate compactness (radius of gyration)
249    #[must_use]
250    pub fn radius_of_gyration(&self) -> f64 {
251        let n = self.positions.len() as f64;
252
253        // Calculate center of mass
254        let cx = self.positions.iter().map(|p| f64::from(p.x)).sum::<f64>() / n;
255        let cy = self.positions.iter().map(|p| f64::from(p.y)).sum::<f64>() / n;
256        let cz = self.positions.iter().map(|p| f64::from(p.z)).sum::<f64>() / n;
257
258        // Calculate radius of gyration
259        let rg_sq = self
260            .positions
261            .iter()
262            .map(|p| {
263                let dx = f64::from(p.x) - cx;
264                let dy = f64::from(p.y) - cy;
265                let dz = f64::from(p.z) - cz;
266                dz.mul_add(dz, dx.mul_add(dx, dy * dy))
267            })
268            .sum::<f64>()
269            / n;
270
271        rg_sq.sqrt()
272    }
273
274    /// Calculate total energy (negative hydrophobic contacts + compactness penalty)
275    #[must_use]
276    pub fn total_energy(&self) -> f64 {
277        let hh_contacts = f64::from(self.hydrophobic_contacts());
278        let compactness_penalty = self.radius_of_gyration();
279
280        // Energy = negative contacts (favorable) + compactness penalty
281        0.1f64.mul_add(compactness_penalty, -hh_contacts)
282    }
283}
284
285/// Protein folding optimization problem
286#[derive(Debug, Clone)]
287pub struct ProteinFoldingProblem {
288    /// The protein sequence to fold
289    pub sequence: ProteinSequence,
290    /// Lattice type for folding
291    pub lattice_type: LatticeType,
292    /// Optimization objectives
293    pub objectives: Vec<FoldingObjective>,
294    /// Quantum error correction framework
295    pub qec_framework: Option<String>,
296    /// Advanced algorithm configuration
297    pub advanced_config: AdvancedAlgorithmConfig,
298    /// Neural scheduling configuration
299    pub neural_config: Option<NeuralSchedulerConfig>,
300}
301
302/// Folding optimization objectives
303#[derive(Debug, Clone)]
304pub enum FoldingObjective {
305    /// Maximize hydrophobic-hydrophobic contacts
306    MaximizeHHContacts,
307    /// Minimize radius of gyration (compactness)
308    MinimizeRadiusOfGyration,
309    /// Minimize total energy
310    MinimizeTotalEnergy,
311    /// Maximize structural stability
312    MaximizeStability,
313}
314
315impl ProteinFoldingProblem {
316    /// Create new protein folding problem
317    #[must_use]
318    pub fn new(sequence: ProteinSequence, lattice_type: LatticeType) -> Self {
319        Self {
320            sequence,
321            lattice_type,
322            objectives: vec![FoldingObjective::MaximizeHHContacts],
323            qec_framework: None,
324            advanced_config: AdvancedAlgorithmConfig {
325                enable_infinite_qaoa: true,
326                enable_zeno_annealing: true,
327                enable_adiabatic_shortcuts: true,
328                enable_counterdiabatic: true,
329                selection_strategy: AlgorithmSelectionStrategy::ProblemSpecific,
330                track_performance: true,
331            },
332            neural_config: None,
333        }
334    }
335
336    /// Enable quantum error correction
337    #[must_use]
338    pub fn with_quantum_error_correction(mut self, config: String) -> Self {
339        self.qec_framework = Some(config);
340        self
341    }
342
343    /// Enable neural annealing schedules
344    #[must_use]
345    pub fn with_neural_annealing(mut self, config: NeuralSchedulerConfig) -> Self {
346        self.neural_config = Some(config);
347        self
348    }
349
350    /// Add optimization objective
351    #[must_use]
352    pub fn add_objective(mut self, objective: FoldingObjective) -> Self {
353        self.objectives.push(objective);
354        self
355    }
356
357    /// Solve using advanced quantum algorithms
358    pub fn solve_with_advanced_algorithms(&self) -> ApplicationResult<ProteinFolding> {
359        println!("Starting protein folding optimization with advanced quantum algorithms");
360
361        // Convert to QUBO formulation
362        let (qubo, variable_map) = self.to_qubo()?;
363
364        // Create advanced quantum algorithms coordinator
365        let algorithms = AdvancedQuantumAlgorithms::with_config(self.advanced_config.clone());
366
367        // Solve using best algorithm for this problem size
368        let result = algorithms.solve(&qubo, None).map_err(|e| {
369            ApplicationError::OptimizationError(format!("Advanced algorithm failed: {e:?}"))
370        })?;
371
372        let solution = result
373            .map_err(|e| ApplicationError::OptimizationError(format!("Solver error: {e}")))?;
374
375        // Convert binary solution back to protein folding
376        self.solution_from_binary(&solution, &variable_map)
377    }
378
379    /// Solve using quantum error correction
380    pub fn solve_with_qec(&self) -> ApplicationResult<ProteinFolding> {
381        if let Some(ref qec_framework) = self.qec_framework {
382            println!("Starting noise-resilient protein folding optimization");
383
384            // Convert to QUBO and then to Ising
385            let (qubo, variable_map) = self.to_qubo()?;
386            let ising_model = qubo.to_ising();
387
388            // Use error mitigation for protein folding optimization
389            let error_config = ErrorMitigationConfig::default();
390            let mut error_manager = ErrorMitigationManager::new(error_config).map_err(|e| {
391                ApplicationError::OptimizationError(format!(
392                    "Failed to create error manager: {e:?}"
393                ))
394            })?;
395
396            // First perform standard annealing
397            let params = AnnealingParams::default();
398            let annealer = QuantumAnnealingSimulator::new(params.clone()).map_err(|e| {
399                ApplicationError::OptimizationError(format!("Failed to create annealer: {e:?}"))
400            })?;
401            let annealing_result = annealer.solve(&ising_model.0).map_err(|e| {
402                ApplicationError::OptimizationError(format!("Annealing failed: {e:?}"))
403            })?;
404
405            // Convert simulator result to error mitigation format
406            let error_mitigation_result =
407                crate::quantum_error_correction::error_mitigation::AnnealingResult {
408                    solution: annealing_result
409                        .best_spins
410                        .iter()
411                        .map(|&x| i32::from(x))
412                        .collect(),
413                    energy: annealing_result.best_energy,
414                    num_occurrences: 1,
415                    chain_break_fraction: 0.0,
416                    timing: std::collections::HashMap::new(),
417                    info: std::collections::HashMap::new(),
418                };
419
420            // Apply error mitigation to improve the result
421            let mitigation_result = error_manager
422                .apply_mitigation(&ising_model.0, error_mitigation_result, &params)
423                .map_err(|e| {
424                    ApplicationError::OptimizationError(format!("Error mitigation failed: {e:?}"))
425                })?;
426
427            let solution = &mitigation_result.mitigated_result.solution;
428
429            // Convert back to folding
430            self.solution_from_binary(&solution, &variable_map)
431        } else {
432            Err(ApplicationError::InvalidConfiguration(
433                "Quantum error correction not enabled".to_string(),
434            ))
435        }
436    }
437
438    /// Solve using neural annealing schedules
439    pub fn solve_with_neural_annealing(&self) -> ApplicationResult<ProteinFolding> {
440        if let Some(ref neural_config) = self.neural_config {
441            println!("Starting protein folding with neural-guided annealing");
442
443            let (qubo, variable_map) = self.to_qubo()?;
444
445            // Create neural annealing scheduler
446            let mut neural_scheduler = NeuralAnnealingScheduler::new(neural_config.clone())
447                .map_err(|e| {
448                    ApplicationError::OptimizationError(format!(
449                        "Failed to create neural scheduler: {e:?}"
450                    ))
451                })?;
452
453            // Optimize using neural-guided schedules
454            let result = neural_scheduler.optimize(&qubo).map_err(|e| {
455                ApplicationError::OptimizationError(format!("Neural annealing failed: {e:?}"))
456            })?;
457
458            let solution = result.map_err(|e| {
459                ApplicationError::OptimizationError(format!("Neural solver error: {e}"))
460            })?;
461
462            self.solution_from_binary(&solution, &variable_map)
463        } else {
464            Err(ApplicationError::InvalidConfiguration(
465                "Neural annealing not enabled".to_string(),
466            ))
467        }
468    }
469
470    /// Optimize hyperparameters using Bayesian optimization
471    pub fn optimize_hyperparameters(&self) -> ApplicationResult<HashMap<String, f64>> {
472        println!("Optimizing folding hyperparameters with Bayesian optimization");
473
474        // Define objective function for hyperparameter optimization
475        let objective = |params: &[f64]| -> f64 {
476            // params[0] = temperature, params[1] = annealing steps, params[2] = algorithm type
477            let temperature = params[0];
478            let steps = params[1] as usize;
479            // let _algorithm_type = params[2] as usize;
480
481            // Simple folding energy approximation for optimization
482            let length = self.sequence.length() as f64;
483            let hydrophobic_ratio = self.sequence.hydrophobic_count() as f64 / length;
484
485            // Simulate annealing performance
486            let energy_scale = -hydrophobic_ratio * length / 4.0; // Approximate optimal HH contacts
487            let thermal_noise = temperature * 0.1;
488            let convergence_factor = (steps as f64 / 1000.0).min(1.0);
489
490            // Return negative energy (minimize)
491            -(energy_scale * convergence_factor - thermal_noise)
492        };
493
494        // Run Bayesian optimization
495        let best_params = optimize_annealing_parameters(objective, Some(30)).map_err(|e| {
496            ApplicationError::OptimizationError(format!("Bayesian optimization failed: {e:?}"))
497        })?;
498
499        let mut result = HashMap::new();
500        result.insert("optimal_temperature".to_string(), best_params[0]);
501        result.insert("optimal_steps".to_string(), best_params[1]);
502        result.insert("optimal_algorithm".to_string(), best_params[2]);
503
504        Ok(result)
505    }
506
507    /// Convert binary solution vector to protein folding
508    fn solution_from_binary(
509        &self,
510        solution: &[i32],
511        variable_map: &HashMap<String, usize>,
512    ) -> ApplicationResult<ProteinFolding> {
513        let mut folding = ProteinFolding::new(self.sequence.clone(), self.lattice_type);
514
515        // Decode positions from binary solution
516        // This is a simplified decoding - in practice would use more sophisticated encoding
517        let seq_len = self.sequence.length();
518
519        // Place first amino acid at origin
520        folding.set_position(0, LatticePosition::new(0, 0, 0))?;
521
522        // Decode subsequent positions using relative moves
523        for i in 1..seq_len {
524            let move_var_base = (i - 1) * 2; // 2 bits per move for 2D
525
526            if move_var_base + 1 < solution.len() {
527                let move_x = i32::from(solution[move_var_base] > 0);
528                let move_y = i32::from(solution[move_var_base + 1] > 0);
529
530                let prev_pos = folding.positions[i - 1];
531                let new_pos = match (move_x, move_y) {
532                    (0, 0) => LatticePosition::new(prev_pos.x + 1, prev_pos.y, prev_pos.z),
533                    (0, 1) => LatticePosition::new(prev_pos.x, prev_pos.y + 1, prev_pos.z),
534                    (1, 0) => LatticePosition::new(prev_pos.x - 1, prev_pos.y, prev_pos.z),
535                    (1, 1) => LatticePosition::new(prev_pos.x, prev_pos.y - 1, prev_pos.z),
536                    _ => LatticePosition::new(prev_pos.x + 1, prev_pos.y, prev_pos.z), // Default case
537                };
538
539                folding.set_position(i, new_pos)?;
540            }
541        }
542
543        // Verify and fix if necessary
544        if !folding.is_valid() {
545            // Apply simple validation fix
546            self.fix_invalid_folding(&mut folding)?;
547        }
548
549        Ok(folding)
550    }
551
552    /// Fix invalid folding configuration
553    fn fix_invalid_folding(&self, folding: &mut ProteinFolding) -> ApplicationResult<()> {
554        // Simple SAW (Self-Avoiding Walk) generation
555        let mut positions = vec![LatticePosition::new(0, 0, 0)];
556        let mut used_positions = std::collections::HashSet::new();
557        used_positions.insert(positions[0]);
558
559        // Generate valid chain
560        for i in 1..self.sequence.length() {
561            let prev_pos = positions[i - 1];
562
563            // Try each direction
564            let directions = [
565                LatticePosition::new(1, 0, 0),
566                LatticePosition::new(-1, 0, 0),
567                LatticePosition::new(0, 1, 0),
568                LatticePosition::new(0, -1, 0),
569            ];
570
571            let mut placed = false;
572            for &dir in &directions {
573                let new_pos = LatticePosition::new(
574                    prev_pos.x + dir.x,
575                    prev_pos.y + dir.y,
576                    prev_pos.z + dir.z,
577                );
578
579                if used_positions.insert(new_pos) {
580                    positions.push(new_pos);
581                    placed = true;
582                    break;
583                }
584            }
585
586            if !placed {
587                return Err(ApplicationError::OptimizationError(
588                    "Cannot generate valid folding configuration".to_string(),
589                ));
590            }
591        }
592
593        folding.positions = positions;
594        Ok(())
595    }
596}
597
598impl OptimizationProblem for ProteinFoldingProblem {
599    type Solution = ProteinFolding;
600    type ObjectiveValue = f64;
601
602    fn description(&self) -> String {
603        format!(
604            "Protein folding optimization for sequence {} (length: {}) on {:?} lattice",
605            self.sequence.id,
606            self.sequence.length(),
607            self.lattice_type
608        )
609    }
610
611    fn size_metrics(&self) -> HashMap<String, usize> {
612        let mut metrics = HashMap::new();
613        metrics.insert("sequence_length".to_string(), self.sequence.length());
614        metrics.insert(
615            "hydrophobic_count".to_string(),
616            self.sequence.hydrophobic_count(),
617        );
618        metrics.insert("polar_count".to_string(), self.sequence.polar_count());
619        metrics.insert("variables".to_string(), (self.sequence.length() - 1) * 2); // Simplified
620        metrics
621    }
622
623    fn validate(&self) -> ApplicationResult<()> {
624        if self.sequence.length() < 2 {
625            return Err(ApplicationError::DataValidationError(
626                "Sequence must have at least 2 amino acids".to_string(),
627            ));
628        }
629
630        if self.sequence.length() > 200 {
631            return Err(ApplicationError::ResourceLimitExceeded(
632                "Sequence too long for current implementation".to_string(),
633            ));
634        }
635
636        Ok(())
637    }
638
639    fn to_qubo(&self) -> ApplicationResult<(QuboModel, HashMap<String, usize>)> {
640        self.validate()?;
641
642        let seq_len = self.sequence.length();
643        let num_vars = (seq_len - 1) * 2; // 2 bits per move for 2D lattice
644
645        let mut qubo = QuboModel::new(num_vars);
646        let mut variable_map = HashMap::new();
647
648        // Map variables
649        for i in 0..(seq_len - 1) {
650            variable_map.insert(format!("move_{i}_x"), i * 2);
651            variable_map.insert(format!("move_{i}_y"), i * 2 + 1);
652        }
653
654        // Add objective terms (simplified QUBO formulation)
655        for obj in &self.objectives {
656            match obj {
657                FoldingObjective::MaximizeHHContacts => {
658                    self.add_contact_terms(&mut qubo)?;
659                }
660                FoldingObjective::MinimizeRadiusOfGyration => {
661                    self.add_compactness_terms(&mut qubo)?;
662                }
663                _ => {
664                    // Combined terms for other objectives
665                    self.add_contact_terms(&mut qubo)?;
666                    self.add_compactness_terms(&mut qubo)?;
667                }
668            }
669        }
670
671        Ok((qubo, variable_map))
672    }
673
674    fn evaluate_solution(
675        &self,
676        solution: &Self::Solution,
677    ) -> ApplicationResult<Self::ObjectiveValue> {
678        if !solution.is_valid() {
679            return Err(ApplicationError::DataValidationError(
680                "Invalid folding configuration".to_string(),
681            ));
682        }
683
684        Ok(solution.total_energy())
685    }
686
687    fn is_feasible(&self, solution: &Self::Solution) -> bool {
688        solution.is_valid()
689    }
690}
691
692impl ProteinFoldingProblem {
693    /// Add hydrophobic contact terms to QUBO
694    fn add_contact_terms(&self, qubo: &mut QuboModel) -> ApplicationResult<()> {
695        // Simplified contact terms - in practice would be more complex
696        let weight = -1.0; // Negative to encourage contacts
697
698        for i in 0..qubo.num_variables {
699            for j in (i + 1)..qubo.num_variables {
700                // Add interaction terms that promote favorable configurations
701                qubo.set_quadratic(i, j, weight * 0.1)?;
702            }
703        }
704
705        Ok(())
706    }
707
708    /// Add compactness terms to QUBO
709    fn add_compactness_terms(&self, qubo: &mut QuboModel) -> ApplicationResult<()> {
710        // Penalty for extended configurations
711        let penalty = 0.5;
712
713        for i in 0..qubo.num_variables {
714            qubo.set_linear(i, penalty)?;
715        }
716
717        Ok(())
718    }
719}
720
721impl IndustrySolution for ProteinFolding {
722    type Problem = ProteinFoldingProblem;
723
724    fn from_binary(problem: &Self::Problem, binary_solution: &[i8]) -> ApplicationResult<Self> {
725        let solution_i32: Vec<i32> = binary_solution.iter().map(|&x| i32::from(x)).collect();
726        let variable_map = HashMap::new(); // Simplified
727        problem.solution_from_binary(&solution_i32, &variable_map)
728    }
729
730    fn summary(&self) -> HashMap<String, String> {
731        let mut summary = HashMap::new();
732        summary.insert("sequence_id".to_string(), self.sequence.id.clone());
733        summary.insert(
734            "sequence_length".to_string(),
735            self.sequence.length().to_string(),
736        );
737        summary.insert(
738            "lattice_type".to_string(),
739            format!("{:?}", self.lattice_type),
740        );
741        summary.insert(
742            "hydrophobic_contacts".to_string(),
743            self.hydrophobic_contacts().to_string(),
744        );
745        summary.insert(
746            "radius_of_gyration".to_string(),
747            format!("{:.3}", self.radius_of_gyration()),
748        );
749        summary.insert(
750            "total_energy".to_string(),
751            format!("{:.3}", self.total_energy()),
752        );
753        summary.insert("is_valid".to_string(), self.is_valid().to_string());
754        summary
755    }
756
757    fn metrics(&self) -> HashMap<String, f64> {
758        let mut metrics = HashMap::new();
759        metrics.insert(
760            "hydrophobic_contacts".to_string(),
761            f64::from(self.hydrophobic_contacts()),
762        );
763        metrics.insert("radius_of_gyration".to_string(), self.radius_of_gyration());
764        metrics.insert("total_energy".to_string(), self.total_energy());
765        metrics.insert(
766            "compactness_score".to_string(),
767            1.0 / (1.0 + self.radius_of_gyration()),
768        );
769        metrics
770    }
771
772    fn export_format(&self) -> ApplicationResult<String> {
773        let mut output = String::new();
774
775        let _ = writeln!(output, "# Protein Folding Result");
776        let _ = writeln!(output, "Sequence ID: {}", self.sequence.id);
777        let _ = writeln!(
778            output,
779            "Sequence: {}",
780            self.sequence
781                .sequence
782                .iter()
783                .map(|aa| format!("{aa}"))
784                .collect::<String>()
785        );
786        let _ = writeln!(output, "Lattice Type: {:?}", self.lattice_type);
787        let _ = write!(output, "Length: {}\n", self.sequence.length());
788        let _ = write!(
789            output,
790            "Hydrophobic Contacts: {}\n",
791            self.hydrophobic_contacts()
792        );
793        let _ = write!(
794            output,
795            "Radius of Gyration: {:.3}\n",
796            self.radius_of_gyration()
797        );
798        let _ = write!(output, "Total Energy: {:.3}\n", self.total_energy());
799        let _ = write!(output, "Valid Configuration: {}\n", self.is_valid());
800
801        output.push_str("\n# Positions\n");
802        for (i, pos) in self.positions.iter().enumerate() {
803            let _ = write!(
804                output,
805                "{}: {} ({}, {}, {})\n",
806                i, self.sequence.sequence[i], pos.x, pos.y, pos.z
807            );
808        }
809
810        Ok(output)
811    }
812}
813
814/// Create benchmark protein folding problems
815pub fn create_benchmark_problems(
816    size: usize,
817) -> ApplicationResult<
818    Vec<Box<dyn OptimizationProblem<Solution = ProteinFolding, ObjectiveValue = f64>>>,
819> {
820    let mut problems = Vec::new();
821
822    // Generate test sequences of different complexity
823    let sequences = match size {
824        s if s <= 10 => {
825            vec!["HPHPPHHPHH", "HPHHHPPHPH", "HPPHHPPHPP"]
826        }
827        s if s <= 25 => {
828            vec![
829                "HPHPPHHPHHHPPHPPHHHPHPH",
830                "HHHPPHPPHHPPHPPHHHPHPH",
831                "HPHPPHPPHHHPPHPPHHHPHP",
832                "HPPHHPPHPPHHHPPHPPHHPH",
833            ]
834        }
835        _ => {
836            vec![
837                "HPHPPHHPHHHPPHPPHHHPHPHHPHPPHHPHHHPPHPPHHHPHPH",
838                "HHHPPHPPHHPPHPPHHHPHPHHHHPPHPPHHPPHPPHHHPHPH",
839                "HPHPPHPPHHHPPHPPHHHPHPHPHPPHPPHHHPPHPPHHHPHP",
840            ]
841        }
842    };
843
844    for (i, seq_str) in sequences.iter().enumerate() {
845        let sequence = ProteinSequence::from_string(seq_str, format!("benchmark_{i}"))?;
846        let problem = ProteinFoldingProblem::new(sequence, LatticeType::Square2D);
847
848        // Note: This is a simplified implementation for the trait object
849        // In practice, would need proper trait object conversion
850        problems.push(Box::new(problem)
851            as Box<
852                dyn OptimizationProblem<Solution = ProteinFolding, ObjectiveValue = f64>,
853            >);
854    }
855
856    Ok(problems)
857}
858
859#[cfg(test)]
860mod tests {
861    use super::*;
862
863    #[test]
864    fn test_protein_sequence_creation() {
865        let sequence = ProteinSequence::from_string("HPHPPHHPHH", "test".to_string())
866            .expect("Failed to create protein sequence from valid string");
867        assert_eq!(sequence.length(), 10);
868        assert_eq!(sequence.hydrophobic_count(), 6);
869        assert_eq!(sequence.polar_count(), 4);
870    }
871
872    #[test]
873    fn test_lattice_position() {
874        let pos1 = LatticePosition::new(0, 0, 0);
875        let pos2 = LatticePosition::new(1, 0, 0);
876
877        assert_eq!(pos1.manhattan_distance(&pos2), 1);
878        assert!((pos1.distance(&pos2) - 1.0).abs() < 1e-10);
879    }
880
881    #[test]
882    fn test_protein_folding_validation() {
883        let sequence = ProteinSequence::from_string("HPHH", "test".to_string())
884            .expect("Failed to create protein sequence");
885        let mut folding = ProteinFolding::new(sequence, LatticeType::Square2D);
886
887        // Set valid positions
888        folding
889            .set_position(0, LatticePosition::new(0, 0, 0))
890            .expect("Failed to set position 0");
891        folding
892            .set_position(1, LatticePosition::new(1, 0, 0))
893            .expect("Failed to set position 1");
894        folding
895            .set_position(2, LatticePosition::new(2, 0, 0))
896            .expect("Failed to set position 2");
897        folding
898            .set_position(3, LatticePosition::new(2, 1, 0))
899            .expect("Failed to set position 3");
900
901        assert!(folding.is_valid());
902    }
903
904    #[test]
905    fn test_hydrophobic_contacts() {
906        let sequence = ProteinSequence::from_string("HPHH", "test".to_string())
907            .expect("Failed to create protein sequence");
908        let mut folding = ProteinFolding::new(sequence, LatticeType::Square2D);
909
910        // Create L-shape with HH contact
911        folding
912            .set_position(0, LatticePosition::new(0, 0, 0))
913            .expect("Failed to set position 0"); // H
914        folding
915            .set_position(1, LatticePosition::new(1, 0, 0))
916            .expect("Failed to set position 1"); // P
917        folding
918            .set_position(2, LatticePosition::new(2, 0, 0))
919            .expect("Failed to set position 2"); // H
920        folding
921            .set_position(3, LatticePosition::new(2, 1, 0))
922            .expect("Failed to set position 3"); // H
923
924        // No HH contacts in this configuration
925        assert_eq!(folding.hydrophobic_contacts(), 0);
926
927        // Modify to create HH contact
928        folding
929            .set_position(3, LatticePosition::new(1, 1, 0))
930            .expect("Failed to update position 3"); // H adjacent to P
931                                                    // Still no HH contact between non-adjacent in sequence
932
933        // Create folded structure with HH contact
934        folding
935            .set_position(0, LatticePosition::new(0, 0, 0))
936            .expect("Failed to set folded position 0"); // H
937        folding
938            .set_position(1, LatticePosition::new(1, 0, 0))
939            .expect("Failed to set folded position 1"); // P
940        folding
941            .set_position(2, LatticePosition::new(1, 1, 0))
942            .expect("Failed to set folded position 2"); // H
943        folding
944            .set_position(3, LatticePosition::new(0, 1, 0))
945            .expect("Failed to set folded position 3"); // H
946
947        // Now positions 0 and 3 (both H) are adjacent
948        assert_eq!(folding.hydrophobic_contacts(), 1);
949    }
950
951    #[test]
952    fn test_problem_creation() {
953        let sequence = ProteinSequence::from_string("HPHPPHHPHH", "test".to_string())
954            .expect("Failed to create protein sequence");
955        let problem = ProteinFoldingProblem::new(sequence, LatticeType::Square2D);
956
957        assert!(problem.validate().is_ok());
958
959        let metrics = problem.size_metrics();
960        assert_eq!(metrics["sequence_length"], 10);
961    }
962
963    #[test]
964    fn test_qubo_conversion() {
965        let sequence = ProteinSequence::from_string("HPHH", "test".to_string())
966            .expect("Failed to create protein sequence");
967        let problem = ProteinFoldingProblem::new(sequence, LatticeType::Square2D);
968
969        let (qubo, variable_map) = problem
970            .to_qubo()
971            .expect("Failed to convert problem to QUBO");
972        assert_eq!(qubo.num_variables, 6); // (4-1) * 2 = 6 variables
973        assert!(!variable_map.is_empty());
974    }
975}