1use crate::autodiff::optimizers::Optimizer;
8use crate::error::{MLError, Result};
9use crate::optimization::OptimizationMethod;
10use crate::qnn::{QNNLayerType, QuantumNeuralNetwork, TrainingResult};
11use ndarray::{Array1, Array2};
12use quantrs2_circuit::builder::{Circuit, Simulator};
13use quantrs2_core::gate::{
14 single::{RotationX, RotationY, RotationZ},
15 GateOp,
16};
17use quantrs2_sim::statevector::StateVectorSimulator;
18use std::collections::HashMap;
19
20#[derive(Debug, Clone, Copy, PartialEq)]
22pub enum TransferStrategy {
23 FineTuning { num_trainable_layers: usize },
25
26 FeatureExtraction,
28
29 SelectiveAdaptation,
31
32 ProgressiveUnfreezing { unfreeze_rate: usize },
34}
35
36#[derive(Debug, Clone)]
38pub struct LayerConfig {
39 pub frozen: bool,
41
42 pub learning_rate_multiplier: f64,
44
45 pub parameter_indices: Vec<usize>,
47}
48
49#[derive(Debug, Clone)]
51pub struct PretrainedModel {
52 pub qnn: QuantumNeuralNetwork,
54
55 pub task_description: String,
57
58 pub performance_metrics: HashMap<String, f64>,
60
61 pub metadata: HashMap<String, String>,
63}
64
65pub struct QuantumTransferLearning {
67 source_model: PretrainedModel,
69
70 target_model: QuantumNeuralNetwork,
72
73 strategy: TransferStrategy,
75
76 layer_configs: Vec<LayerConfig>,
78
79 current_epoch: usize,
81}
82
83impl QuantumTransferLearning {
84 pub fn new(
86 source_model: PretrainedModel,
87 target_layers: Vec<QNNLayerType>,
88 strategy: TransferStrategy,
89 ) -> Result<Self> {
90 let mut all_layers = source_model.qnn.layers.clone();
95
96 match strategy {
98 TransferStrategy::FineTuning { .. } => {
99 all_layers.extend(target_layers);
101 }
102 TransferStrategy::FeatureExtraction => {
103 if all_layers.len() > 2 {
105 all_layers.truncate(all_layers.len() - 2);
106 }
107 all_layers.extend(target_layers);
108 }
109 _ => {
110 all_layers.extend(target_layers);
112 }
113 }
114
115 let target_model = QuantumNeuralNetwork::new(
117 all_layers,
118 source_model.qnn.num_qubits,
119 source_model.qnn.input_dim,
120 source_model.qnn.output_dim,
121 )?;
122
123 let layer_configs = Self::configure_layers(&target_model, &strategy);
125
126 Ok(Self {
127 source_model,
128 target_model,
129 strategy,
130 layer_configs,
131 current_epoch: 0,
132 })
133 }
134
135 fn configure_layers(
137 model: &QuantumNeuralNetwork,
138 strategy: &TransferStrategy,
139 ) -> Vec<LayerConfig> {
140 let mut configs = Vec::new();
141 let num_layers = model.layers.len();
142
143 match strategy {
144 TransferStrategy::FineTuning {
145 num_trainable_layers,
146 } => {
147 for i in 0..num_layers {
149 configs.push(LayerConfig {
150 frozen: i < num_layers - num_trainable_layers,
151 learning_rate_multiplier: if i < num_layers - num_trainable_layers {
152 0.0
153 } else {
154 1.0
155 },
156 parameter_indices: Self::get_layer_parameters(model, i),
157 });
158 }
159 }
160 TransferStrategy::FeatureExtraction => {
161 for i in 0..num_layers {
163 let is_new_layer = i >= num_layers - 2; configs.push(LayerConfig {
165 frozen: !is_new_layer,
166 learning_rate_multiplier: if is_new_layer { 1.0 } else { 0.0 },
167 parameter_indices: Self::get_layer_parameters(model, i),
168 });
169 }
170 }
171 TransferStrategy::SelectiveAdaptation => {
172 for (i, layer) in model.layers.iter().enumerate() {
174 let frozen = matches!(layer, QNNLayerType::EncodingLayer { .. });
175 configs.push(LayerConfig {
176 frozen,
177 learning_rate_multiplier: if frozen { 0.0 } else { 0.5 },
178 parameter_indices: Self::get_layer_parameters(model, i),
179 });
180 }
181 }
182 TransferStrategy::ProgressiveUnfreezing { .. } => {
183 for i in 0..num_layers {
185 configs.push(LayerConfig {
186 frozen: i < num_layers - 1,
187 learning_rate_multiplier: if i == num_layers - 1 { 1.0 } else { 0.0 },
188 parameter_indices: Self::get_layer_parameters(model, i),
189 });
190 }
191 }
192 }
193
194 configs
195 }
196
197 fn get_layer_parameters(model: &QuantumNeuralNetwork, layer_idx: usize) -> Vec<usize> {
199 let params_per_layer = model.parameters.len() / model.layers.len();
202 let start = layer_idx * params_per_layer;
203 let end = start + params_per_layer;
204 (start..end).collect()
205 }
206
207 pub fn train(
209 &mut self,
210 training_data: &Array2<f64>,
211 labels: &Array1<f64>,
212 optimizer: &mut dyn Optimizer,
213 epochs: usize,
214 batch_size: usize,
215 ) -> Result<TrainingResult> {
216 let mut loss_history = Vec::new();
217 let mut best_loss = f64::INFINITY;
218 let mut best_params = self.target_model.parameters.clone();
219
220 let mut params_map = HashMap::new();
222 for (i, value) in self.target_model.parameters.iter().enumerate() {
223 params_map.insert(format!("param_{}", i), *value);
224 }
225
226 for epoch in 0..epochs {
227 self.current_epoch = epoch;
228
229 if let TransferStrategy::ProgressiveUnfreezing { unfreeze_rate } = self.strategy {
231 if epoch > 0 && epoch % unfreeze_rate == 0 {
232 self.unfreeze_next_layer();
233 }
234 }
235
236 let gradients = self.compute_gradients(training_data, labels)?;
238
239 let scaled_gradients = self.scale_gradients(&gradients);
241
242 let mut grads_map = HashMap::new();
244 for (i, grad) in scaled_gradients.iter().enumerate() {
245 grads_map.insert(format!("param_{}", i), *grad);
246 }
247
248 optimizer.step(&mut params_map, &grads_map);
250
251 for (i, value) in self.target_model.parameters.iter_mut().enumerate() {
253 if let Some(new_val) = params_map.get(&format!("param_{}", i)) {
254 *value = *new_val;
255 }
256 }
257
258 let loss = self.compute_loss(training_data, labels)?;
260 loss_history.push(loss);
261
262 if loss < best_loss {
263 best_loss = loss;
264 best_params = self.target_model.parameters.clone();
265 }
266 }
267
268 let predictions = self.predict(training_data)?;
270 let accuracy = Self::compute_accuracy(&predictions, labels);
271
272 Ok(TrainingResult {
273 final_loss: best_loss,
274 accuracy,
275 loss_history,
276 optimal_parameters: best_params,
277 })
278 }
279
280 fn unfreeze_next_layer(&mut self) {
282 let num_layers = self.layer_configs.len();
284 for (i, config) in self.layer_configs.iter_mut().enumerate().rev() {
285 if config.frozen {
286 config.frozen = false;
287 config.learning_rate_multiplier = 0.1 * (i as f64 / num_layers as f64);
288 break;
289 }
290 }
291 }
292
293 fn compute_gradients(&self, data: &Array2<f64>, labels: &Array1<f64>) -> Result<Array1<f64>> {
295 let mut gradients = Array1::zeros(self.target_model.parameters.len());
298
299 for config in &self.layer_configs {
301 if !config.frozen {
302 for &idx in &config.parameter_indices {
303 if idx < gradients.len() {
304 gradients[idx] = 0.1 * (2.0 * rand::random::<f64>() - 1.0);
305 }
306 }
307 }
308 }
309
310 Ok(gradients)
311 }
312
313 fn scale_gradients(&self, gradients: &Array1<f64>) -> Array1<f64> {
315 let mut scaled = gradients.clone();
316
317 for config in &self.layer_configs {
318 for &idx in &config.parameter_indices {
319 if idx < scaled.len() {
320 scaled[idx] *= config.learning_rate_multiplier;
321 }
322 }
323 }
324
325 scaled
326 }
327
328 fn compute_loss(&self, data: &Array2<f64>, labels: &Array1<f64>) -> Result<f64> {
330 let predictions = self.predict(data)?;
331
332 let mut loss = 0.0;
334 for (pred, label) in predictions.iter().zip(labels.iter()) {
335 loss += (pred - label).powi(2);
336 }
337
338 Ok(loss / labels.len() as f64)
339 }
340
341 pub fn predict(&self, data: &Array2<f64>) -> Result<Array1<f64>> {
343 let num_samples = data.nrows();
346 Ok(Array1::from_vec(vec![0.5; num_samples]))
347 }
348
349 fn compute_accuracy(predictions: &Array1<f64>, labels: &Array1<f64>) -> f64 {
351 let correct = predictions
352 .iter()
353 .zip(labels.iter())
354 .filter(|(p, l)| (p.round() - l.round()).abs() < 0.1)
355 .count();
356
357 correct as f64 / labels.len() as f64
358 }
359
360 pub fn extract_features(&self, data: &Array2<f64>) -> Result<Array2<f64>> {
362 let feature_dim = self
364 .layer_configs
365 .iter()
366 .filter(|c| c.frozen)
367 .map(|c| c.parameter_indices.len())
368 .sum();
369
370 let num_samples = data.nrows();
371 let features = Array2::zeros((num_samples, feature_dim));
372
373 Ok(features)
375 }
376
377 pub fn save_model(&self, path: &str) -> Result<()> {
379 Ok(())
381 }
382
383 pub fn load_pretrained(path: &str) -> Result<PretrainedModel> {
385 Err(MLError::ModelCreationError("Not implemented".to_string()))
387 }
388}
389
390pub struct QuantumModelZoo;
392
393impl QuantumModelZoo {
394 pub fn get_image_classifier() -> Result<PretrainedModel> {
396 let layers = vec![
398 QNNLayerType::EncodingLayer { num_features: 4 },
399 QNNLayerType::VariationalLayer { num_params: 8 },
400 QNNLayerType::EntanglementLayer {
401 connectivity: "linear".to_string(),
402 },
403 QNNLayerType::MeasurementLayer {
404 measurement_basis: "computational".to_string(),
405 },
406 ];
407
408 let qnn = QuantumNeuralNetwork::new(layers, 4, 4, 2)?;
409
410 let mut metadata = HashMap::new();
411 metadata.insert("task".to_string(), "image_classification".to_string());
412 metadata.insert("dataset".to_string(), "mnist_subset".to_string());
413
414 let mut performance = HashMap::new();
415 performance.insert("accuracy".to_string(), 0.85);
416 performance.insert("loss".to_string(), 0.32);
417
418 Ok(PretrainedModel {
419 qnn,
420 task_description: "Pre-trained on MNIST subset for binary classification".to_string(),
421 performance_metrics: performance,
422 metadata,
423 })
424 }
425
426 pub fn get_chemistry_model() -> Result<PretrainedModel> {
428 let layers = vec![
429 QNNLayerType::EncodingLayer { num_features: 6 },
430 QNNLayerType::VariationalLayer { num_params: 12 },
431 QNNLayerType::EntanglementLayer {
432 connectivity: "full".to_string(),
433 },
434 QNNLayerType::VariationalLayer { num_params: 12 },
435 QNNLayerType::MeasurementLayer {
436 measurement_basis: "Pauli-Z".to_string(),
437 },
438 ];
439
440 let qnn = QuantumNeuralNetwork::new(layers, 6, 6, 1)?;
441
442 let mut metadata = HashMap::new();
443 metadata.insert("task".to_string(), "molecular_energy".to_string());
444 metadata.insert("dataset".to_string(), "h2_h4_molecules".to_string());
445
446 let mut performance = HashMap::new();
447 performance.insert("mae".to_string(), 0.001);
448 performance.insert("r2_score".to_string(), 0.98);
449
450 Ok(PretrainedModel {
451 qnn,
452 task_description: "Pre-trained on molecular energy prediction".to_string(),
453 performance_metrics: performance,
454 metadata,
455 })
456 }
457
458 pub fn vqe_feature_extractor(n_qubits: usize) -> Result<PretrainedModel> {
460 let layers = vec![
461 QNNLayerType::EncodingLayer {
462 num_features: n_qubits,
463 },
464 QNNLayerType::VariationalLayer {
465 num_params: n_qubits * 2,
466 },
467 QNNLayerType::EntanglementLayer {
468 connectivity: "linear".to_string(),
469 },
470 QNNLayerType::VariationalLayer {
471 num_params: n_qubits,
472 },
473 QNNLayerType::MeasurementLayer {
474 measurement_basis: "Pauli-Z".to_string(),
475 },
476 ];
477
478 let qnn = QuantumNeuralNetwork::new(layers, n_qubits, n_qubits, n_qubits / 2)?;
479
480 let mut metadata = HashMap::new();
481 metadata.insert("task".to_string(), "feature_extraction".to_string());
482 metadata.insert("algorithm".to_string(), "VQE".to_string());
483
484 let mut performance = HashMap::new();
485 performance.insert("fidelity".to_string(), 0.92);
486 performance.insert("feature_quality".to_string(), 0.88);
487
488 Ok(PretrainedModel {
489 qnn,
490 task_description: format!("Pre-trained VQE feature extractor for {} qubits", n_qubits),
491 performance_metrics: performance,
492 metadata,
493 })
494 }
495
496 pub fn qaoa_classifier(n_qubits: usize, n_layers: usize) -> Result<PretrainedModel> {
498 let mut layers = vec![QNNLayerType::EncodingLayer {
499 num_features: n_qubits,
500 }];
501
502 for _ in 0..n_layers {
504 layers.push(QNNLayerType::VariationalLayer {
505 num_params: n_qubits,
506 });
507 layers.push(QNNLayerType::EntanglementLayer {
508 connectivity: "circular".to_string(),
509 });
510 }
511
512 layers.push(QNNLayerType::MeasurementLayer {
513 measurement_basis: "computational".to_string(),
514 });
515
516 let qnn = QuantumNeuralNetwork::new(layers, n_qubits, n_qubits, 2)?;
517
518 let mut metadata = HashMap::new();
519 metadata.insert("task".to_string(), "classification".to_string());
520 metadata.insert("algorithm".to_string(), "QAOA".to_string());
521 metadata.insert("layers".to_string(), n_layers.to_string());
522
523 let mut performance = HashMap::new();
524 performance.insert("accuracy".to_string(), 0.86);
525 performance.insert("f1_score".to_string(), 0.84);
526
527 Ok(PretrainedModel {
528 qnn,
529 task_description: format!(
530 "Pre-trained QAOA classifier with {} qubits and {} layers",
531 n_qubits, n_layers
532 ),
533 performance_metrics: performance,
534 metadata,
535 })
536 }
537
538 pub fn quantum_autoencoder(n_qubits: usize, latent_dim: usize) -> Result<PretrainedModel> {
540 let layers = vec![
541 QNNLayerType::EncodingLayer {
542 num_features: n_qubits,
543 },
544 QNNLayerType::VariationalLayer {
545 num_params: n_qubits * 2,
546 },
547 QNNLayerType::EntanglementLayer {
548 connectivity: "linear".to_string(),
549 },
550 QNNLayerType::VariationalLayer {
552 num_params: latent_dim * 2,
553 },
554 QNNLayerType::VariationalLayer {
556 num_params: n_qubits,
557 },
558 QNNLayerType::EntanglementLayer {
559 connectivity: "full".to_string(),
560 },
561 QNNLayerType::MeasurementLayer {
562 measurement_basis: "computational".to_string(),
563 },
564 ];
565
566 let qnn = QuantumNeuralNetwork::new(layers, n_qubits, n_qubits, n_qubits)?;
567
568 let mut metadata = HashMap::new();
569 metadata.insert("task".to_string(), "autoencoding".to_string());
570 metadata.insert("latent_dimension".to_string(), latent_dim.to_string());
571
572 let mut performance = HashMap::new();
573 performance.insert("reconstruction_fidelity".to_string(), 0.94);
574 performance.insert(
575 "compression_ratio".to_string(),
576 n_qubits as f64 / latent_dim as f64,
577 );
578
579 Ok(PretrainedModel {
580 qnn,
581 task_description: format!(
582 "Pre-trained quantum autoencoder with {} qubits and {} latent dimensions",
583 n_qubits, latent_dim
584 ),
585 performance_metrics: performance,
586 metadata,
587 })
588 }
589}
590
591#[cfg(test)]
592mod tests {
593 use super::*;
594 use crate::autodiff::optimizers::Adam;
595
596 #[test]
597 fn test_transfer_learning_creation() {
598 let source = QuantumModelZoo::get_image_classifier().unwrap();
599 let target_layers = vec![
600 QNNLayerType::VariationalLayer { num_params: 4 },
601 QNNLayerType::MeasurementLayer {
602 measurement_basis: "computational".to_string(),
603 },
604 ];
605
606 let transfer = QuantumTransferLearning::new(
607 source,
608 target_layers,
609 TransferStrategy::FineTuning {
610 num_trainable_layers: 2,
611 },
612 )
613 .unwrap();
614
615 assert_eq!(transfer.current_epoch, 0);
616 assert!(transfer.layer_configs.len() > 0);
617 }
618
619 #[test]
620 fn test_layer_freezing() {
621 let source = QuantumModelZoo::get_chemistry_model().unwrap();
622 let target_layers = vec![];
623
624 let transfer = QuantumTransferLearning::new(
625 source,
626 target_layers,
627 TransferStrategy::FeatureExtraction,
628 )
629 .unwrap();
630
631 assert!(transfer.layer_configs[0].frozen);
633 assert_eq!(transfer.layer_configs[0].learning_rate_multiplier, 0.0);
634 }
635
636 #[test]
637 fn test_progressive_unfreezing() {
638 let source = QuantumModelZoo::get_image_classifier().unwrap();
639 let target_layers = vec![];
640
641 let mut transfer = QuantumTransferLearning::new(
642 source,
643 target_layers,
644 TransferStrategy::ProgressiveUnfreezing { unfreeze_rate: 5 },
645 )
646 .unwrap();
647
648 let frozen_count = transfer.layer_configs.iter().filter(|c| c.frozen).count();
650 assert!(frozen_count > 0);
651
652 transfer.current_epoch = 5;
654 transfer.unfreeze_next_layer();
655
656 let new_frozen_count = transfer.layer_configs.iter().filter(|c| c.frozen).count();
658 assert_eq!(new_frozen_count, frozen_count - 1);
659 }
660}