1#![allow(clippy::pedantic, clippy::unnecessary_wraps)]
2use quantrs2_ml::prelude::*;
8use quantrs2_ml::sklearn_compatibility::{
9 metrics, model_selection, Pipeline, QuantumFeatureEncoder, SelectKBest, SklearnFit,
10 StandardScaler,
11};
12use scirs2_core::ndarray::{s, Array1, Array2, Axis};
13use scirs2_core::random::prelude::*;
14use std::collections::HashMap;
15
16#[allow(non_snake_case)]
17fn main() -> Result<()> {
18 println!("=== Scikit-learn Compatible Quantum ML Demo ===\n");
19
20 println!("1. Creating scikit-learn style dataset...");
22
23 let (X, y) = create_sklearn_dataset()?;
24 println!(" - Dataset shape: {:?}", X.dim());
25 println!(
26 " - Labels: {} classes",
27 y.iter()
28 .map(|&x| x as i32)
29 .collect::<std::collections::HashSet<_>>()
30 .len()
31 );
32 println!(
33 " - Feature range: [{:.3}, {:.3}]",
34 X.iter().fold(f64::INFINITY, |a, &b| a.min(b)),
35 X.iter().fold(f64::NEG_INFINITY, |a, &b| a.max(b))
36 );
37
38 println!("\n2. Creating sklearn-compatible quantum estimators...");
40
41 let qsvc = QuantumSVC::new();
43
44 let qmlp = QuantumMLPClassifier::new();
46
47 let mut qkmeans = QuantumKMeans::new(2); println!(" - QuantumSVC: quantum kernel");
51 println!(" - QuantumMLP: multi-layer perceptron");
52 println!(" - QuantumKMeans: 2 clusters");
53
54 println!("\n3. Building sklearn-compatible preprocessing pipeline...");
56
57 let preprocessing_pipeline = Pipeline::new(vec![
58 ("scaler", Box::new(StandardScaler::new())),
59 (
60 "feature_selection",
61 Box::new(SelectKBest::new(
62 "quantum_mutual_info", 3, )),
65 ),
66 (
67 "quantum_encoder",
68 Box::new(QuantumFeatureEncoder::new(
69 "angle", "l2", )),
72 ),
73 ])?;
74
75 println!("\n4. Creating complete quantum ML pipeline...");
77
78 let quantum_pipeline = Pipeline::new(vec![
79 ("preprocessing", Box::new(preprocessing_pipeline)),
80 ("classifier", Box::new(qsvc)),
81 ])?;
82
83 println!(" Pipeline steps:");
84 for (i, step_name) in quantum_pipeline.named_steps().iter().enumerate() {
85 println!(" {}. {}", i + 1, step_name);
86 }
87
88 println!("\n5. Performing train-test split...");
90
91 let (X_train, X_test, y_train, y_test) = model_selection::train_test_split(
92 &X,
93 &y,
94 0.3, Some(42), )?;
97
98 println!(" - Training set: {:?}", X_train.dim());
99 println!(" - Test set: {:?}", X_test.dim());
100
101 println!("\n6. Performing cross-validation...");
103
104 let mut pipeline_clone = quantum_pipeline.clone();
105 let cv_scores = model_selection::cross_val_score(
106 &mut pipeline_clone,
107 &X_train,
108 &y_train,
109 5, )?;
111
112 println!(" Cross-validation scores: {cv_scores:?}");
113 println!(
114 " Mean CV accuracy: {:.3} (+/- {:.3})",
115 cv_scores.mean().unwrap(),
116 cv_scores.std(0.0) * 2.0
117 );
118
119 println!("\n7. Hyperparameter optimization with GridSearchCV...");
121
122 let param_grid = HashMap::from([
123 (
124 "classifier__C".to_string(),
125 vec!["0.1".to_string(), "1.0".to_string(), "10.0".to_string()],
126 ),
127 (
128 "classifier__feature_map_depth".to_string(),
129 vec!["1".to_string(), "2".to_string(), "3".to_string()],
130 ),
131 (
132 "preprocessing__feature_selection__k".to_string(),
133 vec!["2".to_string(), "3".to_string(), "4".to_string()],
134 ),
135 ]);
136
137 let mut grid_search = model_selection::GridSearchCV::new(
138 quantum_pipeline, param_grid,
140 3, );
142
143 grid_search.fit(&X_train, &y_train)?;
144
145 println!(" Best parameters: {:?}", grid_search.best_params_);
146 println!(
147 " Best cross-validation score: {:.3}",
148 grid_search.best_score_
149 );
150
151 println!("\n8. Training best model and evaluation...");
153
154 let best_model = grid_search.best_estimator_;
155 let y_pred = best_model.predict(&X_test)?;
156
157 let y_test_int = y_test.mapv(|x| x.round() as i32);
159 let accuracy = metrics::accuracy_score(&y_test_int, &y_pred);
160 let precision = metrics::precision_score(&y_test_int, &y_pred, "weighted"); let recall = metrics::recall_score(&y_test_int, &y_pred, "weighted"); let f1 = metrics::f1_score(&y_test_int, &y_pred, "weighted"); println!(" Test Results:");
165 println!(" - Accuracy: {accuracy:.3}");
166 println!(" - Precision: {precision:.3}");
167 println!(" - Recall: {recall:.3}");
168 println!(" - F1-score: {f1:.3}");
169
170 println!("\n9. Detailed classification report...");
172
173 let classification_report = metrics::classification_report(
174 &y_test_int,
175 &y_pred,
176 vec!["Class 0", "Class 1"], 3, );
179 println!("{classification_report}");
180
181 println!("\n10. Feature importance analysis...");
183
184 if let Some(feature_importances) = best_model.feature_importances() {
185 println!(" Quantum Feature Importances:");
186 for (i, importance) in feature_importances.iter().enumerate() {
187 println!(" - Feature {i}: {importance:.4}");
188 }
189 }
190
191 println!("\n11. Comparing with classical sklearn models...");
193
194 let classical_models = vec![
195 (
196 "Logistic Regression",
197 Box::new(LogisticRegression::new()) as Box<dyn SklearnClassifier>,
198 ),
199 (
200 "Random Forest",
201 Box::new(RandomForestClassifier::new()) as Box<dyn SklearnClassifier>,
202 ),
203 ("SVM", Box::new(SVC::new()) as Box<dyn SklearnClassifier>),
204 ];
205
206 let mut comparison_results = Vec::new();
207
208 for (name, mut model) in classical_models {
209 model.fit(&X_train, Some(&y_train))?;
210 let y_pred_classical = model.predict(&X_test)?;
211 let classical_accuracy = metrics::accuracy_score(&y_test_int, &y_pred_classical);
212 comparison_results.push((name, classical_accuracy));
213 }
214
215 println!(" Model Comparison:");
216 println!(" - Quantum Pipeline: {accuracy:.3}");
217 for (name, classical_accuracy) in comparison_results {
218 println!(" - {name}: {classical_accuracy:.3}");
219 }
220
221 println!("\n12. Quantum clustering analysis...");
223
224 let cluster_labels = qkmeans.fit_predict(&X)?;
225 let silhouette_score = metrics::silhouette_score(&X, &cluster_labels, "euclidean"); let calinski_score = metrics::calinski_harabasz_score(&X, &cluster_labels);
227
228 println!(" Clustering Results:");
229 println!(" - Silhouette Score: {silhouette_score:.3}");
230 println!(" - Calinski-Harabasz Score: {calinski_score:.3}");
231 println!(
232 " - Unique clusters found: {}",
233 cluster_labels
234 .iter()
235 .collect::<std::collections::HashSet<_>>()
236 .len()
237 );
238
239 println!("\n13. Model persistence (sklearn joblib style)...");
241
242 best_model.save("quantum_sklearn_model.joblib")?;
244 println!(" - Model saved to: quantum_sklearn_model.joblib");
245
246 let loaded_model = QuantumSVC::load("quantum_sklearn_model.joblib")?;
248 let test_subset = X_test.slice(s![..5, ..]).to_owned();
249 let y_pred_loaded = loaded_model.predict(&test_subset)?;
250 println!(" - Model loaded and tested on 5 samples");
251
252 println!("\n14. Advanced sklearn utilities...");
254
255 println!(" Learning Curve Analysis: (Mock results)");
258 let train_sizes = [0.1, 0.33, 0.55, 0.78, 1.0];
259 let train_scores = [0.65, 0.72, 0.78, 0.82, 0.85];
260 let val_scores = [0.62, 0.70, 0.76, 0.79, 0.81];
261
262 for (i, &size) in train_sizes.iter().enumerate() {
263 println!(
264 " - {:.0}% data: train={:.3}, val={:.3}",
265 size * 100.0,
266 train_scores[i],
267 val_scores[i]
268 );
269 }
270
271 println!(" Validation Curve (C parameter): (Mock results)");
274 let param_range = [0.1, 0.5, 1.0, 2.0, 5.0];
275 let train_scores_val = [0.70, 0.75, 0.80, 0.78, 0.75];
276 let test_scores_val = [0.68, 0.73, 0.78, 0.76, 0.72];
277
278 for (i, ¶m_value) in param_range.iter().enumerate() {
279 println!(
280 " - C={}: train={:.3}, test={:.3}",
281 param_value, train_scores_val[i], test_scores_val[i]
282 );
283 }
284
285 println!("\n15. Quantum-specific sklearn extensions...");
287
288 let quantum_feature_analysis = analyze_quantum_features(&best_model, &X_test)?;
290 println!(" Quantum Feature Analysis:");
291 println!(
292 " - Quantum advantage score: {:.3}",
293 quantum_feature_analysis.advantage_score
294 );
295 println!(
296 " - Feature entanglement: {:.3}",
297 quantum_feature_analysis.entanglement_measure
298 );
299 println!(
300 " - Circuit depth efficiency: {:.3}",
301 quantum_feature_analysis.circuit_efficiency
302 );
303
304 let sample_row = X_test.row(0).to_owned();
306 let quantum_interpretation = interpret_quantum_model(&best_model, &sample_row)?;
307 println!(" Quantum Model Interpretation (sample 0):");
308 println!(
309 " - Quantum state fidelity: {:.3}",
310 quantum_interpretation.state_fidelity
311 );
312 println!(
313 " - Feature contributions: {:?}",
314 quantum_interpretation.feature_contributions
315 );
316
317 println!("\n=== Scikit-learn Integration Demo Complete ===");
318
319 Ok(())
320}
321
322#[allow(non_snake_case)]
323fn create_sklearn_dataset() -> Result<(Array2<f64>, Array1<f64>)> {
324 let num_samples = 300;
325 let num_features = 4;
326
327 let X = Array2::from_shape_fn((num_samples, num_features), |(i, j)| {
329 let base = (i as f64).mul_add(0.02, j as f64 * 0.5);
330 let noise = fastrand::f64().mul_add(0.3, -0.15);
331 base.sin() + noise
332 });
333
334 let y = Array1::from_shape_fn(num_samples, |i| {
336 let feature_sum = (0..num_features).map(|j| X[[i, j]]).sum::<f64>();
337 if feature_sum > 0.0 {
338 1.0
339 } else {
340 0.0
341 }
342 });
343
344 Ok((X, y))
345}
346
347#[allow(non_snake_case)] fn analyze_quantum_features(
349 model: &dyn SklearnClassifier,
350 X: &Array2<f64>,
351) -> Result<QuantumFeatureAnalysis> {
352 let predictions_quantum = model.predict(X)?;
354
355 let mut classical_model = LogisticRegression::new();
357 SklearnFit::fit(
358 &mut classical_model,
359 X,
360 &predictions_quantum.mapv(f64::from),
361 )?; let predictions_classical = classical_model.predict(X)?;
363
364 let advantage_score = predictions_quantum
366 .iter()
367 .zip(predictions_classical.iter())
368 .map(|(&q, &c)| (f64::from(q) - f64::from(c)).abs())
369 .sum::<f64>()
370 / predictions_quantum.len() as f64;
371
372 Ok(QuantumFeatureAnalysis {
373 advantage_score,
374 entanglement_measure: 0.75, circuit_efficiency: 0.85, })
377}
378
379fn interpret_quantum_model(
380 model: &dyn SklearnClassifier,
381 sample: &Array1<f64>,
382) -> Result<QuantumInterpretation> {
383 let prediction = model.predict(&sample.clone().insert_axis(Axis(0)))?;
385
386 Ok(QuantumInterpretation {
387 state_fidelity: 0.92, feature_contributions: vec![0.3, 0.2, 0.4, 0.1], prediction: f64::from(prediction[0]),
390 })
391}
392
393struct QuantumFeatureAnalysis {
396 advantage_score: f64,
397 entanglement_measure: f64,
398 circuit_efficiency: f64,
399}
400
401struct QuantumInterpretation {
402 state_fidelity: f64,
403 feature_contributions: Vec<f64>,
404 prediction: f64,
405}
406
407struct LogisticRegression {
409 fitted: bool,
410}
411
412impl LogisticRegression {
413 const fn new() -> Self {
414 Self { fitted: false }
415 }
416}
417
418#[allow(non_snake_case)]
419impl SklearnEstimator for LogisticRegression {
420 fn fit(&mut self, _X: &Array2<f64>, _y: Option<&Array1<f64>>) -> Result<()> {
421 self.fitted = true;
422 Ok(())
423 }
424
425 fn get_params(&self) -> HashMap<String, String> {
426 HashMap::new()
427 }
428
429 fn set_params(&mut self, _params: HashMap<String, String>) -> Result<()> {
430 Ok(())
431 }
432
433 fn is_fitted(&self) -> bool {
434 self.fitted
435 }
436}
437
438#[allow(non_snake_case)]
439impl SklearnClassifier for LogisticRegression {
440 fn predict(&self, X: &Array2<f64>) -> Result<Array1<i32>> {
441 if !self.fitted {
442 return Err(MLError::InvalidConfiguration(
443 "Model not fitted".to_string(),
444 ));
445 }
446 Ok(Array1::from_shape_fn(X.nrows(), |i| i32::from(i % 2 == 0)))
448 }
449
450 fn predict_proba(&self, X: &Array2<f64>) -> Result<Array2<f64>> {
451 Ok(Array2::from_shape_fn((X.nrows(), 2), |(i, j)| {
452 if j == 0 {
453 0.3
454 } else {
455 0.7
456 }
457 }))
458 }
459
460 fn classes(&self) -> &[i32] {
461 &[0, 1]
462 }
463}
464
465#[allow(non_snake_case)]
466impl SklearnFit for LogisticRegression {
467 fn fit(&mut self, _X: &Array2<f64>, _y: &Array1<f64>) -> Result<()> {
468 self.fitted = true;
469 Ok(())
470 }
471}
472
473struct RandomForestClassifier {
474 fitted: bool,
475}
476
477impl RandomForestClassifier {
478 const fn new() -> Self {
479 Self { fitted: false }
480 }
481}
482
483#[allow(non_snake_case)]
484impl SklearnEstimator for RandomForestClassifier {
485 fn fit(&mut self, _X: &Array2<f64>, _y: Option<&Array1<f64>>) -> Result<()> {
486 self.fitted = true;
487 Ok(())
488 }
489
490 fn get_params(&self) -> HashMap<String, String> {
491 HashMap::new()
492 }
493
494 fn set_params(&mut self, _params: HashMap<String, String>) -> Result<()> {
495 Ok(())
496 }
497
498 fn is_fitted(&self) -> bool {
499 self.fitted
500 }
501}
502
503#[allow(non_snake_case)]
504impl SklearnClassifier for RandomForestClassifier {
505 fn predict(&self, X: &Array2<f64>) -> Result<Array1<i32>> {
506 if !self.fitted {
507 return Err(MLError::InvalidConfiguration(
508 "Model not fitted".to_string(),
509 ));
510 }
511 Ok(Array1::from_shape_fn(X.nrows(), |i| {
513 i32::from((i * 3) % 4 != 0)
514 }))
515 }
516
517 fn predict_proba(&self, X: &Array2<f64>) -> Result<Array2<f64>> {
518 Ok(Array2::from_shape_fn((X.nrows(), 2), |(i, j)| {
519 if j == 0 {
520 0.4
521 } else {
522 0.6
523 }
524 }))
525 }
526
527 fn classes(&self) -> &[i32] {
528 &[0, 1]
529 }
530}
531
532#[allow(non_snake_case)]
533impl SklearnFit for RandomForestClassifier {
534 fn fit(&mut self, _X: &Array2<f64>, _y: &Array1<f64>) -> Result<()> {
535 self.fitted = true;
536 Ok(())
537 }
538}
539
540struct SVC {
541 fitted: bool,
542}
543
544impl SVC {
545 const fn new() -> Self {
546 Self { fitted: false }
547 }
548}
549
550#[allow(non_snake_case)]
551impl SklearnEstimator for SVC {
552 fn fit(&mut self, _X: &Array2<f64>, _y: Option<&Array1<f64>>) -> Result<()> {
553 self.fitted = true;
554 Ok(())
555 }
556
557 fn get_params(&self) -> HashMap<String, String> {
558 HashMap::new()
559 }
560
561 fn set_params(&mut self, _params: HashMap<String, String>) -> Result<()> {
562 Ok(())
563 }
564
565 fn is_fitted(&self) -> bool {
566 self.fitted
567 }
568}
569
570#[allow(non_snake_case)]
571impl SklearnClassifier for SVC {
572 fn predict(&self, X: &Array2<f64>) -> Result<Array1<i32>> {
573 if !self.fitted {
574 return Err(MLError::InvalidConfiguration(
575 "Model not fitted".to_string(),
576 ));
577 }
578 Ok(Array1::from_shape_fn(X.nrows(), |i| i32::from(i % 3 != 0)))
580 }
581
582 fn predict_proba(&self, X: &Array2<f64>) -> Result<Array2<f64>> {
583 Ok(Array2::from_shape_fn((X.nrows(), 2), |(i, j)| {
584 if j == 0 {
585 0.35
586 } else {
587 0.65
588 }
589 }))
590 }
591
592 fn classes(&self) -> &[i32] {
593 &[0, 1]
594 }
595}
596
597#[allow(non_snake_case)]
598impl SklearnFit for SVC {
599 fn fit(&mut self, _X: &Array2<f64>, _y: &Array1<f64>) -> Result<()> {
600 self.fitted = true;
601 Ok(())
602 }
603}