use super::config::*;
use crate::error::{MLError, Result};
use crate::qnn::{QNNLayerType, QuantumNeuralNetwork};
use scirs2_core::ndarray::{s, Array1, Array2};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::f64::consts::PI;
#[derive(Debug, Clone)]
pub struct QuantumFeatureExtractor {
config: FeatureEngineeringConfig,
feature_circuits: Vec<Vec<f64>>,
transform_network: QuantumNeuralNetwork,
fourier_generator: Option<QuantumFourierFeatures>,
wavelet_transformer: Option<QuantumWaveletTransform>,
feature_stats: FeatureStatistics,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct QuantumFourierFeatures {
num_components: usize,
frequency_ranges: Vec<(f64, f64)>,
qft_circuit: Vec<f64>,
learned_frequencies: Array1<f64>,
phase_relationships: Array2<f64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct QuantumWaveletTransform {
wavelet_type: WaveletType,
num_levels: usize,
wavelet_circuits: Vec<Vec<f64>>,
threshold: f64,
coefficients: Vec<Array2<f64>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FeatureStatistics {
pub means: Array1<f64>,
pub stds: Array1<f64>,
pub ranges: Array1<f64>,
pub correlations: Array2<f64>,
pub entanglement_measures: Array1<f64>,
}
#[derive(Debug, Clone)]
pub struct LagFeatureGenerator {
lag_periods: Vec<usize>,
feature_names: Vec<String>,
}
#[derive(Debug, Clone)]
pub struct RollingStatsCalculator {
window_sizes: Vec<usize>,
stats_types: Vec<StatType>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum StatType {
Mean,
Std,
Min,
Max,
Median,
Quantile(f64),
Skewness,
Kurtosis,
}
#[derive(Debug, Clone)]
pub struct InteractionFeatureGenerator {
max_interaction_order: usize,
interaction_types: Vec<InteractionType>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum InteractionType {
Multiplication,
Division,
Addition,
Subtraction,
QuantumEntanglement,
}
impl QuantumFeatureExtractor {
pub fn new(config: FeatureEngineeringConfig, num_qubits: usize) -> Result<Self> {
let mut feature_circuits = Vec::new();
for circuit_idx in 0..5 {
let mut circuit_params = Vec::new();
for qubit_idx in 0..num_qubits {
circuit_params.push(1.0); circuit_params.push(PI * circuit_idx as f64 / 5.0); }
for qubit_idx in 0..num_qubits.saturating_sub(1) {
circuit_params.push(2.0); circuit_params.push(PI / 4.0 * qubit_idx as f64); }
feature_circuits.push(circuit_params);
}
let layers = vec![
QNNLayerType::EncodingLayer { num_features: 100 },
QNNLayerType::VariationalLayer { num_params: 50 },
QNNLayerType::MeasurementLayer {
measurement_basis: "computational".to_string(),
},
];
let transform_network = QuantumNeuralNetwork::new(layers, num_qubits, 100, 50)?;
let fourier_generator = if config.quantum_fourier_features {
Some(QuantumFourierFeatures::new(
20,
vec![(0.1, 10.0), (10.0, 100.0)],
num_qubits,
)?)
} else {
None
};
let wavelet_transformer = if config.wavelet_decomposition {
Some(QuantumWaveletTransform::new(
WaveletType::Daubechies(4),
3,
num_qubits,
)?)
} else {
None
};
let feature_stats = FeatureStatistics::new();
Ok(Self {
config,
feature_circuits,
transform_network,
fourier_generator,
wavelet_transformer,
feature_stats,
})
}
pub fn extract_features(&self, data: &Array2<f64>) -> Result<Array2<f64>> {
let mut features = data.clone();
features = self.add_lag_features(&features)?;
features = self.add_rolling_features(&features)?;
if let Some(ref fourier_gen) = self.fourier_generator {
features = fourier_gen.transform(&features)?;
}
if let Some(ref wavelet_trans) = self.wavelet_transformer {
features = wavelet_trans.decompose(&features)?;
}
if self.config.interaction_features {
features = self.add_interaction_features(&features)?;
}
features = self.apply_quantum_transformation(&features)?;
features = self.normalize_features(&features)?;
Ok(features)
}
fn add_lag_features(&self, data: &Array2<f64>) -> Result<Array2<f64>> {
if self.config.lag_features.is_empty() {
return Ok(data.clone());
}
let (n_samples, n_features) = data.dim();
let total_lag_features = self.config.lag_features.len() * n_features;
let mut enhanced_data = Array2::zeros((n_samples, n_features + total_lag_features));
enhanced_data.slice_mut(s![.., 0..n_features]).assign(data);
let mut feature_offset = n_features;
for &lag in &self.config.lag_features {
for feature_idx in 0..n_features {
for sample_idx in lag..n_samples {
enhanced_data[[sample_idx, feature_offset]] =
data[[sample_idx - lag, feature_idx]];
}
feature_offset += 1;
}
}
Ok(enhanced_data)
}
fn add_rolling_features(&self, data: &Array2<f64>) -> Result<Array2<f64>> {
if self.config.rolling_windows.is_empty() {
return Ok(data.clone());
}
let (n_samples, n_features) = data.dim();
let stats_per_window = 3; let total_rolling_features =
self.config.rolling_windows.len() * n_features * stats_per_window;
let mut enhanced_data = Array2::zeros((n_samples, n_features + total_rolling_features));
enhanced_data.slice_mut(s![.., 0..n_features]).assign(data);
let mut feature_offset = n_features;
for &window_size in &self.config.rolling_windows {
for feature_idx in 0..n_features {
for sample_idx in window_size..n_samples {
let window_start = sample_idx.saturating_sub(window_size);
let window_data = data.slice(s![window_start..sample_idx, feature_idx]);
enhanced_data[[sample_idx, feature_offset]] = window_data.mean().unwrap_or(0.0);
enhanced_data[[sample_idx, feature_offset + 1]] = window_data.std(1.0);
enhanced_data[[sample_idx, feature_offset + 2]] =
window_data.iter().fold(f64::NEG_INFINITY, |a, &b| a.max(b));
}
feature_offset += stats_per_window;
}
}
Ok(enhanced_data)
}
fn add_interaction_features(&self, data: &Array2<f64>) -> Result<Array2<f64>> {
let (n_samples, n_features) = data.dim();
if n_features < 2 {
return Ok(data.clone());
}
let n_interactions = n_features * (n_features - 1) / 2;
let mut enhanced_data = Array2::zeros((n_samples, n_features + n_interactions));
enhanced_data.slice_mut(s![.., 0..n_features]).assign(data);
let mut interaction_idx = n_features;
for i in 0..n_features {
for j in (i + 1)..n_features {
for sample_idx in 0..n_samples {
enhanced_data[[sample_idx, interaction_idx]] =
data[[sample_idx, i]] * data[[sample_idx, j]];
}
interaction_idx += 1;
}
}
Ok(enhanced_data)
}
fn apply_quantum_transformation(&self, data: &Array2<f64>) -> Result<Array2<f64>> {
if !self.config.quantum_features {
return Ok(data.clone());
}
let mut quantum_features = Array2::zeros((data.nrows(), self.transform_network.output_dim));
for (i, row) in data.rows().into_iter().enumerate() {
let row_vec = row.to_owned();
let transformed = self.transform_network.forward(&row_vec)?;
quantum_features.row_mut(i).assign(&transformed);
}
let (n_samples, n_features) = data.dim();
let mut combined_features =
Array2::zeros((n_samples, n_features + quantum_features.ncols()));
combined_features
.slice_mut(s![.., 0..n_features])
.assign(data);
combined_features
.slice_mut(s![.., n_features..])
.assign(&quantum_features);
Ok(combined_features)
}
fn normalize_features(&self, data: &Array2<f64>) -> Result<Array2<f64>> {
let mut normalized = data.clone();
for j in 0..data.ncols() {
let column = data.column(j);
let mean = column.mean().unwrap_or(0.0);
let std = column.std(1.0).max(1e-8);
for i in 0..data.nrows() {
normalized[[i, j]] = (data[[i, j]] - mean) / std;
}
}
Ok(normalized)
}
pub fn fit_statistics(&mut self, data: &Array2<f64>) -> Result<()> {
self.feature_stats.compute_statistics(data)?;
Ok(())
}
pub fn get_feature_importance(&self) -> Result<Array1<f64>> {
Ok(self.feature_stats.entanglement_measures.clone())
}
}
impl QuantumFourierFeatures {
pub fn new(
num_components: usize,
frequency_ranges: Vec<(f64, f64)>,
num_qubits: usize,
) -> Result<Self> {
let mut qft_circuit = Vec::new();
for qubit_idx in 0..num_qubits {
qft_circuit.push(1.0); }
for i in 0..num_qubits {
for j in (i + 1)..num_qubits {
let phase = PI / 2_f64.powi((j - i) as i32);
qft_circuit.push(phase);
}
}
let learned_frequencies = Array1::from_shape_fn(num_components, |i| 0.1 + i as f64 * 0.1);
let phase_relationships = Array2::zeros((num_components, num_components));
Ok(Self {
num_components,
frequency_ranges,
qft_circuit,
learned_frequencies,
phase_relationships,
})
}
pub fn transform(&self, data: &Array2<f64>) -> Result<Array2<f64>> {
let (n_samples, n_features) = data.dim();
let fourier_features_count = self.num_components * 2; let mut fourier_features = Array2::zeros((n_samples, n_features + fourier_features_count));
fourier_features
.slice_mut(s![.., 0..n_features])
.assign(data);
for i in 0..n_samples {
for (j, &freq) in self.learned_frequencies.iter().enumerate() {
let phase = i as f64 * freq * 2.0 * PI / n_samples as f64;
let quantum_phase = self.apply_quantum_phase_enhancement(phase, j)?;
fourier_features[[i, n_features + 2 * j]] = quantum_phase.sin();
fourier_features[[i, n_features + 2 * j + 1]] = quantum_phase.cos();
}
}
Ok(fourier_features)
}
fn apply_quantum_phase_enhancement(&self, phase: f64, component_idx: usize) -> Result<f64> {
let mut enhanced_phase = phase;
if component_idx < self.qft_circuit.len() {
let circuit_param = self.qft_circuit[component_idx % self.qft_circuit.len()];
enhanced_phase = phase * circuit_param + 0.1 * (phase * circuit_param).sin();
}
Ok(enhanced_phase)
}
pub fn learn_frequencies(&mut self, data: &Array2<f64>) -> Result<()> {
for i in 0..self.num_components.min(data.ncols()) {
let column = data.column(i % data.ncols());
let estimated_freq = self.estimate_dominant_frequency(&column)?;
self.learned_frequencies[i] = estimated_freq;
}
Ok(())
}
fn estimate_dominant_frequency(
&self,
signal: &scirs2_core::ndarray::ArrayView1<f64>,
) -> Result<f64> {
let n = signal.len();
let mut max_power = 0.0;
let mut dominant_freq = 0.1;
for k in 1..n / 2 {
let freq = k as f64 / n as f64;
let mut power = 0.0;
for (i, &value) in signal.iter().enumerate() {
power += value * (2.0 * PI * freq * i as f64).cos();
}
if power.abs() > max_power {
max_power = power.abs();
dominant_freq = freq;
}
}
Ok(dominant_freq)
}
}
impl QuantumWaveletTransform {
pub fn new(wavelet_type: WaveletType, num_levels: usize, num_qubits: usize) -> Result<Self> {
let mut wavelet_circuits = Vec::new();
for level in 0..num_levels {
let mut circuit_params = Vec::new();
for qubit_idx in 0..num_qubits / 2 {
circuit_params.push(1.0); circuit_params.push(PI / 4.0 * (level + 1) as f64); }
for qubit_idx in 0..num_qubits / 2 {
circuit_params.push(2.0_f64.powi(-(level as i32))); }
wavelet_circuits.push(circuit_params);
}
Ok(Self {
wavelet_type,
num_levels,
wavelet_circuits,
threshold: 0.1,
coefficients: Vec::new(),
})
}
pub fn decompose(&self, data: &Array2<f64>) -> Result<Array2<f64>> {
let mut decomposed = data.clone();
for level in 0..self.num_levels {
decomposed = self.apply_wavelet_level(&decomposed, level)?;
}
self.apply_threshold(&mut decomposed);
Ok(decomposed)
}
fn apply_wavelet_level(&self, data: &Array2<f64>, level: usize) -> Result<Array2<f64>> {
if level >= self.wavelet_circuits.len() {
return Ok(data.clone());
}
let circuit = &self.wavelet_circuits[level];
let mut result = data.clone();
for i in 0..data.nrows() {
for j in 0..data.ncols() {
let mut value = data[[i, j]];
for (k, ¶m) in circuit.iter().enumerate() {
let scale = 2.0_f64.powi(-(level as i32));
let wavelet_value = self.wavelet_function(value * scale, param)?;
value = value * 0.7 + wavelet_value * 0.3; }
result[[i, j]] = value;
}
}
Ok(result)
}
fn wavelet_function(&self, x: f64, quantum_param: f64) -> Result<f64> {
match self.wavelet_type {
WaveletType::Haar => {
let classical_haar = if x >= 0.0 && x < 0.5 {
1.0
} else if x >= 0.5 && x < 1.0 {
-1.0
} else {
0.0
};
let quantum_enhancement = (quantum_param * x).sin() * 0.1;
Ok(classical_haar + quantum_enhancement)
}
WaveletType::Daubechies(_) => {
let classical = (PI * x).sin() * (-x * x / 2.0).exp();
let quantum_enhancement = (quantum_param * x * PI).cos() * 0.05;
Ok(classical + quantum_enhancement)
}
WaveletType::Quantum => {
let quantum_phase = quantum_param * x * PI;
Ok(quantum_phase.sin() * (-x * x).exp())
}
_ => {
Ok((PI * x).sin() * (-x * x / 2.0).exp())
}
}
}
fn apply_threshold(&self, data: &mut Array2<f64>) {
for value in data.iter_mut() {
if value.abs() < self.threshold {
*value = 0.0;
}
}
}
}
impl FeatureStatistics {
pub fn new() -> Self {
Self {
means: Array1::zeros(0),
stds: Array1::zeros(0),
ranges: Array1::zeros(0),
correlations: Array2::zeros((0, 0)),
entanglement_measures: Array1::zeros(0),
}
}
pub fn compute_statistics(&mut self, data: &Array2<f64>) -> Result<()> {
let (n_samples, n_features) = data.dim();
self.means = Array1::zeros(n_features);
self.stds = Array1::zeros(n_features);
self.ranges = Array1::zeros(n_features);
for j in 0..n_features {
let column = data.column(j);
self.means[j] = column.mean().unwrap_or(0.0);
self.stds[j] = column.std(1.0);
let min_val = column.iter().fold(f64::INFINITY, |a, &b| a.min(b));
let max_val = column.iter().fold(f64::NEG_INFINITY, |a, &b| a.max(b));
self.ranges[j] = max_val - min_val;
}
self.correlations = Array2::zeros((n_features, n_features));
for i in 0..n_features {
for j in 0..n_features {
let corr = self.compute_correlation(data, i, j)?;
self.correlations[[i, j]] = corr;
}
}
self.entanglement_measures = Array1::zeros(n_features);
for j in 0..n_features {
let entanglement = self.compute_quantum_entanglement(data, j)?;
self.entanglement_measures[j] = entanglement;
}
Ok(())
}
fn compute_correlation(&self, data: &Array2<f64>, i: usize, j: usize) -> Result<f64> {
let col_i = data.column(i);
let col_j = data.column(j);
let mean_i = col_i.mean().unwrap_or(0.0);
let mean_j = col_j.mean().unwrap_or(0.0);
let mut numerator = 0.0;
let mut sum_sq_i = 0.0;
let mut sum_sq_j = 0.0;
for (val_i, val_j) in col_i.iter().zip(col_j.iter()) {
let dev_i = val_i - mean_i;
let dev_j = val_j - mean_j;
numerator += dev_i * dev_j;
sum_sq_i += dev_i * dev_i;
sum_sq_j += dev_j * dev_j;
}
let denominator = (sum_sq_i * sum_sq_j).sqrt();
if denominator < 1e-10 {
Ok(0.0)
} else {
Ok(numerator / denominator)
}
}
fn compute_quantum_entanglement(&self, data: &Array2<f64>, feature_idx: usize) -> Result<f64> {
let column = data.column(feature_idx);
let mut entropy = 0.0;
let n_bins = 10;
let min_val = column.iter().fold(f64::INFINITY, |a, &b| a.min(b));
let max_val = column.iter().fold(f64::NEG_INFINITY, |a, &b| a.max(b));
let range = max_val - min_val;
if range > 1e-10 {
let mut bin_counts = vec![0; n_bins];
for &value in column.iter() {
let bin_idx = ((value - min_val) / range * (n_bins - 1) as f64) as usize;
let bin_idx = bin_idx.min(n_bins - 1);
bin_counts[bin_idx] += 1;
}
let n_total = column.len() as f64;
for &count in &bin_counts {
if count > 0 {
let prob = count as f64 / n_total;
entropy -= prob * prob.ln();
}
}
}
Ok(entropy / n_bins as f64) }
}
pub struct QuantumFeatureSelector {
selection_method: FeatureSelectionMethod,
max_features: Option<usize>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum FeatureSelectionMethod {
QuantumMutualInformation,
QuantumEntanglement,
VariationalImportance,
HybridSelection,
}
impl QuantumFeatureSelector {
pub fn new(method: FeatureSelectionMethod, max_features: Option<usize>) -> Self {
Self {
selection_method: method,
max_features,
}
}
pub fn select_features(&self, data: &Array2<f64>, target: &Array1<f64>) -> Result<Vec<usize>> {
match self.selection_method {
FeatureSelectionMethod::QuantumMutualInformation => {
self.quantum_mutual_information_selection(data, target)
}
FeatureSelectionMethod::QuantumEntanglement => {
self.quantum_entanglement_selection(data, target)
}
FeatureSelectionMethod::VariationalImportance => {
self.variational_importance_selection(data, target)
}
FeatureSelectionMethod::HybridSelection => self.hybrid_selection(data, target),
}
}
fn quantum_mutual_information_selection(
&self,
data: &Array2<f64>,
target: &Array1<f64>,
) -> Result<Vec<usize>> {
let n_features = data.ncols();
let mut feature_scores = Vec::new();
for feature_idx in 0..n_features {
let column = data.column(feature_idx);
let mutual_info = self.compute_quantum_mutual_information(&column, target)?;
feature_scores.push((feature_idx, mutual_info));
}
feature_scores.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
let num_to_select = self.max_features.unwrap_or(n_features).min(n_features);
Ok(feature_scores
.into_iter()
.take(num_to_select)
.map(|(idx, _)| idx)
.collect())
}
fn compute_quantum_mutual_information(
&self,
feature: &scirs2_core::ndarray::ArrayView1<f64>,
target: &Array1<f64>,
) -> Result<f64> {
let mut mutual_info = 0.0;
let n_bins = 5;
let feature_bins = self.discretize_values(feature, n_bins)?;
let target_bins = self.discretize_values(&target.view(), n_bins)?;
let n_samples = feature.len();
let mut joint_counts = HashMap::new();
let mut feature_counts = HashMap::new();
let mut target_counts = HashMap::new();
for i in 0..n_samples {
let f_bin = feature_bins[i];
let t_bin = target_bins[i];
*joint_counts.entry((f_bin, t_bin)).or_insert(0) += 1;
*feature_counts.entry(f_bin).or_insert(0) += 1;
*target_counts.entry(t_bin).or_insert(0) += 1;
}
for ((f_bin, t_bin), &joint_count) in &joint_counts {
let joint_prob = joint_count as f64 / n_samples as f64;
let feature_prob = *feature_counts.get(f_bin).unwrap_or(&0) as f64 / n_samples as f64;
let target_prob = *target_counts.get(t_bin).unwrap_or(&0) as f64 / n_samples as f64;
if joint_prob > 0.0 && feature_prob > 0.0 && target_prob > 0.0 {
let classical_mi = joint_prob * (joint_prob / (feature_prob * target_prob)).ln();
let quantum_factor = 1.0 + 0.1 * (joint_prob * PI).sin().abs();
mutual_info += classical_mi * quantum_factor;
}
}
Ok(mutual_info)
}
fn discretize_values(
&self,
values: &scirs2_core::ndarray::ArrayView1<f64>,
n_bins: usize,
) -> Result<Vec<usize>> {
let min_val = values.iter().fold(f64::INFINITY, |a, &b| a.min(b));
let max_val = values.iter().fold(f64::NEG_INFINITY, |a, &b| a.max(b));
let range = max_val - min_val;
let mut bins = Vec::new();
for &value in values.iter() {
let bin_idx = if range > 1e-10 {
((value - min_val) / range * (n_bins - 1) as f64) as usize
} else {
0
};
bins.push(bin_idx.min(n_bins - 1));
}
Ok(bins)
}
fn quantum_entanglement_selection(
&self,
data: &Array2<f64>,
target: &Array1<f64>,
) -> Result<Vec<usize>> {
Ok((0..data.ncols()).collect())
}
fn variational_importance_selection(
&self,
data: &Array2<f64>,
target: &Array1<f64>,
) -> Result<Vec<usize>> {
Ok((0..data.ncols()).collect())
}
fn hybrid_selection(&self, data: &Array2<f64>, target: &Array1<f64>) -> Result<Vec<usize>> {
self.quantum_mutual_information_selection(data, target)
}
}