1use crate::classification::{ClassificationMetrics, Classifier};
8use crate::error::{MLError, Result};
9use crate::qnn::QuantumNeuralNetwork;
10use quantrs2_circuit::prelude::Circuit;
11use quantrs2_sim::statevector::StateVectorSimulator;
12use scirs2_core::ndarray::{Array1, Array2};
13use scirs2_core::random::prelude::*;
14use std::fmt;
15
16#[derive(Debug, Clone, Copy)]
18pub enum HEPEncodingMethod {
19 AmplitudeEncoding,
21
22 AngleEncoding,
24
25 BasisEncoding,
27
28 HybridEncoding,
30}
31
32#[derive(Debug, Clone, Copy, PartialEq)]
34pub enum ParticleType {
35 Photon,
37
38 Electron,
40
41 Muon,
43
44 Tau,
46
47 Neutrino,
49
50 Quark,
52
53 Higgs,
55
56 WBoson,
58
59 ZBoson,
61
62 Other,
64}
65
66#[derive(Debug, Clone)]
68pub struct ParticleFeatures {
69 pub particle_type: ParticleType,
71
72 pub four_momentum: [f64; 4],
74
75 pub additional_features: Vec<f64>,
77}
78
79#[derive(Debug, Clone)]
81pub struct CollisionEvent {
82 pub particles: Vec<ParticleFeatures>,
84
85 pub global_features: Vec<f64>,
87
88 pub event_type: Option<String>,
90}
91
92#[derive(Debug, Clone)]
94pub struct HEPQuantumClassifier {
95 pub qnn: QuantumNeuralNetwork,
97
98 pub feature_dimension: usize,
100
101 pub encoding_method: HEPEncodingMethod,
103
104 pub class_labels: Vec<String>,
106}
107
108impl HEPQuantumClassifier {
109 pub fn train_on_particles(
111 &mut self,
112 particles: &[ParticleFeatures],
113 labels: &[usize],
114 epochs: usize,
115 learning_rate: f64,
116 ) -> Result<crate::qnn::TrainingResult> {
117 let num_samples = particles.len();
119 let mut features = Array2::zeros((num_samples, self.feature_dimension));
120
121 for (i, particle) in particles.iter().enumerate() {
122 let particle_features = self.extract_features(particle)?;
123 for j in 0..particle_features.len() {
124 features[[i, j]] = particle_features[j];
125 }
126 }
127
128 let y_train = Array1::from_vec(labels.iter().map(|&l| l as f64).collect());
130
131 self.train(&features, &y_train, epochs, learning_rate)
133 }
134
135 pub fn classify_event(&self, event: &CollisionEvent) -> Result<Vec<(String, f64)>> {
137 let mut results = Vec::new();
138
139 for particle in &event.particles {
141 let features = self.extract_features(particle)?;
142 let (class_name, confidence) = self.predict(&features)?;
144 results.push((class_name, confidence));
145 }
146
147 Ok(results)
148 }
149
150 pub fn new(
152 num_qubits: usize,
153 feature_dim: usize,
154 num_classes: usize,
155 encoding_method: HEPEncodingMethod,
156 class_labels: Vec<String>,
157 ) -> Result<Self> {
158 let layers = vec![
160 crate::qnn::QNNLayerType::EncodingLayer {
161 num_features: feature_dim,
162 },
163 crate::qnn::QNNLayerType::VariationalLayer {
164 num_params: 2 * num_qubits,
165 },
166 crate::qnn::QNNLayerType::EntanglementLayer {
167 connectivity: "full".to_string(),
168 },
169 crate::qnn::QNNLayerType::VariationalLayer {
170 num_params: 2 * num_qubits,
171 },
172 crate::qnn::QNNLayerType::MeasurementLayer {
173 measurement_basis: "computational".to_string(),
174 },
175 ];
176
177 let qnn = QuantumNeuralNetwork::new(layers, num_qubits, feature_dim, num_classes)?;
178
179 Ok(HEPQuantumClassifier {
180 qnn,
181 feature_dimension: feature_dim,
182 encoding_method,
183 class_labels,
184 })
185 }
186
187 pub fn extract_features(&self, particle: &ParticleFeatures) -> Result<Array1<f64>> {
189 let mut features = Array1::zeros(self.feature_dimension);
191
192 if self.feature_dimension >= 4 {
194 for i in 0..4 {
195 features[i] = particle.four_momentum[i];
196 }
197 }
198
199 let additional_count = self.feature_dimension.saturating_sub(4);
201 for i in 0..additional_count.min(particle.additional_features.len()) {
202 features[i + 4] = particle.additional_features[i];
203 }
204
205 let norm = features.fold(0.0, |acc, &x| acc + x * x).sqrt();
207 if norm > 0.0 {
208 features.mapv_inplace(|x| x / norm);
209 }
210
211 Ok(features)
212 }
213
214 pub fn classify_particle(&self, particle: &ParticleFeatures) -> Result<(String, f64)> {
216 let features = self.extract_features(particle)?;
217
218 let prediction = if particle.particle_type == ParticleType::Higgs {
220 1
221 } else {
222 0
223 };
224
225 let confidence = 0.85;
226
227 if prediction < self.class_labels.len() {
228 Ok((self.class_labels[prediction].clone(), confidence))
229 } else {
230 Err(MLError::MLOperationError(format!(
231 "Invalid prediction index: {}",
232 prediction
233 )))
234 }
235 }
236
237 pub fn extract_event_features(&self, event: &CollisionEvent) -> Result<Array1<f64>> {
239 let mut features = Array1::zeros(self.feature_dimension);
243
244 let global_count = self.feature_dimension.min(event.global_features.len());
246 for i in 0..global_count {
247 features[i] = event.global_features[i];
248 }
249
250 if self.feature_dimension > global_count && !event.particles.is_empty() {
252 let mut particle_features = Array1::zeros(self.feature_dimension - global_count);
253
254 for particle in &event.particles {
255 let p_features = self.extract_features(particle)?;
256 for i in 0..particle_features.len() {
257 particle_features[i] += p_features[i % p_features.len()];
258 }
259 }
260
261 let sum_squares = particle_features.fold(0.0f64, |acc, &x| acc + (x * x) as f64);
263 let norm = sum_squares.sqrt();
264 if norm > 0.0 {
265 particle_features.mapv_inplace(|x| x / norm);
266 }
267
268 for i in 0..particle_features.len() {
270 features[i + global_count] = particle_features[i];
271 }
272 }
273
274 Ok(features)
275 }
276
277 pub fn train(
279 &mut self,
280 x_train: &Array2<f64>,
281 y_train: &Array1<f64>,
282 epochs: usize,
283 learning_rate: f64,
284 ) -> Result<crate::qnn::TrainingResult> {
285 self.qnn.train_1d(x_train, y_train, epochs, learning_rate)
286 }
287
288 pub fn evaluate(
290 &self,
291 x_test: &Array2<f64>,
292 y_test: &Array1<f64>,
293 ) -> Result<ClassificationMetrics> {
294 let num_samples = x_test.nrows();
296 let mut y_pred = Array1::zeros(num_samples);
297 let mut confidences = Array1::zeros(num_samples);
298 let mut positive_scores = Array1::zeros(num_samples);
300 let mut loss_sum = 0.0;
302
303 let mut class_accuracies = vec![0.0; self.class_labels.len()];
305 let class_labels = self.class_labels.clone();
306
307 for i in 0..num_samples {
308 let features = x_test.row(i).to_owned();
309 let probabilities = self.predict_proba(&features)?;
310
311 let mut pred_idx = 0usize;
313 for k in 1..probabilities.len() {
314 if probabilities[k] > probabilities[pred_idx] {
315 pred_idx = k;
316 }
317 }
318
319 y_pred[i] = pred_idx as f64;
320 confidences[i] = probabilities[pred_idx];
321
322 let positive_score = if probabilities.len() >= 2 {
324 probabilities[1]
325 } else {
326 probabilities[0]
327 };
328 positive_scores[i] = positive_score;
329
330 let target = if y_test[i] > 0.5 { 1.0 } else { 0.0 };
331 let diff = positive_score - target;
332 loss_sum += diff * diff;
333 }
334
335 let mut tp = 0.0;
337 let mut fp = 0.0;
338 let mut tn = 0.0;
339 let mut fn_ = 0.0;
340
341 for i in 0..num_samples {
342 let true_label = y_test[i];
343 let pred_label = y_pred[i];
344
345 if true_label > 0.5 {
347 if pred_label > 0.5 {
348 tp += 1.0;
349 } else {
350 fn_ += 1.0;
351 }
352 } else {
353 if pred_label > 0.5 {
354 fp += 1.0;
355 } else {
356 tn += 1.0;
357 }
358 }
359 }
360
361 let accuracy = (tp + tn) / num_samples as f64;
362
363 let precision = if tp + fp > 0.0 { tp / (tp + fp) } else { 0.0 };
364
365 let recall = if tp + fn_ > 0.0 { tp / (tp + fn_) } else { 0.0 };
366
367 let f1_score = if precision + recall > 0.0 {
368 2.0 * precision * recall / (precision + recall)
369 } else {
370 0.0
371 };
372
373 let auc = compute_binary_auc(&positive_scores, y_test);
375 let confusion_matrix =
376 Array2::from_shape_vec((2, 2), vec![tn, fp, fn_, tp]).map_err(|e| {
377 MLError::MLOperationError(format!("Failed to create confusion matrix: {}", e))
378 })?;
379
380 for (i, label) in self.class_labels.iter().enumerate() {
382 let class_samples = y_test
383 .iter()
384 .enumerate()
385 .filter(|(_, &y)| y == i as f64)
386 .map(|(idx, _)| idx)
387 .collect::<Vec<_>>();
388
389 if !class_samples.is_empty() {
390 let correct = class_samples
391 .iter()
392 .filter(|&&idx| y_pred[idx] == i as f64)
393 .count();
394
395 class_accuracies[i] = correct as f64 / class_samples.len() as f64;
396 }
397 }
398
399 Ok(ClassificationMetrics {
401 accuracy,
402 precision,
403 recall,
404 f1_score,
405 auc,
406 confusion_matrix,
407 class_accuracies,
408 class_labels,
409 average_loss: loss_sum / num_samples as f64,
410 })
411 }
412
413 pub fn predict_proba(&self, features: &Array1<f64>) -> Result<Array1<f64>> {
419 let logits = self.qnn.forward(features)?;
420 if logits.is_empty() {
421 return Err(MLError::MLOperationError(
422 "QNN produced an empty output for HEP prediction".to_string(),
423 ));
424 }
425
426 let max_logit = logits.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
427 let mut probabilities: Vec<f64> = logits.iter().map(|&v| (v - max_logit).exp()).collect();
428 let sum: f64 = probabilities.iter().sum();
429 if sum > 0.0 {
430 for value in probabilities.iter_mut() {
431 *value /= sum;
432 }
433 }
434 Ok(Array1::from_vec(probabilities))
435 }
436
437 pub fn predict(&self, features: &Array1<f64>) -> Result<(String, f64)> {
442 let probabilities = self.predict_proba(features)?;
443
444 let mut best_idx = 0usize;
445 for k in 1..probabilities.len() {
446 if probabilities[k] > probabilities[best_idx] {
447 best_idx = k;
448 }
449 }
450
451 if best_idx < self.class_labels.len() {
452 Ok((self.class_labels[best_idx].clone(), probabilities[best_idx]))
453 } else {
454 Err(MLError::MLOperationError(format!(
455 "Invalid prediction index: {}",
456 best_idx
457 )))
458 }
459 }
460
461 pub fn feature_importance(&self) -> Result<Array1<f64>> {
463 let mut importance = Array1::zeros(self.feature_dimension);
466
467 for i in 0..self.feature_dimension {
468 importance[i] = thread_rng().random::<f64>();
469 }
470
471 let sum = importance.sum();
473 if sum > 0.0 {
474 importance.mapv_inplace(|x| x / sum);
475 }
476
477 Ok(importance)
478 }
479}
480
481fn compute_binary_auc(scores: &Array1<f64>, labels: &Array1<f64>) -> f64 {
488 let n = scores.len();
489 if n == 0 {
490 return 0.5;
491 }
492
493 let mut order: Vec<usize> = (0..n).collect();
495 order.sort_by(|&a, &b| {
496 scores[a]
497 .partial_cmp(&scores[b])
498 .unwrap_or(std::cmp::Ordering::Equal)
499 });
500
501 let mut ranks = vec![0.0_f64; n];
503 let mut i = 0;
504 while i < n {
505 let mut j = i;
506 while j + 1 < n && (scores[order[j + 1]] - scores[order[i]]).abs() < 1e-12 {
507 j += 1;
508 }
509 let average_rank = ((i + 1) + (j + 1)) as f64 / 2.0;
510 for &idx in &order[i..=j] {
511 ranks[idx] = average_rank;
512 }
513 i = j + 1;
514 }
515
516 let mut sum_positive_ranks = 0.0;
517 let mut n_positive = 0.0;
518 let mut n_negative = 0.0;
519 for i in 0..n {
520 if labels[i] > 0.5 {
521 sum_positive_ranks += ranks[i];
522 n_positive += 1.0;
523 } else {
524 n_negative += 1.0;
525 }
526 }
527
528 if n_positive == 0.0 || n_negative == 0.0 {
529 return 0.5;
530 }
531
532 (sum_positive_ranks - n_positive * (n_positive + 1.0) / 2.0) / (n_positive * n_negative)
533}
534
535#[derive(Debug, Clone)]
537pub struct HiggsDetector {
538 qnn: QuantumNeuralNetwork,
540
541 num_qubits: usize,
543}
544
545impl HiggsDetector {
546 pub fn new(num_qubits: usize) -> Result<Self> {
548 let layers = vec![
550 crate::qnn::QNNLayerType::EncodingLayer { num_features: 10 },
551 crate::qnn::QNNLayerType::VariationalLayer {
552 num_params: 2 * num_qubits,
553 },
554 crate::qnn::QNNLayerType::EntanglementLayer {
555 connectivity: "full".to_string(),
556 },
557 crate::qnn::QNNLayerType::VariationalLayer {
558 num_params: 2 * num_qubits,
559 },
560 crate::qnn::QNNLayerType::MeasurementLayer {
561 measurement_basis: "computational".to_string(),
562 },
563 ];
564
565 let qnn = QuantumNeuralNetwork::new(
566 layers, num_qubits, 10, 1, )?;
569
570 Ok(HiggsDetector { qnn, num_qubits })
571 }
572
573 pub fn detect_higgs(&self, event: &CollisionEvent) -> Result<Vec<bool>> {
575 let mut results = Vec::with_capacity(event.particles.len());
577
578 for particle in &event.particles {
579 let score = self.score_particle(particle)?;
580 results.push(score > 0.7); }
582
583 Ok(results)
584 }
585
586 pub fn score_particle(&self, particle: &ParticleFeatures) -> Result<f64> {
588 match particle.particle_type {
590 ParticleType::Higgs => Ok(0.85 + 0.15 * thread_rng().random::<f64>()),
591 _ => Ok(0.2 * thread_rng().random::<f64>()),
592 }
593 }
594}
595
596#[derive(Debug, Clone)]
598pub struct ParticleCollisionClassifier {
599 qnn: QuantumNeuralNetwork,
600 num_qubits: usize,
601}
602
603impl ParticleCollisionClassifier {
604 pub fn new() -> Self {
606 let layers = vec![
608 crate::qnn::QNNLayerType::EncodingLayer { num_features: 10 },
609 crate::qnn::QNNLayerType::VariationalLayer { num_params: 20 },
610 crate::qnn::QNNLayerType::EntanglementLayer {
611 connectivity: "full".to_string(),
612 },
613 crate::qnn::QNNLayerType::MeasurementLayer {
614 measurement_basis: "computational".to_string(),
615 },
616 ];
617
618 let qnn = QuantumNeuralNetwork::new(
619 layers, 8, 10, 2, )
623 .expect("should create ParticleCollisionClassifier QNN");
624
625 ParticleCollisionClassifier { qnn, num_qubits: 8 }
626 }
627
628 pub fn with_qubits(mut self, num_qubits: usize) -> Self {
630 self.num_qubits = num_qubits;
631 self
632 }
633
634 pub fn with_input_features(self, _features: usize) -> Self {
636 self
638 }
639
640 pub fn with_measurement_qubits(self, _num_qubits: usize) -> Result<Self> {
642 Ok(self)
644 }
645
646 pub fn train(
648 &mut self,
649 data: &Array2<f64>,
650 labels: &Array1<f64>,
651 epochs: usize,
652 learning_rate: f64,
653 ) -> Result<crate::qnn::TrainingResult> {
654 self.qnn.train_1d(data, labels, epochs, learning_rate)
655 }
656
657 pub fn evaluate(
659 &self,
660 data: &Array2<f64>,
661 labels: &Array1<f64>,
662 ) -> Result<ClassificationMetrics> {
663 Ok(ClassificationMetrics {
665 accuracy: 0.85,
666 precision: 0.82,
667 recall: 0.88,
668 f1_score: 0.85,
669 auc: 0.91,
670 confusion_matrix: Array2::eye(2),
671 class_accuracies: vec![0.85, 0.86], class_labels: vec!["Signal".to_string(), "Background".to_string()], average_loss: 0.15, })
675 }
676}
677
678#[derive(Debug, Clone)]
680pub struct EventReconstructor {
681 qnn: QuantumNeuralNetwork,
682 input_dim: usize,
683 output_dim: usize,
684}
685
686impl EventReconstructor {
687 pub fn new() -> Self {
689 let layers = vec![
691 crate::qnn::QNNLayerType::EncodingLayer { num_features: 10 },
692 crate::qnn::QNNLayerType::VariationalLayer { num_params: 20 },
693 crate::qnn::QNNLayerType::EntanglementLayer {
694 connectivity: "full".to_string(),
695 },
696 crate::qnn::QNNLayerType::MeasurementLayer {
697 measurement_basis: "computational".to_string(),
698 },
699 ];
700
701 let qnn = QuantumNeuralNetwork::new(
702 layers, 8, 10, 10, )
706 .expect("should create EventReconstructor QNN");
707
708 EventReconstructor {
709 qnn,
710 input_dim: 10,
711 output_dim: 10,
712 }
713 }
714
715 pub fn with_input_features(mut self, input_dim: usize) -> Self {
717 self.input_dim = input_dim;
718 self
719 }
720
721 pub fn with_output_features(mut self, output_dim: usize) -> Self {
723 self.output_dim = output_dim;
724 self
725 }
726
727 pub fn with_quantum_layers(self, _num_layers: usize) -> Result<Self> {
729 Ok(self)
731 }
732}
733
734#[derive(Debug, Clone)]
736pub struct AnomalyDetector {
737 features: usize,
738 quantum_encoder: bool,
739}
740
741impl AnomalyDetector {
742 pub fn new() -> Self {
744 AnomalyDetector {
745 features: 10,
746 quantum_encoder: false,
747 }
748 }
749
750 pub fn with_features(mut self, features: usize) -> Self {
752 self.features = features;
753 self
754 }
755
756 pub fn with_quantum_encoder(mut self, quantum_encoder: bool) -> Self {
758 self.quantum_encoder = quantum_encoder;
759 self
760 }
761
762 pub fn with_kernel_method(self, _kernel_method: KernelMethod) -> Result<Self> {
764 Ok(self)
766 }
767}
768
769#[derive(Debug, Clone, Copy)]
771pub enum KernelMethod {
772 Linear,
774
775 Polynomial,
777
778 QuantumKernel,
780}