1use std::collections::{HashMap, HashSet, VecDeque};
20use std::fmt;
21
22use crate::advanced_quantum_algorithms::{
23 AdiabaticShortcutsOptimizer, AdvancedAlgorithmConfig, AdvancedQuantumAlgorithms,
24 AlgorithmSelectionStrategy, InfiniteDepthQAOA, InfiniteQAOAConfig, QuantumZenoAnnealer,
25 ShortcutsConfig, ZenoConfig,
26};
27use crate::applications::{
28 ApplicationError, ApplicationResult, IndustrySolution, OptimizationProblem,
29};
30use crate::bayesian_hyperopt::{optimize_annealing_parameters, BayesianHyperoptimizer};
31use crate::ising::{IsingModel, QuboModel};
32use crate::neural_annealing_schedules::{NeuralAnnealingScheduler, NeuralSchedulerConfig};
33use crate::quantum_error_correction::{
34 ErrorCorrectionCode, ErrorMitigationConfig, ErrorMitigationManager, LogicalAnnealingEncoder,
35 NoiseResilientAnnealingProtocol, SyndromeDetector,
36};
37use crate::simulator::{AnnealingParams, AnnealingResult, QuantumAnnealingSimulator};
38use std::fmt::Write;
39
40#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
42pub enum AtomType {
43 Hydrogen,
44 Carbon,
45 Nitrogen,
46 Oxygen,
47 Phosphorus,
48 Sulfur,
49 Fluorine,
50 Chlorine,
51 Bromine,
52 Iodine,
53 Custom(u8), }
55
56impl AtomType {
57 #[must_use]
58 pub const fn atomic_number(&self) -> u8 {
59 match self {
60 Self::Hydrogen => 1,
61 Self::Carbon => 6,
62 Self::Nitrogen => 7,
63 Self::Oxygen => 8,
64 Self::Phosphorus => 15,
65 Self::Sulfur => 16,
66 Self::Fluorine => 9,
67 Self::Chlorine => 17,
68 Self::Bromine => 35,
69 Self::Iodine => 53,
70 Self::Custom(n) => *n,
71 }
72 }
73
74 #[must_use]
75 pub const fn symbol(&self) -> &'static str {
76 match self {
77 Self::Hydrogen => "H",
78 Self::Carbon => "C",
79 Self::Nitrogen => "N",
80 Self::Oxygen => "O",
81 Self::Phosphorus => "P",
82 Self::Sulfur => "S",
83 Self::Fluorine => "F",
84 Self::Chlorine => "Cl",
85 Self::Bromine => "Br",
86 Self::Iodine => "I",
87 Self::Custom(_) => "X",
88 }
89 }
90
91 #[must_use]
92 pub const fn valence(&self) -> u8 {
93 match self {
94 Self::Hydrogen | Self::Fluorine | Self::Chlorine => 1,
95 Self::Carbon => 4,
96 Self::Nitrogen => 3,
97 Self::Oxygen => 2,
98 Self::Phosphorus => 5,
99 Self::Sulfur => 6,
100 Self::Bromine => 1,
101 Self::Iodine => 1,
102 Self::Custom(_) => 4, }
104 }
105}
106
107#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
109pub enum Hybridization {
110 SP,
111 SP2,
112 SP3,
113 SP3D,
114 SP3D2,
115}
116
117#[derive(Debug, Clone, Copy, PartialEq)]
119pub struct Position3D {
120 pub x: f64,
121 pub y: f64,
122 pub z: f64,
123}
124
125impl Position3D {
126 #[must_use]
127 pub const fn new(x: f64, y: f64, z: f64) -> Self {
128 Self { x, y, z }
129 }
130
131 #[must_use]
132 pub fn distance(&self, other: &Self) -> f64 {
133 (self.z - other.z)
134 .mul_add(
135 self.z - other.z,
136 (self.y - other.y).mul_add(self.y - other.y, (self.x - other.x).powi(2)),
137 )
138 .sqrt()
139 }
140}
141
142#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
144pub enum BondType {
145 Single,
146 Double,
147 Triple,
148 Aromatic,
149}
150
151impl BondType {
152 #[must_use]
153 pub const fn bond_order(&self) -> f64 {
154 match self {
155 Self::Single => 1.0,
156 Self::Double => 2.0,
157 Self::Triple => 3.0,
158 Self::Aromatic => 1.5,
159 }
160 }
161}
162
163#[derive(Debug, Clone)]
165pub struct Atom {
166 pub id: usize,
167 pub atom_type: AtomType,
168 pub formal_charge: i8,
169 pub hybridization: Option<String>,
170 pub aromatic: bool,
171 pub coordinates: Option<[f64; 3]>, }
173
174impl Atom {
175 #[must_use]
176 pub const fn new(id: usize, atom_type: AtomType) -> Self {
177 Self {
178 id,
179 atom_type,
180 formal_charge: 0,
181 hybridization: None,
182 aromatic: false,
183 coordinates: None,
184 }
185 }
186
187 #[must_use]
188 pub const fn with_charge(mut self, charge: i8) -> Self {
189 self.formal_charge = charge;
190 self
191 }
192
193 #[must_use]
194 pub const fn with_coordinates(mut self, coords: [f64; 3]) -> Self {
195 self.coordinates = Some(coords);
196 self
197 }
198
199 #[must_use]
200 pub const fn set_aromatic(mut self, aromatic: bool) -> Self {
201 self.aromatic = aromatic;
202 self
203 }
204}
205
206#[derive(Debug, Clone)]
208pub struct Bond {
209 pub atom1: usize,
210 pub atom2: usize,
211 pub bond_type: BondType,
212 pub aromatic: bool,
213 pub in_ring: bool,
214}
215
216impl Bond {
217 #[must_use]
218 pub const fn new(atom1: usize, atom2: usize, bond_type: BondType) -> Self {
219 Self {
220 atom1,
221 atom2,
222 bond_type,
223 aromatic: false,
224 in_ring: false,
225 }
226 }
227
228 #[must_use]
229 pub const fn set_aromatic(mut self, aromatic: bool) -> Self {
230 self.aromatic = aromatic;
231 self
232 }
233
234 #[must_use]
235 pub const fn set_in_ring(mut self, in_ring: bool) -> Self {
236 self.in_ring = in_ring;
237 self
238 }
239}
240
241#[derive(Debug, Clone)]
243pub struct Molecule {
244 pub id: String,
245 pub atoms: Vec<Atom>,
246 pub bonds: Vec<Bond>,
247 pub smiles: Option<String>,
248 pub properties: HashMap<String, f64>,
249}
250
251impl Molecule {
252 #[must_use]
253 pub fn new(id: String) -> Self {
254 Self {
255 id,
256 atoms: Vec::new(),
257 bonds: Vec::new(),
258 smiles: None,
259 properties: HashMap::new(),
260 }
261 }
262
263 pub fn add_atom(&mut self, atom: Atom) -> usize {
264 let id = self.atoms.len();
265 self.atoms.push(atom);
266 id
267 }
268
269 pub fn add_bond(&mut self, bond: Bond) {
270 self.bonds.push(bond);
271 }
272
273 pub fn set_smiles(&mut self, smiles: String) {
274 self.smiles = Some(smiles);
275 }
276
277 pub fn set_property(&mut self, name: String, value: f64) {
278 self.properties.insert(name, value);
279 }
280
281 #[must_use]
283 pub fn molecular_weight(&self) -> f64 {
284 self.atoms
285 .iter()
286 .map(|atom| {
287 match atom.atom_type {
288 AtomType::Hydrogen => 1.008,
289 AtomType::Carbon => 12.011,
290 AtomType::Nitrogen => 14.007,
291 AtomType::Oxygen => 15.999,
292 AtomType::Phosphorus => 30.974,
293 AtomType::Sulfur => 32.065,
294 AtomType::Fluorine => 18.998,
295 AtomType::Chlorine => 35.453,
296 AtomType::Bromine => 79.904,
297 AtomType::Iodine => 126.904,
298 AtomType::Custom(_) => 12.0, }
300 })
301 .sum()
302 }
303
304 #[must_use]
306 pub fn logp_approximation(&self) -> f64 {
307 let mut logp = 0.0;
308
309 for atom in &self.atoms {
311 match atom.atom_type {
312 AtomType::Carbon => logp += 0.5,
313 AtomType::Nitrogen => logp -= 0.5,
314 AtomType::Oxygen => logp -= 1.0,
315 AtomType::Sulfur => logp += 0.2,
316 AtomType::Fluorine => logp += 0.1,
317 AtomType::Chlorine => logp += 0.7,
318 AtomType::Bromine => logp += 1.0,
319 _ => {}
320 }
321 }
322
323 logp
324 }
325
326 #[must_use]
328 pub fn tpsa_approximation(&self) -> f64 {
329 let mut tpsa = 0.0;
330
331 for atom in &self.atoms {
332 match atom.atom_type {
333 AtomType::Nitrogen => tpsa += 23.79,
334 AtomType::Oxygen => tpsa += 23.06,
335 _ => {}
336 }
337 }
338
339 tpsa
340 }
341
342 #[must_use]
344 pub fn rotatable_bonds(&self) -> usize {
345 self.bonds
346 .iter()
347 .filter(|bond| {
348 bond.bond_type == BondType::Single && !bond.in_ring && !self.is_terminal_bond(bond)
349 })
350 .count()
351 }
352
353 fn is_terminal_bond(&self, bond: &Bond) -> bool {
354 let atom1_degree = self
355 .bonds
356 .iter()
357 .filter(|b| b.atom1 == bond.atom1 || b.atom2 == bond.atom2)
358 .count();
359 let atom2_degree = self
360 .bonds
361 .iter()
362 .filter(|b| b.atom1 == bond.atom1 || b.atom2 == bond.atom2)
363 .count();
364 atom1_degree == 1 || atom2_degree == 1
365 }
366
367 #[must_use]
369 pub fn drug_likeness_score(&self) -> f64 {
370 let mw = self.molecular_weight();
371 let logp = self.logp_approximation();
372 let tpsa = self.tpsa_approximation();
373 let rot_bonds = self.rotatable_bonds() as f64;
374
375 let mut score = 1.0;
376
377 if mw > 500.0 {
379 score *= 0.5;
380 }
381
382 if logp > 5.0 || logp < -2.0 {
384 score *= 0.5;
385 }
386
387 if tpsa > 140.0 {
389 score *= 0.7;
390 }
391
392 if rot_bonds > 10.0 {
394 score *= 0.8;
395 }
396
397 score
398 }
399}
400
401#[derive(Debug, Clone)]
403pub struct DrugTargetInteraction {
404 pub drug_molecule: Molecule,
405 pub target_id: String,
406 pub target_type: TargetType,
407 pub binding_affinity: Option<f64>, pub selectivity: HashMap<String, f64>, pub admet_properties: AdmetProperties,
410}
411
412#[derive(Debug, Clone, PartialEq, Eq)]
414pub enum TargetType {
415 Enzyme,
416 Receptor,
417 IonChannel,
418 Transporter,
419 StructuralProtein,
420 Other(String),
421}
422
423#[derive(Debug, Clone)]
425pub struct AdmetProperties {
426 pub absorption: AdsorptionProperties,
427 pub distribution: DistributionProperties,
428 pub metabolism: MetabolismProperties,
429 pub excretion: ExcretionProperties,
430 pub toxicity: ToxicityProperties,
431}
432
433#[derive(Debug, Clone)]
434pub struct AdsorptionProperties {
435 pub bioavailability: Option<f64>,
436 pub permeability: Option<f64>,
437 pub solubility: Option<f64>,
438}
439
440#[derive(Debug, Clone)]
441pub struct DistributionProperties {
442 pub volume_distribution: Option<f64>,
443 pub protein_binding: Option<f64>,
444 pub blood_brain_barrier: Option<f64>,
445}
446
447#[derive(Debug, Clone)]
448pub struct MetabolismProperties {
449 pub clearance: Option<f64>,
450 pub half_life: Option<f64>,
451 pub cyp_inhibition: HashMap<String, f64>,
452}
453
454#[derive(Debug, Clone)]
455pub struct ExcretionProperties {
456 pub renal_clearance: Option<f64>,
457 pub biliary_excretion: Option<f64>,
458}
459
460#[derive(Debug, Clone)]
461pub struct ToxicityProperties {
462 pub cytotoxicity: Option<f64>,
463 pub hepatotoxicity: Option<f64>,
464 pub cardiotoxicity: Option<f64>,
465 pub mutagenicity: Option<f64>,
466}
467
468impl Default for AdmetProperties {
469 fn default() -> Self {
470 Self {
471 absorption: AdsorptionProperties {
472 bioavailability: None,
473 permeability: None,
474 solubility: None,
475 },
476 distribution: DistributionProperties {
477 volume_distribution: None,
478 protein_binding: None,
479 blood_brain_barrier: None,
480 },
481 metabolism: MetabolismProperties {
482 clearance: None,
483 half_life: None,
484 cyp_inhibition: HashMap::new(),
485 },
486 excretion: ExcretionProperties {
487 renal_clearance: None,
488 biliary_excretion: None,
489 },
490 toxicity: ToxicityProperties {
491 cytotoxicity: None,
492 hepatotoxicity: None,
493 cardiotoxicity: None,
494 mutagenicity: None,
495 },
496 }
497 }
498}
499
500#[derive(Debug, Clone)]
502pub enum DrugOptimizationObjective {
503 MaximizeAffinity,
505 MinimizeOffTarget,
507 MaximizeDrugLikeness,
509 MinimizeToxicity,
511 MaximizeSynthesizability,
513 OptimizeAdmet,
515 MultiObjective(Vec<(Self, f64)>),
517}
518
519#[derive(Debug, Clone)]
521pub struct DrugDiscoveryProblem {
522 pub target_interaction: DrugTargetInteraction,
524 pub objectives: Vec<DrugOptimizationObjective>,
526 pub constraints: Vec<MolecularConstraint>,
528 pub qec_framework: Option<String>,
530 pub advanced_config: AdvancedAlgorithmConfig,
532 pub neural_config: Option<NeuralSchedulerConfig>,
534}
535
536#[derive(Debug, Clone)]
538pub enum MolecularConstraint {
539 MolecularWeightRange { min: f64, max: f64 },
541 LogPRange { min: f64, max: f64 },
543 TpsaLimit(f64),
545 MaxRotatableBonds(usize),
547 RequiredGroups(Vec<String>),
549 ForbiddenSubstructures(Vec<String>),
551 MinSynthesizability(f64),
553 MaxHeavyAtoms(usize),
555}
556
557impl DrugDiscoveryProblem {
558 #[must_use]
559 pub fn new(target_interaction: DrugTargetInteraction) -> Self {
560 Self {
561 target_interaction,
562 objectives: vec![DrugOptimizationObjective::MaximizeAffinity],
563 constraints: vec![],
564 qec_framework: None,
565 advanced_config: AdvancedAlgorithmConfig {
566 enable_infinite_qaoa: true,
567 enable_zeno_annealing: true,
568 enable_adiabatic_shortcuts: true,
569 enable_counterdiabatic: true,
570 selection_strategy: AlgorithmSelectionStrategy::ProblemSpecific,
571 track_performance: true,
572 },
573 neural_config: None,
574 }
575 }
576
577 #[must_use]
578 pub fn with_quantum_error_correction(mut self, config: String) -> Self {
579 self.qec_framework = Some(config);
580 self
581 }
582
583 #[must_use]
584 pub fn with_neural_annealing(mut self, config: NeuralSchedulerConfig) -> Self {
585 self.neural_config = Some(config);
586 self
587 }
588
589 #[must_use]
590 pub fn add_objective(mut self, objective: DrugOptimizationObjective) -> Self {
591 self.objectives.push(objective);
592 self
593 }
594
595 #[must_use]
596 pub fn add_constraint(mut self, constraint: MolecularConstraint) -> Self {
597 self.constraints.push(constraint);
598 self
599 }
600
601 pub fn solve_with_infinite_qaoa(&self) -> ApplicationResult<Molecule> {
603 println!("Starting drug discovery optimization with infinite-depth QAOA");
604
605 let (ising_model, variable_map) = self.to_ising_model()?;
606
607 let qaoa_config = InfiniteQAOAConfig {
608 max_depth: 50,
609 initial_depth: 3,
610 optimization_tolerance: 1e-8,
611 ..Default::default()
612 };
613
614 let mut qaoa = InfiniteDepthQAOA::new(qaoa_config);
615 let result = qaoa.solve(&ising_model).map_err(|e| {
616 ApplicationError::OptimizationError(format!("Infinite QAOA failed: {e:?}"))
617 })?;
618
619 let solution = result
620 .map_err(|e| ApplicationError::OptimizationError(format!("QAOA solver error: {e}")))?;
621
622 self.solution_to_molecule(&solution, &variable_map)
623 }
624
625 pub fn solve_with_zeno_annealing(&self) -> ApplicationResult<Molecule> {
627 println!("Starting drug discovery optimization with Quantum Zeno annealing");
628
629 let (ising_model, variable_map) = self.to_ising_model()?;
630
631 let zeno_config = ZenoConfig {
632 measurement_frequency: 2.0,
633 total_evolution_time: 20.0,
634 evolution_time_step: 0.05,
635 ..Default::default()
636 };
637
638 let mut zeno_annealer = QuantumZenoAnnealer::new(zeno_config);
639 let result = zeno_annealer.solve(&ising_model).map_err(|e| {
640 ApplicationError::OptimizationError(format!("Zeno annealing failed: {e:?}"))
641 })?;
642
643 let solution = result
644 .map_err(|e| ApplicationError::OptimizationError(format!("Zeno solver error: {e}")))?;
645
646 self.solution_to_molecule(&solution, &variable_map)
647 }
648
649 pub fn solve_with_adiabatic_shortcuts(&self) -> ApplicationResult<Molecule> {
651 println!("Starting drug discovery optimization with adiabatic shortcuts");
652
653 let (ising_model, variable_map) = self.to_ising_model()?;
654
655 let shortcuts_config = ShortcutsConfig::default();
656 let mut shortcuts_optimizer = AdiabaticShortcutsOptimizer::new(shortcuts_config);
657
658 let result = shortcuts_optimizer.solve(&ising_model).map_err(|e| {
659 ApplicationError::OptimizationError(format!("Adiabatic shortcuts failed: {e:?}"))
660 })?;
661
662 let solution = result.map_err(|e| {
663 ApplicationError::OptimizationError(format!("Shortcuts solver error: {e}"))
664 })?;
665
666 self.solution_to_molecule(&solution, &variable_map)
667 }
668
669 pub fn solve_with_error_correction(&self) -> ApplicationResult<Molecule> {
671 if let Some(ref qec_framework) = self.qec_framework {
672 println!("Starting noise-resilient drug discovery optimization");
673
674 let (ising_model, variable_map) = self.to_ising_model()?;
675
676 let error_config = ErrorMitigationConfig::default();
678 let mut error_manager = ErrorMitigationManager::new(error_config).map_err(|e| {
679 ApplicationError::OptimizationError(format!(
680 "Failed to create error manager: {e:?}"
681 ))
682 })?;
683
684 let params = AnnealingParams::default();
686 let annealer = QuantumAnnealingSimulator::new(params.clone()).map_err(|e| {
687 ApplicationError::OptimizationError(format!("Failed to create annealer: {e:?}"))
688 })?;
689 let annealing_result = annealer.solve(&ising_model).map_err(|e| {
690 ApplicationError::OptimizationError(format!("Annealing failed: {e:?}"))
691 })?;
692
693 let error_mitigation_result =
695 crate::quantum_error_correction::error_mitigation::AnnealingResult {
696 solution: annealing_result
697 .best_spins
698 .iter()
699 .map(|&x| i32::from(x))
700 .collect(),
701 energy: annealing_result.best_energy,
702 num_occurrences: 1,
703 chain_break_fraction: 0.0,
704 timing: std::collections::HashMap::new(),
705 info: std::collections::HashMap::new(),
706 };
707
708 let mitigation_result = error_manager
710 .apply_mitigation(&ising_model, error_mitigation_result, ¶ms)
711 .map_err(|e| {
712 ApplicationError::OptimizationError(format!("Error mitigation failed: {e:?}"))
713 })?;
714
715 let solution = &mitigation_result.mitigated_result.solution;
716
717 self.solution_to_molecule(solution, &variable_map)
718 } else {
719 Err(ApplicationError::InvalidConfiguration(
720 "Quantum error correction not enabled".to_string(),
721 ))
722 }
723 }
724
725 pub fn optimize_molecular_parameters(&self) -> ApplicationResult<HashMap<String, f64>> {
727 println!("Optimizing molecular parameters with Bayesian optimization");
728
729 let objective = |params: &[f64]| -> f64 {
730 let mw_target = params[0];
732 let logp_target = params[1];
733 let tpsa_target = params[2];
734
735 let current_mw = self.target_interaction.drug_molecule.molecular_weight();
736 let current_logp = self.target_interaction.drug_molecule.logp_approximation();
737 let current_tpsa = self.target_interaction.drug_molecule.tpsa_approximation();
738
739 let mw_score = -((current_mw - mw_target) / 100.0).powi(2);
741 let logp_score = -((current_logp - logp_target) / 2.0).powi(2);
742 let tpsa_score = -((current_tpsa - tpsa_target) / 50.0).powi(2);
743
744 let drug_likeness = self.target_interaction.drug_molecule.drug_likeness_score();
746
747 mw_score + logp_score + tpsa_score + drug_likeness
748 };
749
750 let best_params = optimize_annealing_parameters(objective, Some(40)).map_err(|e| {
751 ApplicationError::OptimizationError(format!("Bayesian optimization failed: {e:?}"))
752 })?;
753
754 let mut result = HashMap::new();
755 result.insert("optimal_molecular_weight".to_string(), best_params[0]);
756 result.insert("optimal_logp".to_string(), best_params[1]);
757 result.insert("optimal_tpsa".to_string(), best_params[2]);
758
759 Ok(result)
760 }
761
762 fn to_ising_model(&self) -> ApplicationResult<(IsingModel, HashMap<String, usize>)> {
764 let num_atoms = self.target_interaction.drug_molecule.atoms.len();
765 let num_variables = num_atoms * 8; let mut ising = IsingModel::new(num_variables);
768 let mut variable_map = HashMap::new();
769
770 for (i, atom) in self
772 .target_interaction
773 .drug_molecule
774 .atoms
775 .iter()
776 .enumerate()
777 {
778 for bit in 0..8 {
779 variable_map.insert(format!("atom_{i}_bit_{bit}"), i * 8 + bit);
780 }
781 }
782
783 for (i, atom) in self
785 .target_interaction
786 .drug_molecule
787 .atoms
788 .iter()
789 .enumerate()
790 {
791 for bit in 0..8 {
792 let var_idx = i * 8 + bit;
793 let mut bias = 0.0;
794
795 for objective in &self.objectives {
797 match objective {
798 DrugOptimizationObjective::MaximizeAffinity => {
799 match atom.atom_type {
801 AtomType::Nitrogen | AtomType::Oxygen => bias -= 0.5,
802 AtomType::Carbon => bias -= 0.2,
803 _ => bias += 0.1,
804 }
805 }
806 DrugOptimizationObjective::MaximizeDrugLikeness => {
807 bias -=
809 self.target_interaction.drug_molecule.drug_likeness_score() * 0.1;
810 }
811 DrugOptimizationObjective::MinimizeToxicity => {
812 if matches!(atom.atom_type, AtomType::Bromine | AtomType::Iodine) {
814 bias += 1.0;
815 }
816 }
817 _ => {
818 bias += 0.05; }
820 }
821 }
822
823 ising
824 .set_bias(var_idx, bias)
825 .map_err(|e| ApplicationError::OptimizationError(e.to_string()))?;
826 }
827 }
828
829 for bond in &self.target_interaction.drug_molecule.bonds {
831 let atom1_base = bond.atom1 * 8;
832 let atom2_base = bond.atom2 * 8;
833
834 for bit1 in 0..8 {
835 for bit2 in 0..8 {
836 let var1 = atom1_base + bit1;
837 let var2 = atom2_base + bit2;
838
839 if var1 < var2 && var1 < num_variables && var2 < num_variables {
840 let coupling = bond.bond_type.bond_order() * 0.1;
841 ising
842 .set_coupling(var1, var2, -coupling)
843 .map_err(|e| ApplicationError::OptimizationError(e.to_string()))?;
844 }
846 }
847 }
848 }
849
850 Ok((ising, variable_map))
851 }
852
853 fn solution_to_molecule(
855 &self,
856 solution: &[i32],
857 variable_map: &HashMap<String, usize>,
858 ) -> ApplicationResult<Molecule> {
859 let mut optimized_molecule = self.target_interaction.drug_molecule.clone();
860
861 for (i, atom) in optimized_molecule.atoms.iter_mut().enumerate() {
863 let mut atom_encoding = 0u8;
865
866 for bit in 0..8 {
867 if let Some(&var_index) = variable_map.get(&format!("atom_{i}_bit_{bit}")) {
868 if var_index < solution.len() && solution[var_index] > 0 {
869 atom_encoding |= 1 << bit;
870 }
871 }
872 }
873
874 atom.atom_type = match atom_encoding % 10 {
876 0 => AtomType::Carbon,
877 1 => AtomType::Nitrogen,
878 2 => AtomType::Oxygen,
879 3 => AtomType::Sulfur,
880 4 => AtomType::Phosphorus,
881 5 => AtomType::Fluorine,
882 6 => AtomType::Chlorine,
883 7 => AtomType::Hydrogen,
884 _ => atom.atom_type, };
886 }
887
888 optimized_molecule.set_property(
890 "molecular_weight".to_string(),
891 optimized_molecule.molecular_weight(),
892 );
893 optimized_molecule
894 .set_property("logp".to_string(), optimized_molecule.logp_approximation());
895 optimized_molecule
896 .set_property("tpsa".to_string(), optimized_molecule.tpsa_approximation());
897 optimized_molecule.set_property(
898 "drug_likeness".to_string(),
899 optimized_molecule.drug_likeness_score(),
900 );
901
902 Ok(optimized_molecule)
903 }
904}
905
906impl OptimizationProblem for DrugDiscoveryProblem {
907 type Solution = Molecule;
908 type ObjectiveValue = f64;
909
910 fn description(&self) -> String {
911 format!(
912 "Drug discovery optimization for {} targeting {} (MW: {:.2})",
913 self.target_interaction.drug_molecule.id,
914 self.target_interaction.target_id,
915 self.target_interaction.drug_molecule.molecular_weight()
916 )
917 }
918
919 fn size_metrics(&self) -> HashMap<String, usize> {
920 let mut metrics = HashMap::new();
921 metrics.insert(
922 "num_atoms".to_string(),
923 self.target_interaction.drug_molecule.atoms.len(),
924 );
925 metrics.insert(
926 "num_bonds".to_string(),
927 self.target_interaction.drug_molecule.bonds.len(),
928 );
929 metrics.insert(
930 "rotatable_bonds".to_string(),
931 self.target_interaction.drug_molecule.rotatable_bonds(),
932 );
933 metrics.insert(
934 "variables".to_string(),
935 self.target_interaction.drug_molecule.atoms.len() * 8,
936 );
937 metrics
938 }
939
940 fn validate(&self) -> ApplicationResult<()> {
941 if self.target_interaction.drug_molecule.atoms.is_empty() {
942 return Err(ApplicationError::DataValidationError(
943 "Molecule must have at least one atom".to_string(),
944 ));
945 }
946
947 if self.target_interaction.drug_molecule.atoms.len() > 100 {
948 return Err(ApplicationError::ResourceLimitExceeded(
949 "Molecule too large for current implementation".to_string(),
950 ));
951 }
952
953 for constraint in &self.constraints {
955 match constraint {
956 MolecularConstraint::MolecularWeightRange { min, max } => {
957 let mw = self.target_interaction.drug_molecule.molecular_weight();
958 if mw < *min || mw > *max {
959 return Err(ApplicationError::ConstraintViolation(format!(
960 "Molecular weight {mw} outside range [{min}, {max}]"
961 )));
962 }
963 }
964 MolecularConstraint::LogPRange { min, max } => {
965 let logp = self.target_interaction.drug_molecule.logp_approximation();
966 if logp < *min || logp > *max {
967 return Err(ApplicationError::ConstraintViolation(format!(
968 "LogP {logp} outside range [{min}, {max}]"
969 )));
970 }
971 }
972 MolecularConstraint::MaxHeavyAtoms(max_atoms) => {
973 let heavy_atoms = self
974 .target_interaction
975 .drug_molecule
976 .atoms
977 .iter()
978 .filter(|atom| atom.atom_type != AtomType::Hydrogen)
979 .count();
980 if heavy_atoms > *max_atoms {
981 return Err(ApplicationError::ConstraintViolation(format!(
982 "Heavy atom count {heavy_atoms} exceeds maximum {max_atoms}"
983 )));
984 }
985 }
986 _ => {
987 }
989 }
990 }
991
992 Ok(())
993 }
994
995 fn to_qubo(&self) -> ApplicationResult<(QuboModel, HashMap<String, usize>)> {
996 let (ising, variable_map) = self.to_ising_model()?;
998 let qubo = ising.to_qubo();
999 Ok((qubo, variable_map))
1000 }
1001
1002 fn evaluate_solution(
1003 &self,
1004 solution: &Self::Solution,
1005 ) -> ApplicationResult<Self::ObjectiveValue> {
1006 let mut total_score = 0.0;
1007
1008 for objective in &self.objectives {
1010 match objective {
1011 DrugOptimizationObjective::MaximizeAffinity => {
1012 total_score += solution.drug_likeness_score() * 10.0;
1014 }
1015 DrugOptimizationObjective::MaximizeDrugLikeness => {
1016 total_score += solution.drug_likeness_score() * 5.0;
1017 }
1018 DrugOptimizationObjective::MinimizeToxicity => {
1019 let toxic_penalty = solution
1021 .atoms
1022 .iter()
1023 .filter(|atom| {
1024 matches!(atom.atom_type, AtomType::Bromine | AtomType::Iodine)
1025 })
1026 .count() as f64;
1027 total_score -= toxic_penalty * 2.0;
1028 }
1029 _ => {
1030 total_score += 1.0; }
1032 }
1033 }
1034
1035 Ok(total_score)
1036 }
1037
1038 fn is_feasible(&self, solution: &Self::Solution) -> bool {
1039 for constraint in &self.constraints {
1041 match constraint {
1042 MolecularConstraint::MolecularWeightRange { min, max } => {
1043 let mw = solution.molecular_weight();
1044 if mw < *min || mw > *max {
1045 return false;
1046 }
1047 }
1048 MolecularConstraint::LogPRange { min, max } => {
1049 let logp = solution.logp_approximation();
1050 if logp < *min || logp > *max {
1051 return false;
1052 }
1053 }
1054 MolecularConstraint::MaxHeavyAtoms(max_atoms) => {
1055 let heavy_atoms = solution
1056 .atoms
1057 .iter()
1058 .filter(|atom| atom.atom_type != AtomType::Hydrogen)
1059 .count();
1060 if heavy_atoms > *max_atoms {
1061 return false;
1062 }
1063 }
1064 _ => {
1065 }
1067 }
1068 }
1069
1070 true
1071 }
1072}
1073
1074impl IndustrySolution for Molecule {
1075 type Problem = DrugDiscoveryProblem;
1076
1077 fn from_binary(problem: &Self::Problem, binary_solution: &[i8]) -> ApplicationResult<Self> {
1078 let solution_i32: Vec<i32> = binary_solution.iter().map(|&x| i32::from(x)).collect();
1079 let variable_map = HashMap::new(); problem.solution_to_molecule(&solution_i32, &variable_map)
1081 }
1082
1083 fn summary(&self) -> HashMap<String, String> {
1084 let mut summary = HashMap::new();
1085 summary.insert("molecule_id".to_string(), self.id.clone());
1086 summary.insert("num_atoms".to_string(), self.atoms.len().to_string());
1087 summary.insert("num_bonds".to_string(), self.bonds.len().to_string());
1088 summary.insert(
1089 "molecular_weight".to_string(),
1090 format!("{:.2}", self.molecular_weight()),
1091 );
1092 summary.insert(
1093 "logp".to_string(),
1094 format!("{:.2}", self.logp_approximation()),
1095 );
1096 summary.insert(
1097 "tpsa".to_string(),
1098 format!("{:.2}", self.tpsa_approximation()),
1099 );
1100 summary.insert(
1101 "rotatable_bonds".to_string(),
1102 self.rotatable_bonds().to_string(),
1103 );
1104 summary.insert(
1105 "drug_likeness".to_string(),
1106 format!("{:.3}", self.drug_likeness_score()),
1107 );
1108
1109 if let Some(ref smiles) = self.smiles {
1110 summary.insert("smiles".to_string(), smiles.clone());
1111 }
1112
1113 summary
1114 }
1115
1116 fn metrics(&self) -> HashMap<String, f64> {
1117 let mut metrics = HashMap::new();
1118 metrics.insert("molecular_weight".to_string(), self.molecular_weight());
1119 metrics.insert("logp".to_string(), self.logp_approximation());
1120 metrics.insert("tpsa".to_string(), self.tpsa_approximation());
1121 metrics.insert("rotatable_bonds".to_string(), self.rotatable_bonds() as f64);
1122 metrics.insert(
1123 "drug_likeness_score".to_string(),
1124 self.drug_likeness_score(),
1125 );
1126
1127 let heavy_atom_count = self
1128 .atoms
1129 .iter()
1130 .filter(|atom| atom.atom_type != AtomType::Hydrogen)
1131 .count() as f64;
1132 metrics.insert("heavy_atom_count".to_string(), heavy_atom_count);
1133
1134 for (key, value) in &self.properties {
1136 metrics.insert(key.clone(), *value);
1137 }
1138
1139 metrics
1140 }
1141
1142 fn export_format(&self) -> ApplicationResult<String> {
1143 let mut output = String::new();
1144
1145 output.push_str("# Drug Discovery Molecular Result\n");
1146 writeln!(output, "Molecule ID: {}", self.id).expect("Writing to String should not fail");
1147 write!(
1148 output,
1149 "Molecular Weight: {:.2} Da\n",
1150 self.molecular_weight()
1151 )
1152 .expect("Writing to String should not fail");
1153 write!(
1154 output,
1155 "LogP (Lipophilicity): {:.2}\n",
1156 self.logp_approximation()
1157 )
1158 .expect("Writing to String should not fail");
1159 write!(output, "TPSA: {:.2} Ų\n", self.tpsa_approximation())
1160 .expect("Writing to String should not fail");
1161 write!(output, "Rotatable Bonds: {}\n", self.rotatable_bonds())
1162 .expect("Writing to String should not fail");
1163 write!(
1164 output,
1165 "Drug-likeness Score: {:.3}\n",
1166 self.drug_likeness_score()
1167 )
1168 .expect("Writing to String should not fail");
1169
1170 if let Some(ref smiles) = self.smiles {
1171 writeln!(output, "SMILES: {smiles}").expect("Writing to String should not fail");
1172 }
1173
1174 output.push_str("\n# Atoms\n");
1175 for (i, atom) in self.atoms.iter().enumerate() {
1176 write!(
1177 output,
1178 "Atom {}: {} ({})",
1179 i,
1180 atom.atom_type.symbol(),
1181 atom.atom_type.atomic_number()
1182 )
1183 .expect("Writing to String should not fail");
1184 if atom.formal_charge != 0 {
1185 write!(output, " charge={}", atom.formal_charge)
1186 .expect("Writing to String should not fail");
1187 }
1188 if atom.aromatic {
1189 output.push_str(" aromatic");
1190 }
1191 if let Some(coords) = atom.coordinates {
1192 write!(
1193 output,
1194 " coords=({:.3}, {:.3}, {:.3})",
1195 coords[0], coords[1], coords[2]
1196 )
1197 .expect("Writing to String should not fail");
1198 }
1199 output.push('\n');
1200 }
1201
1202 output.push_str("\n# Bonds\n");
1203 for (i, bond) in self.bonds.iter().enumerate() {
1204 write!(
1205 output,
1206 "Bond {}: {} - {} ({:?})",
1207 i, bond.atom1, bond.atom2, bond.bond_type
1208 )
1209 .expect("Writing to String should not fail");
1210 if bond.aromatic {
1211 output.push_str(" aromatic");
1212 }
1213 if bond.in_ring {
1214 output.push_str(" in_ring");
1215 }
1216 output.push('\n');
1217 }
1218
1219 if !self.properties.is_empty() {
1220 output.push_str("\n# Properties\n");
1221 for (key, value) in &self.properties {
1222 writeln!(output, "{key}: {value:.6}").expect("Writing to String should not fail");
1223 }
1224 }
1225
1226 Ok(output)
1227 }
1228}
1229
1230pub fn create_benchmark_problems(
1232 size: usize,
1233) -> ApplicationResult<Vec<Box<dyn OptimizationProblem<Solution = Molecule, ObjectiveValue = f64>>>>
1234{
1235 let mut problems = Vec::new();
1236
1237 let molecule_specs = match size {
1239 s if s <= 10 => {
1240 vec![
1241 (
1242 "aspirin",
1243 vec![
1244 (AtomType::Carbon, 9),
1245 (AtomType::Hydrogen, 8),
1246 (AtomType::Oxygen, 4),
1247 ],
1248 ),
1249 (
1250 "caffeine",
1251 vec![
1252 (AtomType::Carbon, 8),
1253 (AtomType::Hydrogen, 10),
1254 (AtomType::Nitrogen, 4),
1255 (AtomType::Oxygen, 2),
1256 ],
1257 ),
1258 ]
1259 }
1260 s if s <= 25 => {
1261 vec![
1262 (
1263 "ibuprofen",
1264 vec![
1265 (AtomType::Carbon, 13),
1266 (AtomType::Hydrogen, 18),
1267 (AtomType::Oxygen, 2),
1268 ],
1269 ),
1270 (
1271 "acetaminophen",
1272 vec![
1273 (AtomType::Carbon, 8),
1274 (AtomType::Hydrogen, 9),
1275 (AtomType::Nitrogen, 1),
1276 (AtomType::Oxygen, 2),
1277 ],
1278 ),
1279 (
1280 "penicillin",
1281 vec![
1282 (AtomType::Carbon, 16),
1283 (AtomType::Hydrogen, 18),
1284 (AtomType::Nitrogen, 2),
1285 (AtomType::Oxygen, 4),
1286 (AtomType::Sulfur, 1),
1287 ],
1288 ),
1289 ]
1290 }
1291 _ => {
1292 vec![
1293 (
1294 "vancomycin",
1295 vec![
1296 (AtomType::Carbon, 66),
1297 (AtomType::Hydrogen, 75),
1298 (AtomType::Chlorine, 2),
1299 (AtomType::Nitrogen, 9),
1300 (AtomType::Oxygen, 24),
1301 ],
1302 ),
1303 (
1304 "doxorubicin",
1305 vec![
1306 (AtomType::Carbon, 27),
1307 (AtomType::Hydrogen, 29),
1308 (AtomType::Nitrogen, 1),
1309 (AtomType::Oxygen, 11),
1310 ],
1311 ),
1312 ]
1313 }
1314 };
1315
1316 for (i, (name, atom_composition)) in molecule_specs.iter().enumerate() {
1317 let mut molecule = Molecule::new(format!("benchmark_{name}"));
1318
1319 let mut atom_id = 0;
1321 for (atom_type, count) in atom_composition {
1322 for _ in 0..*count {
1323 let atom = Atom::new(atom_id, *atom_type);
1324 molecule.add_atom(atom);
1325 atom_id += 1;
1326 }
1327 }
1328
1329 for j in 0..(molecule.atoms.len() - 1) {
1331 let bond = Bond::new(j, j + 1, BondType::Single);
1332 molecule.add_bond(bond);
1333 }
1334
1335 let target_interaction = DrugTargetInteraction {
1337 drug_molecule: molecule,
1338 target_id: format!("target_{i}"),
1339 target_type: TargetType::Enzyme,
1340 binding_affinity: Some(6.5), selectivity: HashMap::new(),
1342 admet_properties: AdmetProperties::default(),
1343 };
1344
1345 let mut problem = DrugDiscoveryProblem::new(target_interaction);
1346
1347 match i % 3 {
1349 0 => problem = problem.add_objective(DrugOptimizationObjective::MaximizeAffinity),
1350 1 => problem = problem.add_objective(DrugOptimizationObjective::MaximizeDrugLikeness),
1351 2 => problem = problem.add_objective(DrugOptimizationObjective::MinimizeToxicity),
1352 _ => problem = problem.add_objective(DrugOptimizationObjective::OptimizeAdmet),
1353 }
1354
1355 problem = problem.add_constraint(MolecularConstraint::MolecularWeightRange {
1357 min: 100.0,
1358 max: 800.0,
1359 });
1360 problem = problem.add_constraint(MolecularConstraint::LogPRange {
1361 min: -2.0,
1362 max: 5.0,
1363 });
1364
1365 problems.push(Box::new(problem)
1367 as Box<
1368 dyn OptimizationProblem<Solution = Molecule, ObjectiveValue = f64>,
1369 >);
1370 }
1371
1372 Ok(problems)
1373}
1374
1375#[cfg(test)]
1376mod tests {
1377 use super::*;
1378
1379 #[test]
1380 fn test_atom_creation() {
1381 let atom = Atom::new(0, AtomType::Carbon)
1382 .with_charge(-1)
1383 .with_coordinates([1.0, 2.0, 3.0])
1384 .set_aromatic(true);
1385
1386 assert_eq!(atom.id, 0);
1387 assert_eq!(atom.atom_type, AtomType::Carbon);
1388 assert_eq!(atom.formal_charge, -1);
1389 assert_eq!(atom.coordinates, Some([1.0, 2.0, 3.0]));
1390 assert!(atom.aromatic);
1391 }
1392
1393 #[test]
1394 fn test_molecule_properties() {
1395 let mut molecule = Molecule::new("test".to_string());
1396
1397 molecule.add_atom(Atom::new(0, AtomType::Carbon));
1399 molecule.add_atom(Atom::new(1, AtomType::Hydrogen));
1400 molecule.add_atom(Atom::new(2, AtomType::Oxygen));
1401
1402 assert_eq!(molecule.atoms.len(), 3);
1403 assert!(molecule.molecular_weight() > 0.0);
1404
1405 let drug_likeness = molecule.drug_likeness_score();
1406 assert!(drug_likeness > 0.0 && drug_likeness <= 1.0);
1407 }
1408
1409 #[test]
1410 fn test_atom_type_properties() {
1411 assert_eq!(AtomType::Carbon.atomic_number(), 6);
1412 assert_eq!(AtomType::Carbon.symbol(), "C");
1413 assert_eq!(AtomType::Carbon.valence(), 4);
1414
1415 assert_eq!(AtomType::Nitrogen.atomic_number(), 7);
1416 assert_eq!(AtomType::Oxygen.valence(), 2);
1417 }
1418
1419 #[test]
1420 fn test_bond_properties() {
1421 let bond = Bond::new(0, 1, BondType::Double)
1422 .set_aromatic(true)
1423 .set_in_ring(true);
1424
1425 assert_eq!(bond.atom1, 0);
1426 assert_eq!(bond.atom2, 1);
1427 assert_eq!(bond.bond_type, BondType::Double);
1428 assert_eq!(bond.bond_type.bond_order(), 2.0);
1429 assert!(bond.aromatic);
1430 assert!(bond.in_ring);
1431 }
1432
1433 #[test]
1434 fn test_molecular_calculations() {
1435 let mut molecule = Molecule::new("ethanol".to_string());
1436
1437 molecule.add_atom(Atom::new(0, AtomType::Carbon));
1439 molecule.add_atom(Atom::new(1, AtomType::Carbon));
1440 molecule.add_atom(Atom::new(2, AtomType::Oxygen));
1441 for i in 3..9 {
1442 molecule.add_atom(Atom::new(i, AtomType::Hydrogen));
1443 }
1444
1445 molecule.add_bond(Bond::new(0, 1, BondType::Single));
1447 molecule.add_bond(Bond::new(1, 2, BondType::Single));
1448
1449 let mw = molecule.molecular_weight();
1450 assert!(mw > 40.0 && mw < 50.0); let logp = molecule.logp_approximation();
1453 assert!(logp > -2.0 && logp < 2.0); }
1455
1456 #[test]
1457 fn test_drug_discovery_problem() {
1458 let mut molecule = Molecule::new("test_drug".to_string());
1459 molecule.add_atom(Atom::new(0, AtomType::Carbon));
1460 molecule.add_atom(Atom::new(1, AtomType::Nitrogen));
1461
1462 let target_interaction = DrugTargetInteraction {
1463 drug_molecule: molecule,
1464 target_id: "test_target".to_string(),
1465 target_type: TargetType::Enzyme,
1466 binding_affinity: Some(7.0),
1467 selectivity: HashMap::new(),
1468 admet_properties: AdmetProperties::default(),
1469 };
1470
1471 let problem = DrugDiscoveryProblem::new(target_interaction)
1472 .add_objective(DrugOptimizationObjective::MaximizeAffinity)
1473 .add_constraint(MolecularConstraint::MolecularWeightRange {
1474 min: 20.0,
1475 max: 500.0,
1476 });
1477
1478 assert!(problem.validate().is_ok());
1479
1480 let metrics = problem.size_metrics();
1481 assert_eq!(metrics["num_atoms"], 2);
1482 }
1483
1484 #[test]
1485 fn test_ising_conversion() {
1486 let mut molecule = Molecule::new("test".to_string());
1487 molecule.add_atom(Atom::new(0, AtomType::Carbon));
1488 molecule.add_atom(Atom::new(1, AtomType::Nitrogen));
1489
1490 let target_interaction = DrugTargetInteraction {
1491 drug_molecule: molecule,
1492 target_id: "test".to_string(),
1493 target_type: TargetType::Enzyme,
1494 binding_affinity: None,
1495 selectivity: HashMap::new(),
1496 admet_properties: AdmetProperties::default(),
1497 };
1498
1499 let problem = DrugDiscoveryProblem::new(target_interaction);
1500 let (ising, variable_map) = problem
1501 .to_ising_model()
1502 .expect("should convert to Ising model");
1503
1504 assert_eq!(ising.num_qubits, 16); assert!(!variable_map.is_empty());
1506 }
1507}