1use crate::error::{QearError, QearResult};
8use crate::reservoir::QuantumReservoir;
9use ndarray::{Array1, Array2, Axis};
10
11#[cfg(feature = "serde")]
12use serde::{Deserialize, Serialize};
13
14#[derive(Debug, Clone)]
16#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
17pub struct FeatureConfig {
18 pub include_raw_states: bool,
20 pub include_probabilities: bool,
22 pub include_correlations: bool,
24 pub include_temporal_diff: bool,
26 pub include_spectral: bool,
28 pub include_moments: bool,
30 pub moment_order: usize,
32 pub window_size: usize,
34 pub polynomial_degree: usize,
36}
37
38impl Default for FeatureConfig {
39 fn default() -> Self {
40 Self {
41 include_raw_states: true,
42 include_probabilities: true,
43 include_correlations: false,
44 include_temporal_diff: true,
45 include_spectral: false,
46 include_moments: true,
47 moment_order: 4,
48 window_size: 10,
49 polynomial_degree: 2,
50 }
51 }
52}
53
54impl FeatureConfig {
55 pub fn minimal() -> Self {
57 Self {
58 include_raw_states: true,
59 include_probabilities: false,
60 include_correlations: false,
61 include_temporal_diff: false,
62 include_spectral: false,
63 include_moments: false,
64 moment_order: 2,
65 window_size: 5,
66 polynomial_degree: 1,
67 }
68 }
69
70 pub fn comprehensive() -> Self {
72 Self {
73 include_raw_states: true,
74 include_probabilities: true,
75 include_correlations: true,
76 include_temporal_diff: true,
77 include_spectral: true,
78 include_moments: true,
79 moment_order: 4,
80 window_size: 20,
81 polynomial_degree: 3,
82 }
83 }
84}
85
86#[derive(Debug, Clone)]
88#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
89pub struct FeatureExtractor {
90 config: FeatureConfig,
92}
93
94impl FeatureExtractor {
95 pub fn new(config: FeatureConfig) -> Self {
97 Self { config }
98 }
99
100 pub fn default_extractor() -> Self {
102 Self::new(FeatureConfig::default())
103 }
104
105 pub fn extract(&self, states: &Array2<f64>) -> QearResult<Array2<f64>> {
107 let n_samples = states.nrows();
108 let mut feature_sets: Vec<Array2<f64>> = Vec::new();
109
110 if self.config.include_raw_states {
112 feature_sets.push(states.clone());
113 }
114
115 if self.config.include_probabilities {
117 let probs = states.mapv(|x| x * x);
118 feature_sets.push(probs);
119 }
120
121 if self.config.include_correlations {
123 let correlations = self.compute_pairwise_correlations(states)?;
124 feature_sets.push(correlations);
125 }
126
127 if self.config.include_temporal_diff && n_samples > 1 {
129 let diffs = self.compute_temporal_differences(states)?;
130 feature_sets.push(diffs);
131 }
132
133 if self.config.include_moments {
135 let moments = self.compute_moments(states)?;
136 feature_sets.push(moments);
137 }
138
139 if self.config.include_spectral && n_samples >= self.config.window_size {
141 let spectral = self.compute_spectral_features(states)?;
142 feature_sets.push(spectral);
143 }
144
145 if feature_sets.is_empty() {
147 return Err(QearError::feature_extraction(
148 "No features selected in configuration",
149 ));
150 }
151
152 let total_features: usize = feature_sets.iter().map(|f| f.ncols()).sum();
153 let mut result = Array2::zeros((n_samples, total_features));
154 let mut col_offset = 0;
155
156 for features in feature_sets {
157 let n_cols = features.ncols();
158 for i in 0..n_samples {
159 for j in 0..n_cols {
160 result[[i, col_offset + j]] = features[[i, j]];
161 }
162 }
163 col_offset += n_cols;
164 }
165
166 Ok(result)
167 }
168
169 fn compute_pairwise_correlations(&self, states: &Array2<f64>) -> QearResult<Array2<f64>> {
171 let n_samples = states.nrows();
172 let n_neurons = states.ncols();
173
174 let max_correlations = 100;
176 let step = ((n_neurons * (n_neurons - 1) / 2) as f64 / max_correlations as f64)
177 .sqrt()
178 .ceil() as usize;
179 let step = step.max(1);
180
181 let selected_neurons: Vec<usize> = (0..n_neurons).step_by(step).collect();
182 let n_pairs = selected_neurons.len() * (selected_neurons.len() - 1) / 2;
183
184 let mut correlations = Array2::zeros((n_samples, n_pairs.max(1)));
185
186 let mut pair_idx = 0;
187 for (i, &ni) in selected_neurons.iter().enumerate() {
188 for &nj in selected_neurons.iter().skip(i + 1) {
189 for t in 0..n_samples {
190 correlations[[t, pair_idx]] = states[[t, ni]] * states[[t, nj]];
191 }
192 pair_idx += 1;
193 if pair_idx >= n_pairs {
194 break;
195 }
196 }
197 if pair_idx >= n_pairs {
198 break;
199 }
200 }
201
202 Ok(correlations)
203 }
204
205 fn compute_temporal_differences(&self, states: &Array2<f64>) -> QearResult<Array2<f64>> {
207 let n_samples = states.nrows();
208 let n_neurons = states.ncols();
209
210 let mut diffs = Array2::zeros((n_samples, n_neurons));
211
212 for j in 0..n_neurons {
214 diffs[[0, j]] = 0.0;
215 }
216
217 for i in 1..n_samples {
219 for j in 0..n_neurons {
220 diffs[[i, j]] = states[[i, j]] - states[[i - 1, j]];
221 }
222 }
223
224 Ok(diffs)
225 }
226
227 fn compute_moments(&self, states: &Array2<f64>) -> QearResult<Array2<f64>> {
229 let n_samples = states.nrows();
230 let order = self.config.moment_order;
231
232 let mut moments = Array2::zeros((n_samples, order));
234
235 for i in 0..n_samples {
236 let row = states.row(i);
237 let mean = row.mean().unwrap_or(0.0);
238
239 moments[[i, 0]] = mean;
241
242 if order >= 2 {
243 let variance: f64 = row.iter().map(|x| (x - mean).powi(2)).sum::<f64>()
245 / row.len() as f64;
246 moments[[i, 1]] = variance;
247
248 if order >= 3 && variance > 1e-10 {
249 let std_dev = variance.sqrt();
251 let skewness: f64 = row.iter().map(|x| ((x - mean) / std_dev).powi(3)).sum::<f64>()
252 / row.len() as f64;
253 moments[[i, 2]] = skewness;
254 }
255
256 if order >= 4 && variance > 1e-10 {
257 let std_dev = variance.sqrt();
259 let kurtosis: f64 = row.iter().map(|x| ((x - mean) / std_dev).powi(4)).sum::<f64>()
260 / row.len() as f64
261 - 3.0; moments[[i, 3]] = kurtosis;
263 }
264 }
265 }
266
267 Ok(moments)
268 }
269
270 fn compute_spectral_features(&self, states: &Array2<f64>) -> QearResult<Array2<f64>> {
272 let n_samples = states.nrows();
273 let n_neurons = states.ncols();
274 let window = self.config.window_size;
275
276 let n_freq = window / 2 + 1;
278 let mut spectral = Array2::zeros((n_samples, n_freq));
279
280 for i in 0..n_samples {
281 if i + 1 >= window {
282 let start = i + 1 - window;
284
285 for k in 0..n_freq {
286 let mut real_sum = 0.0;
287 let mut imag_sum = 0.0;
288
289 for t in 0..window {
290 let mut sample_avg = 0.0;
292 for j in 0..n_neurons {
293 sample_avg += states[[start + t, j]];
294 }
295 sample_avg /= n_neurons as f64;
296
297 let angle = -2.0 * std::f64::consts::PI * k as f64 * t as f64 / window as f64;
298 real_sum += sample_avg * angle.cos();
299 imag_sum += sample_avg * angle.sin();
300 }
301
302 spectral[[i, k]] = (real_sum * real_sum + imag_sum * imag_sum).sqrt() / window as f64;
303 }
304 }
305 }
306
307 Ok(spectral)
308 }
309
310 pub fn extract_single(&self, state: &Array1<f64>) -> QearResult<Array1<f64>> {
312 let states = state.clone().insert_axis(Axis(0));
313 let features = self.extract(&states)?;
314 Ok(features.row(0).to_owned())
315 }
316
317 pub fn config(&self) -> &FeatureConfig {
319 &self.config
320 }
321}
322
323#[derive(Debug, Clone)]
327#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
328pub struct QuantumTomography {
329 num_bases: usize,
331}
332
333impl QuantumTomography {
334 pub fn new(num_bases: usize) -> Self {
336 Self { num_bases }
337 }
338
339 pub fn measure(&self, reservoir: &QuantumReservoir) -> QearResult<Array1<f64>> {
341 let (real, imag) = reservoir.complex_state();
342 let n = real.len();
343
344 let mut measurements = Vec::with_capacity(self.num_bases * 3);
346
347 let z_expectations = self.pauli_z_expectation(real, imag);
349 measurements.extend(z_expectations.iter().take(self.num_bases.min(n)));
350
351 let x_expectations = self.pauli_x_expectation(real, imag);
353 measurements.extend(x_expectations.iter().take(self.num_bases.min(n)));
354
355 let y_expectations = self.pauli_y_expectation(real, imag);
357 measurements.extend(y_expectations.iter().take(self.num_bases.min(n)));
358
359 Ok(Array1::from_vec(measurements))
360 }
361
362 fn pauli_z_expectation(&self, real: &Array1<f64>, imag: &Array1<f64>) -> Vec<f64> {
364 let n = real.len();
365 let n_qubits = (n as f64).log2().ceil() as usize;
366
367 let mut expectations = Vec::with_capacity(n_qubits);
368
369 for q in 0..n_qubits {
370 let mut expectation = 0.0;
371 for i in 0..n {
372 let prob = real[i] * real[i] + imag[i] * imag[i];
373 let sign = if (i >> q) & 1 == 0 { 1.0 } else { -1.0 };
375 expectation += sign * prob;
376 }
377 expectations.push(expectation);
378 }
379
380 expectations
381 }
382
383 fn pauli_x_expectation(&self, real: &Array1<f64>, imag: &Array1<f64>) -> Vec<f64> {
385 let n = real.len();
386 let n_qubits = (n as f64).log2().ceil() as usize;
387
388 let mut expectations = Vec::with_capacity(n_qubits);
389
390 for q in 0..n_qubits {
391 let mut expectation = 0.0;
392 for i in 0..n {
393 let j = i ^ (1 << q);
395 if j < n {
396 expectation += real[i] * real[j] + imag[i] * imag[j];
398 }
399 }
400 expectations.push(expectation);
401 }
402
403 expectations
404 }
405
406 fn pauli_y_expectation(&self, real: &Array1<f64>, imag: &Array1<f64>) -> Vec<f64> {
408 let n = real.len();
409 let n_qubits = (n as f64).log2().ceil() as usize;
410
411 let mut expectations = Vec::with_capacity(n_qubits);
412
413 for q in 0..n_qubits {
414 let mut expectation = 0.0;
415 for i in 0..n {
416 let j = i ^ (1 << q);
417 if j < n {
418 let sign = if (i >> q) & 1 == 0 { 1.0 } else { -1.0 };
420 expectation += sign * (real[i] * imag[j] - imag[i] * real[j]);
421 }
422 }
423 expectations.push(expectation);
424 }
425
426 expectations
427 }
428
429 pub fn num_bases(&self) -> usize {
431 self.num_bases
432 }
433}
434
435#[derive(Debug, Clone)]
437#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
438pub struct ExpectationComputer {
439 observables: Vec<Array1<f64>>,
441}
442
443impl ExpectationComputer {
444 pub fn new(n_qubits: usize) -> Self {
446 let size = 1 << n_qubits;
447 let mut observables = Vec::new();
448
449 for i in 0..size {
451 let mut observable = Array1::zeros(size);
452 observable[i] = 1.0;
453 observables.push(observable);
454 }
455
456 Self { observables }
457 }
458
459 pub fn with_observables(observables: Vec<Array1<f64>>) -> Self {
461 Self { observables }
462 }
463
464 pub fn add_observable(&mut self, observable: Array1<f64>) {
466 self.observables.push(observable);
467 }
468
469 pub fn compute(&self, reservoir: &QuantumReservoir) -> QearResult<Array1<f64>> {
471 let probs = reservoir.probability_amplitudes();
472 let mut expectations = Vec::with_capacity(self.observables.len());
473
474 for observable in &self.observables {
475 if observable.len() != probs.len() {
476 return Err(QearError::dimension_mismatch(observable.len(), probs.len()));
477 }
478 let expectation: f64 = observable.iter().zip(probs.iter()).map(|(o, p)| o * p).sum();
479 expectations.push(expectation);
480 }
481
482 Ok(Array1::from_vec(expectations))
483 }
484
485 pub fn compute_single(
487 &self,
488 reservoir: &QuantumReservoir,
489 observable_idx: usize,
490 ) -> QearResult<f64> {
491 if observable_idx >= self.observables.len() {
492 return Err(QearError::invalid_parameter(
493 "observable_idx",
494 format!("Index {} out of bounds", observable_idx),
495 ));
496 }
497
498 let probs = reservoir.probability_amplitudes();
499 let observable = &self.observables[observable_idx];
500 let expectation: f64 = observable.iter().zip(probs.iter()).map(|(o, p)| o * p).sum();
501
502 Ok(expectation)
503 }
504
505 pub fn num_observables(&self) -> usize {
507 self.observables.len()
508 }
509}
510
511#[cfg(test)]
512mod tests {
513 use super::*;
514 use crate::reservoir::ReservoirConfig;
515
516 #[test]
517 fn test_feature_config_default() {
518 let config = FeatureConfig::default();
519 assert!(config.include_raw_states);
520 assert!(config.include_probabilities);
521 }
522
523 #[test]
524 fn test_feature_extractor_creation() {
525 let extractor = FeatureExtractor::default_extractor();
526 assert!(extractor.config().include_raw_states);
527 }
528
529 #[test]
530 fn test_feature_extraction() {
531 let config = FeatureConfig::minimal();
532 let extractor = FeatureExtractor::new(config);
533
534 let states = Array2::from_shape_fn((10, 16), |(i, j)| {
535 ((i + j) as f64 / 26.0).sin()
536 });
537
538 let features = extractor.extract(&states).unwrap();
539 assert_eq!(features.nrows(), 10);
540 assert!(features.ncols() >= 16); }
542
543 #[test]
544 fn test_feature_extraction_comprehensive() {
545 let config = FeatureConfig::comprehensive();
546 let extractor = FeatureExtractor::new(config);
547
548 let states = Array2::from_shape_fn((50, 16), |(i, j)| {
549 ((i + j) as f64 / 66.0).sin()
550 });
551
552 let features = extractor.extract(&states).unwrap();
553 assert_eq!(features.nrows(), 50);
554 assert!(features.ncols() > 16); }
556
557 #[test]
558 fn test_temporal_differences() {
559 let extractor = FeatureExtractor::default_extractor();
560
561 let states = Array2::from_shape_vec(
562 (3, 2),
563 vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0],
564 )
565 .unwrap();
566
567 let diffs = extractor.compute_temporal_differences(&states).unwrap();
568
569 assert_eq!(diffs[[0, 0]], 0.0);
570 assert_eq!(diffs[[0, 1]], 0.0);
571 assert!((diffs[[1, 0]] - 2.0).abs() < 1e-10);
572 assert!((diffs[[1, 1]] - 2.0).abs() < 1e-10);
573 }
574
575 #[test]
576 fn test_moments_computation() {
577 let extractor = FeatureExtractor::default_extractor();
578
579 let states = Array2::from_shape_fn((5, 10), |(i, _j)| i as f64);
580 let moments = extractor.compute_moments(&states).unwrap();
581
582 assert_eq!(moments.nrows(), 5);
583 assert_eq!(moments.ncols(), 4); }
585
586 #[test]
587 fn test_quantum_tomography() {
588 let config = ReservoirConfig::new(4).with_seed(42);
589 let reservoir = QuantumReservoir::new(config).unwrap();
590
591 let tomography = QuantumTomography::new(4);
592 let measurements = tomography.measure(&reservoir).unwrap();
593
594 assert!(measurements.len() > 0);
595 }
596
597 #[test]
598 fn test_expectation_computer() {
599 let config = ReservoirConfig::new(4).with_seed(42);
600 let reservoir = QuantumReservoir::new(config).unwrap();
601
602 let computer = ExpectationComputer::new(4);
603 let expectations = computer.compute(&reservoir).unwrap();
604
605 assert_eq!(expectations.len(), 16);
607
608 let sum: f64 = expectations.sum();
610 assert!((sum - 1.0).abs() < 1e-6);
611 }
612
613 #[test]
614 fn test_single_state_extraction() {
615 let extractor = FeatureExtractor::default_extractor();
616 let state = Array1::from_vec(vec![0.1, 0.2, 0.3, 0.4]);
617
618 let features = extractor.extract_single(&state).unwrap();
619 assert!(features.len() > 0);
620 }
621
622 #[test]
623 fn test_pairwise_correlations() {
624 let extractor = FeatureExtractor::default_extractor();
625 let states = Array2::from_shape_fn((10, 8), |(i, j)| {
626 ((i + j) as f64 / 18.0).sin()
627 });
628
629 let correlations = extractor.compute_pairwise_correlations(&states).unwrap();
630 assert_eq!(correlations.nrows(), 10);
631 }
632}