1use super::*;
2
3pub struct PropertyPredictor {
5 property_models: HashMap<String, PropertyModel>,
6 descriptor_calculator: DescriptorCalculator,
7 machine_learning_models: HashMap<String, MLModel>,
8}
9
10#[derive(Debug, Clone)]
12pub struct PropertyModel {
13 pub model_id: String,
14 pub property_type: PropertyType,
15 pub model_type: PropertyModelType,
16 pub parameters: PropertyModelParameters,
17}
18
19#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
21pub enum PropertyType {
22 BoilingPoint,
23 MeltingPoint,
24 Density,
25 Viscosity,
26 SurfaceTension,
27 HeatCapacity,
28 ThermalConductivity,
29 ElectricalConductivity,
30 OpticalProperties,
31 MagneticProperties,
32}
33
34#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
36pub enum PropertyModelType {
37 GroupContribution,
38 QSPR,
39 MachineLearning,
40 MolecularDynamics,
41 QuantumMechanical,
42}
43
44#[derive(Debug, Clone, Serialize, Deserialize)]
46pub struct PropertyModelParameters {
47 pub coefficients: HashMap<String, f64>,
48 pub descriptors: Vec<String>,
49 pub reference_data: Vec<ReferenceData>,
50}
51
52#[derive(Debug, Clone, Serialize, Deserialize)]
54pub struct ReferenceData {
55 pub molecule_id: String,
56 pub property_value: f64,
57 pub conditions: ReferenceConditions,
58}
59
60#[derive(Debug, Clone, Serialize, Deserialize)]
62pub struct ReferenceConditions {
63 pub temperature: f64,
64 pub pressure: f64,
65 pub phase: PhaseType,
66}
67
68pub struct DescriptorCalculator {
70 molecular_descriptors: MolecularDescriptors,
71 quantum_descriptors: QuantumDescriptors,
72 topological_descriptors: TopologicalDescriptors,
73}
74
75#[derive(Debug, Clone)]
77pub struct MolecularDescriptors {
78 pub molecular_weight: f64,
79 pub formula: String,
80 pub atom_count: HashMap<String, usize>,
81 pub bond_count: HashMap<String, usize>,
82 pub ring_count: usize,
83}
84
85#[derive(Debug, Clone)]
87pub struct QuantumDescriptors {
88 pub homo_energy: f64,
89 pub lumo_energy: f64,
90 pub gap: f64,
91 pub dipole_moment: f64,
92 pub polarizability: f64,
93}
94
95#[derive(Debug, Clone)]
97pub struct TopologicalDescriptors {
98 pub connectivity_index: f64,
99 pub shape_index: f64,
100 pub wiener_index: f64,
101 pub randic_index: f64,
102}
103
104#[derive(Debug, Clone)]
106pub struct MLModel {
107 pub model_id: String,
108 pub model_type: MLModelType,
109 pub model_parameters: MLModelParameters,
110 pub training_data: TrainingData,
111}
112
113#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
115pub enum MLModelType {
116 LinearRegression,
117 RandomForest,
118 NeuralNetwork,
119 SupportVector,
120 GaussianProcess,
121}
122
123#[derive(Debug, Clone, Serialize, Deserialize)]
125pub struct MLModelParameters {
126 pub hyperparameters: HashMap<String, f64>,
127 pub feature_importance: HashMap<String, f64>,
128 pub model_performance: ModelPerformance,
129}
130
131#[derive(Debug, Clone, Serialize, Deserialize)]
133pub struct ModelPerformance {
134 pub r_squared: f64,
135 pub rmse: f64,
136 pub mae: f64,
137 pub cross_validation_score: f64,
138}
139
140#[derive(Debug, Clone)]
142pub struct TrainingData {
143 pub data_id: String,
144 pub features: Vec<Vec<f64>>,
145 pub targets: Vec<f64>,
146 pub data_size: usize,
147}
148
149impl PropertyPredictor {
150 pub fn new() -> Self {
151 Self {
152 property_models: HashMap::new(),
153 descriptor_calculator: DescriptorCalculator::new(),
154 machine_learning_models: HashMap::new(),
155 }
156 }
157
158 pub fn initialize(&mut self) -> Result<(), ChemistryError> {
159 self.descriptor_calculator.initialize()?;
160 self.register_standard_qspr_models();
166 Ok(())
167 }
168
169 fn register_standard_qspr_models(&mut self) {
171 self.register_model(
174 "boiling_point",
175 PropertyModel {
176 model_id: "qspr_boiling_point".to_string(),
177 property_type: PropertyType::BoilingPoint,
178 model_type: PropertyModelType::GroupContribution,
179 parameters: PropertyModelParameters {
180 coefficients: {
181 let mut c = HashMap::new();
182 c.insert("intercept".to_string(), 198.2);
183 c.insert("C".to_string(), 23.97);
184 c.insert("H".to_string(), 22.88);
185 c.insert("O".to_string(), 10.0);
186 c.insert("N".to_string(), 5.0);
187 c.insert("ring".to_string(), -50.0);
188 c
189 },
190 descriptors: vec![
191 "C".to_string(),
192 "H".to_string(),
193 "O".to_string(),
194 "N".to_string(),
195 "ring".to_string(),
196 ],
197 reference_data: Vec::new(),
198 },
199 },
200 );
201
202 self.register_model(
205 "melting_point",
206 PropertyModel {
207 model_id: "qspr_melting_point".to_string(),
208 property_type: PropertyType::MeltingPoint,
209 model_type: PropertyModelType::GroupContribution,
210 parameters: PropertyModelParameters {
211 coefficients: {
212 let mut c = HashMap::new();
213 c.insert("intercept".to_string(), 122.5);
214 c.insert("C".to_string(), -5.51);
215 c.insert("H".to_string(), 8.45);
216 c.insert("O".to_string(), 4.0);
217 c.insert("N".to_string(), 2.5);
218 c.insert("ring".to_string(), -20.0);
219 c
220 },
221 descriptors: vec![
222 "C".to_string(),
223 "H".to_string(),
224 "O".to_string(),
225 "N".to_string(),
226 "ring".to_string(),
227 ],
228 reference_data: Vec::new(),
229 },
230 },
231 );
232
233 self.register_model(
236 "solubility",
237 PropertyModel {
238 model_id: "qspr_solubility".to_string(),
239 property_type: PropertyType::Density, model_type: PropertyModelType::GroupContribution,
241 parameters: PropertyModelParameters {
242 coefficients: {
243 let mut c = HashMap::new();
244 c.insert("intercept".to_string(), 0.5);
245 c.insert("logP".to_string(), -0.5);
246 c.insert("molecular_weight".to_string(), -0.01);
247 c
248 },
249 descriptors: vec!["logP".to_string(), "molecular_weight".to_string()],
250 reference_data: Vec::new(),
251 },
252 },
253 );
254
255 self.register_model(
258 "molecular_weight",
259 PropertyModel {
260 model_id: "qspr_molecular_weight".to_string(),
261 property_type: PropertyType::Density, model_type: PropertyModelType::GroupContribution,
263 parameters: PropertyModelParameters {
264 coefficients: {
265 let mut c = HashMap::new();
266 c.insert("intercept".to_string(), 0.0);
267 c.insert("C".to_string(), 12.011);
268 c.insert("H".to_string(), 1.008);
269 c.insert("O".to_string(), 15.999);
270 c.insert("N".to_string(), 14.007);
271 c.insert("S".to_string(), 32.06);
272 c.insert("Cl".to_string(), 35.45);
273 c
274 },
275 descriptors: vec![
276 "C".to_string(),
277 "H".to_string(),
278 "O".to_string(),
279 "N".to_string(),
280 "S".to_string(),
281 "Cl".to_string(),
282 ],
283 reference_data: Vec::new(),
284 },
285 },
286 );
287 }
288
289 pub fn validate_molecule(&self, molecule: &Molecule) -> Result<(), ChemistryError> {
290 if molecule.atoms.is_empty() {
291 return Err(ChemistryError::ValidationError(
292 "Molecule must have at least one atom".to_string(),
293 ));
294 }
295 Ok(())
296 }
297
298 pub fn predict(
307 &self,
308 property_name: &str,
309 molecular_descriptors: &HashMap<String, f64>,
310 ) -> Result<f64, ChemistryError> {
311 let model = self.property_models.get(property_name).ok_or_else(|| {
312 ChemistryError::NotImplemented(format!(
313 "no QSPR model registered for property '{}'",
314 property_name
315 ))
316 })?;
317
318 let mut predicted = 0.0;
319 for (descriptor, coefficient) in &model.parameters.coefficients {
320 if descriptor == "intercept" {
321 predicted += coefficient;
322 } else {
323 let value = molecular_descriptors
324 .get(descriptor)
325 .copied()
326 .unwrap_or(0.0);
327 predicted += coefficient * value;
328 }
329 }
330 Ok(predicted)
331 }
332
333 pub fn predict_from_molecule(
340 &self,
341 molecule: &Molecule,
342 properties: &[PropertyType],
343 ) -> Result<PredictedProperties, ChemistryError> {
344 let mut descriptors: HashMap<String, f64> = HashMap::new();
346 let mut molecular_weight = 0.0;
347 let mut atom_counts: HashMap<String, f64> = HashMap::new();
348 for atom in &molecule.atoms {
349 molecular_weight += atom.mass;
350 *atom_counts.entry(atom.element.clone()).or_insert(0.0) += 1.0;
351 }
352 descriptors.insert("molecular_weight".to_string(), molecular_weight);
353 for (element, count) in &atom_counts {
354 descriptors.insert(element.clone(), *count);
355 }
356 descriptors.insert("logP".to_string(), 0.0);
359
360 let mut result = PredictedProperties::new();
361 for property_type in properties {
362 let name = match property_type {
363 PropertyType::BoilingPoint => "boiling_point",
364 PropertyType::MeltingPoint => "melting_point",
365 _ => continue,
367 };
368 match self.predict(name, &descriptors) {
369 Ok(value) => {
370 result.properties.insert(name.to_string(), value);
371 }
372 Err(ChemistryError::NotImplemented(_)) => continue,
373 Err(e) => return Err(e),
374 }
375 }
376
377 if result.properties.is_empty() {
378 return Err(ChemistryError::NotImplemented(
379 "no QSPR models available for the requested property types".to_string(),
380 ));
381 }
382 Ok(result)
383 }
384
385 pub fn register_model(&mut self, name: &str, model: PropertyModel) {
388 self.property_models.insert(name.to_string(), model);
389 }
390
391 pub fn list_properties(&self) -> Vec<String> {
393 self.property_models.keys().cloned().collect()
394 }
395
396 pub fn register_ml_model(&mut self, model: MLModel) {
399 self.machine_learning_models
400 .insert(model.model_id.clone(), model);
401 }
402
403 pub fn get_ml_model(&self, model_id: &str) -> Option<&MLModel> {
405 self.machine_learning_models.get(model_id)
406 }
407
408 pub fn get_ml_model_mut(&mut self, model_id: &str) -> Option<&mut MLModel> {
410 self.machine_learning_models.get_mut(model_id)
411 }
412
413 pub fn list_ml_models(&self) -> Vec<String> {
415 self.machine_learning_models.keys().cloned().collect()
416 }
417
418 pub fn remove_ml_model(&mut self, model_id: &str) -> Option<MLModel> {
420 self.machine_learning_models.remove(model_id)
421 }
422}
423
424impl PropertyModel {
425 pub fn new() -> Self {
426 Self {
427 model_id: "model_1".to_string(),
428 property_type: PropertyType::BoilingPoint,
429 model_type: PropertyModelType::GroupContribution,
430 parameters: PropertyModelParameters::new(),
431 }
432 }
433}
434
435impl PropertyModelParameters {
436 pub fn new() -> Self {
437 Self {
438 coefficients: HashMap::new(),
439 descriptors: vec!["molecular_weight".to_string()],
440 reference_data: vec![ReferenceData::new()],
441 }
442 }
443}
444
445impl ReferenceData {
446 pub fn new() -> Self {
447 Self {
448 molecule_id: "mol_1".to_string(),
449 property_value: 100.0,
450 conditions: ReferenceConditions::new(),
451 }
452 }
453}
454
455impl ReferenceConditions {
456 pub fn new() -> Self {
457 Self {
458 temperature: 298.15,
459 pressure: 1.0,
460 phase: PhaseType::Liquid,
461 }
462 }
463}
464
465impl DescriptorCalculator {
466 pub fn new() -> Self {
467 Self {
468 molecular_descriptors: MolecularDescriptors::new(),
469 quantum_descriptors: QuantumDescriptors::new(),
470 topological_descriptors: TopologicalDescriptors::new(),
471 }
472 }
473
474 pub fn molecular_descriptors(&self) -> &MolecularDescriptors {
476 &self.molecular_descriptors
477 }
478
479 pub fn molecular_descriptors_mut(&mut self) -> &mut MolecularDescriptors {
481 &mut self.molecular_descriptors
482 }
483
484 pub fn quantum_descriptors(&self) -> &QuantumDescriptors {
486 &self.quantum_descriptors
487 }
488
489 pub fn quantum_descriptors_mut(&mut self) -> &mut QuantumDescriptors {
491 &mut self.quantum_descriptors
492 }
493
494 pub fn topological_descriptors(&self) -> &TopologicalDescriptors {
496 &self.topological_descriptors
497 }
498
499 pub fn topological_descriptors_mut(&mut self) -> &mut TopologicalDescriptors {
501 &mut self.topological_descriptors
502 }
503
504 pub fn initialize(&mut self) -> Result<(), ChemistryError> {
505 Ok(())
506 }
507}
508
509impl MolecularDescriptors {
510 pub fn new() -> Self {
511 Self {
512 molecular_weight: 16.04,
513 formula: "CH4".to_string(),
514 atom_count: HashMap::new(),
515 bond_count: HashMap::new(),
516 ring_count: 0,
517 }
518 }
519}
520
521impl QuantumDescriptors {
522 pub fn new() -> Self {
523 Self {
524 homo_energy: -13.6,
525 lumo_energy: 0.0,
526 gap: 13.6,
527 dipole_moment: 0.0,
528 polarizability: 0.0,
529 }
530 }
531}
532
533impl TopologicalDescriptors {
534 pub fn new() -> Self {
535 Self {
536 connectivity_index: 1.0,
537 shape_index: 1.0,
538 wiener_index: 1.0,
539 randic_index: 1.0,
540 }
541 }
542}
543
544impl MLModel {
545 pub fn new() -> Self {
546 Self {
547 model_id: "ml_1".to_string(),
548 model_type: MLModelType::LinearRegression,
549 model_parameters: MLModelParameters::new(),
550 training_data: TrainingData::new(),
551 }
552 }
553}
554
555impl MLModelParameters {
556 pub fn new() -> Self {
557 Self {
558 hyperparameters: HashMap::new(),
559 feature_importance: HashMap::new(),
560 model_performance: ModelPerformance::new(),
561 }
562 }
563}
564
565impl ModelPerformance {
566 pub fn new() -> Self {
567 Self {
568 r_squared: 0.95,
569 rmse: 0.1,
570 mae: 0.08,
571 cross_validation_score: 0.0, }
573 }
574}
575
576impl TrainingData {
577 pub fn new() -> Self {
578 Self {
579 data_id: "data_1".to_string(),
580 features: vec![vec![1.0; 10]; 100],
581 targets: vec![100.0; 100],
582 data_size: 100,
583 }
584 }
585}