use super::config::{
DimensionalityReduction, FeatureExtractionConfig, FeatureNormalization, FeatureSelectionMethod,
};
use crate::applications::ApplicationResult;
use crate::ising::IsingModel;
use std::collections::HashMap;
use std::time::Instant;
#[derive(Debug, Clone)]
pub struct ProblemFeatures {
pub size: usize,
pub density: f64,
pub graph_features: GraphFeatures,
pub statistical_features: StatisticalFeatures,
pub spectral_features: SpectralFeatures,
pub domain_features: HashMap<String, f64>,
}
#[derive(Debug, Clone)]
pub struct GraphFeatures {
pub num_vertices: usize,
pub num_edges: usize,
pub avg_degree: f64,
pub clustering_coefficient: f64,
pub path_length_stats: PathLengthStats,
pub centrality_measures: CentralityMeasures,
}
#[derive(Debug, Clone)]
pub struct PathLengthStats {
pub avg_shortest_path: f64,
pub diameter: usize,
pub radius: usize,
pub eccentricity_stats: DistributionStats,
}
#[derive(Debug, Clone)]
pub struct CentralityMeasures {
pub degree_centrality: DistributionStats,
pub betweenness_centrality: DistributionStats,
pub closeness_centrality: DistributionStats,
pub eigenvector_centrality: DistributionStats,
}
#[derive(Debug, Clone)]
pub struct DistributionStats {
pub mean: f64,
pub std_dev: f64,
pub min: f64,
pub max: f64,
pub skewness: f64,
pub kurtosis: f64,
}
#[derive(Debug, Clone)]
pub struct StatisticalFeatures {
pub bias_stats: DistributionStats,
pub coupling_stats: DistributionStats,
pub energy_landscape: EnergyLandscapeFeatures,
pub correlation_features: CorrelationFeatures,
}
#[derive(Debug, Clone)]
pub struct EnergyLandscapeFeatures {
pub local_minima_estimate: usize,
pub energy_barriers: Vec<f64>,
pub ruggedness: f64,
pub basin_sizes: DistributionStats,
}
#[derive(Debug, Clone)]
pub struct CorrelationFeatures {
pub autocorrelation: Vec<f64>,
pub cross_correlation: HashMap<String, f64>,
pub mutual_information: f64,
}
#[derive(Debug, Clone)]
pub struct SpectralFeatures {
pub eigenvalue_stats: DistributionStats,
pub spectral_gap: f64,
pub spectral_radius: f64,
pub trace: f64,
pub condition_number: f64,
}
#[derive(Debug)]
pub struct FeatureExtractor {
pub config: FeatureExtractionConfig,
pub transformers: Vec<FeatureTransformer>,
pub selectors: Vec<FeatureSelector>,
pub reducers: Vec<DimensionalityReducer>,
}
#[derive(Debug)]
pub struct FeatureTransformer {
pub transformer_type: TransformerType,
pub parameters: HashMap<String, f64>,
pub is_fitted: bool,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum TransformerType {
Polynomial,
Interaction,
Logarithmic,
BoxCox,
Custom(String),
}
#[derive(Debug)]
pub struct FeatureSelector {
pub method: FeatureSelectionMethod,
pub selected_features: Vec<usize>,
pub importance_scores: Vec<f64>,
}
#[derive(Debug)]
pub struct DimensionalityReducer {
pub method: DimensionalityReduction,
pub target_dims: usize,
pub transformation_matrix: Option<Vec<Vec<f64>>>,
pub explained_variance: Vec<f64>,
}
impl FeatureExtractor {
#[must_use]
pub const fn new(config: FeatureExtractionConfig) -> Self {
Self {
config,
transformers: Vec::new(),
selectors: Vec::new(),
reducers: Vec::new(),
}
}
pub fn extract_features(&mut self, problem: &IsingModel) -> ApplicationResult<ProblemFeatures> {
let graph_features = if self.config.enable_graph_features {
self.extract_graph_features(problem)?
} else {
GraphFeatures::default()
};
let statistical_features = if self.config.enable_statistical_features {
self.extract_statistical_features(problem)?
} else {
StatisticalFeatures::default()
};
let spectral_features = if self.config.enable_spectral_features {
self.extract_spectral_features(problem)?
} else {
SpectralFeatures::default()
};
Ok(ProblemFeatures {
size: problem.num_qubits,
density: self.calculate_density(problem),
graph_features,
statistical_features,
spectral_features,
domain_features: HashMap::new(),
})
}
fn extract_graph_features(&self, problem: &IsingModel) -> ApplicationResult<GraphFeatures> {
let num_vertices = problem.num_qubits;
let mut num_edges = 0;
for i in 0..problem.num_qubits {
for j in (i + 1)..problem.num_qubits {
if problem.get_coupling(i, j).unwrap_or(0.0).abs() > 1e-10 {
num_edges += 1;
}
}
}
let avg_degree = if num_vertices > 0 {
2.0 * num_edges as f64 / num_vertices as f64
} else {
0.0
};
Ok(GraphFeatures {
num_vertices,
num_edges,
avg_degree,
clustering_coefficient: 0.1, path_length_stats: PathLengthStats {
avg_shortest_path: avg_degree.ln().max(1.0),
diameter: num_vertices / 2,
radius: num_vertices / 4,
eccentricity_stats: DistributionStats::default(),
},
centrality_measures: CentralityMeasures {
degree_centrality: DistributionStats::default(),
betweenness_centrality: DistributionStats::default(),
closeness_centrality: DistributionStats::default(),
eigenvector_centrality: DistributionStats::default(),
},
})
}
fn extract_statistical_features(
&self,
problem: &IsingModel,
) -> ApplicationResult<StatisticalFeatures> {
let mut bias_values = Vec::new();
let mut coupling_values = Vec::new();
for i in 0..problem.num_qubits {
bias_values.push(problem.get_bias(i).unwrap_or(0.0));
}
for i in 0..problem.num_qubits {
for j in (i + 1)..problem.num_qubits {
let coupling = problem.get_coupling(i, j).unwrap_or(0.0);
if coupling.abs() > 1e-10 {
coupling_values.push(coupling);
}
}
}
Ok(StatisticalFeatures {
bias_stats: self.calculate_distribution_stats(&bias_values),
coupling_stats: self.calculate_distribution_stats(&coupling_values),
energy_landscape: EnergyLandscapeFeatures {
local_minima_estimate: (problem.num_qubits as f64).sqrt() as usize,
energy_barriers: vec![1.0, 2.0, 3.0],
ruggedness: 0.5,
basin_sizes: DistributionStats::default(),
},
correlation_features: CorrelationFeatures {
autocorrelation: vec![1.0, 0.8, 0.6, 0.4, 0.2],
cross_correlation: HashMap::new(),
mutual_information: 0.3,
},
})
}
fn extract_spectral_features(
&self,
problem: &IsingModel,
) -> ApplicationResult<SpectralFeatures> {
let n = problem.num_qubits as f64;
let spectral_gap_estimate = 1.0 / n.sqrt();
Ok(SpectralFeatures {
eigenvalue_stats: DistributionStats {
mean: 0.0,
std_dev: 1.0,
min: -n,
max: n,
skewness: 0.0,
kurtosis: 3.0,
},
spectral_gap: spectral_gap_estimate,
spectral_radius: n,
trace: 0.0,
condition_number: n,
})
}
fn calculate_density(&self, problem: &IsingModel) -> f64 {
let mut num_edges = 0;
let max_edges = problem.num_qubits * (problem.num_qubits - 1) / 2;
for i in 0..problem.num_qubits {
for j in (i + 1)..problem.num_qubits {
if problem.get_coupling(i, j).unwrap_or(0.0).abs() > 1e-10 {
num_edges += 1;
}
}
}
if max_edges > 0 {
f64::from(num_edges) / max_edges as f64
} else {
0.0
}
}
fn calculate_distribution_stats(&self, values: &[f64]) -> DistributionStats {
if values.is_empty() {
return DistributionStats::default();
}
let mean = values.iter().sum::<f64>() / values.len() as f64;
let variance = values.iter().map(|x| (x - mean).powi(2)).sum::<f64>() / values.len() as f64;
let std_dev = variance.sqrt();
let min = values.iter().fold(f64::INFINITY, |a, &b| a.min(b));
let max = values.iter().fold(f64::NEG_INFINITY, |a, &b| a.max(b));
DistributionStats {
mean,
std_dev,
min,
max,
skewness: 0.0, kurtosis: 3.0, }
}
}
impl Default for DistributionStats {
fn default() -> Self {
Self {
mean: 0.0,
std_dev: 1.0,
min: 0.0,
max: 1.0,
skewness: 0.0,
kurtosis: 3.0,
}
}
}
impl Default for GraphFeatures {
fn default() -> Self {
Self {
num_vertices: 0,
num_edges: 0,
avg_degree: 0.0,
clustering_coefficient: 0.0,
path_length_stats: PathLengthStats {
avg_shortest_path: 0.0,
diameter: 0,
radius: 0,
eccentricity_stats: DistributionStats::default(),
},
centrality_measures: CentralityMeasures {
degree_centrality: DistributionStats::default(),
betweenness_centrality: DistributionStats::default(),
closeness_centrality: DistributionStats::default(),
eigenvector_centrality: DistributionStats::default(),
},
}
}
}
impl Default for StatisticalFeatures {
fn default() -> Self {
Self {
bias_stats: DistributionStats::default(),
coupling_stats: DistributionStats::default(),
energy_landscape: EnergyLandscapeFeatures {
local_minima_estimate: 0,
energy_barriers: Vec::new(),
ruggedness: 0.0,
basin_sizes: DistributionStats::default(),
},
correlation_features: CorrelationFeatures {
autocorrelation: Vec::new(),
cross_correlation: HashMap::new(),
mutual_information: 0.0,
},
}
}
}
impl Default for SpectralFeatures {
fn default() -> Self {
Self {
eigenvalue_stats: DistributionStats::default(),
spectral_gap: 0.0,
spectral_radius: 0.0,
trace: 0.0,
condition_number: 1.0,
}
}
}