#![allow(dead_code)]
#[cfg(feature = "dwave")]
use crate::compile::CompiledModel;
use crate::sampler::{SampleResult, Sampler, SamplerError, SamplerResult};
use scirs2_core::ndarray::{Array, Array2, IxDyn};
use scirs2_core::random::prelude::*;
use scirs2_core::random::prelude::*;
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
#[cfg(feature = "scirs")]
use crate::scirs_stub::{
scirs2_ml::{CrossValidation, RandomForest},
scirs2_optimization::bayesian::{AcquisitionFunction, BayesianOptimizer, KernelType},
};
pub trait SamplerPlugin: Send + Sync {
fn name(&self) -> &str;
fn version(&self) -> &str;
fn initialize(&mut self, config: &HashMap<String, String>) -> Result<(), String>;
fn create_sampler(&self) -> Box<dyn Sampler>;
fn default_config(&self) -> HashMap<String, String>;
fn validate_config(&self, config: &HashMap<String, String>) -> Result<(), String>;
}
pub struct PluginManager {
plugins: HashMap<String, Box<dyn SamplerPlugin>>,
configs: HashMap<String, HashMap<String, String>>,
}
impl Default for PluginManager {
fn default() -> Self {
Self::new()
}
}
impl PluginManager {
pub fn new() -> Self {
Self {
plugins: HashMap::new(),
configs: HashMap::new(),
}
}
pub fn register_plugin(&mut self, plugin: Box<dyn SamplerPlugin>) -> Result<(), String> {
let name = plugin.name().to_string();
if self.plugins.contains_key(&name) {
return Err(format!("Plugin {name} already registered"));
}
let default_config = plugin.default_config();
self.configs.insert(name.clone(), default_config);
self.plugins.insert(name, plugin);
Ok(())
}
pub fn configure_plugin(
&mut self,
name: &str,
config: HashMap<String, String>,
) -> Result<(), String> {
let plugin = self
.plugins
.get(name)
.ok_or_else(|| format!("Plugin {name} not found"))?;
plugin.validate_config(&config)?;
self.configs.insert(name.to_string(), config);
Ok(())
}
pub fn create_sampler(&mut self, name: &str) -> Result<Box<dyn Sampler>, String> {
let plugin = self
.plugins
.get_mut(name)
.ok_or_else(|| format!("Plugin {name} not found"))?;
let config = self.configs.get(name).cloned().unwrap_or_default();
plugin.initialize(&config)?;
Ok(plugin.create_sampler())
}
pub fn list_plugins(&self) -> Vec<PluginInfo> {
self.plugins
.values()
.map(|p| PluginInfo {
name: p.name().to_string(),
version: p.version().to_string(),
})
.collect()
}
}
#[derive(Debug, Clone)]
pub struct PluginInfo {
pub name: String,
pub version: String,
}
pub struct HyperparameterOptimizer {
search_space: HashMap<String, ParameterSpace>,
method: OptimizationMethod,
num_trials: usize,
cv_folds: usize,
}
#[derive(Debug, Clone)]
pub enum ParameterSpace {
Continuous { min: f64, max: f64, log_scale: bool },
Discrete { values: Vec<f64> },
Categorical { options: Vec<String> },
}
#[derive(Debug, Clone)]
pub enum OptimizationMethod {
RandomSearch,
GridSearch { resolution: usize },
#[cfg(feature = "scirs")]
Bayesian {
kernel: KernelType,
acquisition: AcquisitionFunction,
exploration: f64,
},
Evolutionary {
population_size: usize,
mutation_rate: f64,
},
}
impl HyperparameterOptimizer {
pub fn new(method: OptimizationMethod, num_trials: usize) -> Self {
Self {
search_space: HashMap::new(),
method,
num_trials,
cv_folds: 5,
}
}
pub fn add_parameter(&mut self, name: &str, space: ParameterSpace) {
self.search_space.insert(name.to_string(), space);
}
#[cfg(feature = "dwave")]
pub fn optimize<F>(
&self,
objective: F,
validation_problems: &[CompiledModel],
) -> Result<OptimizationResult, String>
where
F: Fn(&HashMap<String, f64>) -> Box<dyn Sampler>,
{
match &self.method {
OptimizationMethod::RandomSearch => self.random_search(objective, validation_problems),
OptimizationMethod::GridSearch { resolution } => {
self.grid_search(objective, validation_problems, *resolution)
}
#[cfg(feature = "scirs")]
OptimizationMethod::Bayesian {
kernel,
acquisition,
exploration,
} => self.bayesian_optimization(
objective,
validation_problems,
*kernel,
*acquisition,
*exploration,
),
OptimizationMethod::Evolutionary {
population_size,
mutation_rate,
} => self.evolutionary_optimization(
objective,
validation_problems,
*population_size,
*mutation_rate,
),
}
}
#[cfg(feature = "dwave")]
fn random_search<F>(
&self,
objective: F,
validation_problems: &[CompiledModel],
) -> Result<OptimizationResult, String>
where
F: Fn(&HashMap<String, f64>) -> Box<dyn Sampler>,
{
let mut rng = thread_rng();
let mut best_params = HashMap::new();
let mut best_score = f64::INFINITY;
let mut history = Vec::new();
for trial in 0..self.num_trials {
let mut params = self.sample_parameters(&mut rng)?;
let sampler = objective(¶ms);
let mut score = self.evaluate_sampler(sampler, validation_problems)?;
history.push(TrialResult {
parameters: params.clone(),
score,
iteration: trial,
});
if score < best_score {
best_score = score;
best_params = params;
}
}
let convergence_curve = self.compute_convergence_curve(&history);
Ok(OptimizationResult {
best_parameters: best_params,
best_score,
history,
convergence_curve,
})
}
#[cfg(feature = "dwave")]
fn grid_search<F>(
&self,
objective: F,
validation_problems: &[CompiledModel],
resolution: usize,
) -> Result<OptimizationResult, String>
where
F: Fn(&HashMap<String, f64>) -> Box<dyn Sampler>,
{
let grid_points = self.generate_grid(resolution)?;
let mut best_params = HashMap::new();
let mut best_score = f64::INFINITY;
let mut history = Vec::new();
for (i, params) in grid_points.iter().enumerate() {
let sampler = objective(params);
let mut score = self.evaluate_sampler(sampler, validation_problems)?;
history.push(TrialResult {
parameters: params.clone(),
score,
iteration: i,
});
if score < best_score {
best_score = score;
best_params = params.clone();
}
}
let convergence_curve = self.compute_convergence_curve(&history);
Ok(OptimizationResult {
best_parameters: best_params,
best_score,
history,
convergence_curve,
})
}
#[cfg(all(feature = "scirs", feature = "dwave"))]
fn bayesian_optimization<F>(
&self,
objective: F,
validation_problems: &[CompiledModel],
kernel: KernelType,
acquisition: AcquisitionFunction,
exploration: f64,
) -> Result<OptimizationResult, String>
where
F: Fn(&HashMap<String, f64>) -> Box<dyn Sampler>,
{
use scirs2_core::ndarray::Array1;
let dim = self.search_space.len();
let mut optimizer = BayesianOptimizer::new(dim, kernel, acquisition, exploration)
.map_err(|e| e.to_string())?;
let mut history = Vec::new();
let mut x_data = Vec::new();
let mut y_data = Vec::new();
let mut rng = thread_rng();
for _ in 0..std::cmp::min(10, self.num_trials / 4) {
let mut params = self.sample_parameters(&mut rng)?;
let sampler = objective(¶ms);
let mut score = self.evaluate_sampler(sampler, validation_problems)?;
let mut x = self.params_to_array(¶ms)?;
x_data.push(x);
y_data.push(score);
history.push(TrialResult {
parameters: params,
score,
iteration: history.len(),
});
}
let y_array = Array1::from_vec(y_data.clone());
optimizer
.update(&x_data, &y_array)
.map_err(|e| e.to_string())?;
for _ in history.len()..self.num_trials {
let x_next = optimizer.suggest_next().map_err(|e| e.to_string())?;
let mut params = self.array_to_params(&x_next)?;
let sampler = objective(¶ms);
let mut score = self.evaluate_sampler(sampler, validation_problems)?;
x_data.push(x_next);
y_data.push(score);
let y_array = Array1::from_vec(y_data.clone());
optimizer
.update(&x_data, &y_array)
.map_err(|e| e.to_string())?;
history.push(TrialResult {
parameters: params,
score,
iteration: history.len(),
});
}
let (best_idx, &best_score) = y_data
.iter()
.enumerate()
.min_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
.ok_or_else(|| "No optimization trials completed".to_string())?;
let best_params = self.array_to_params(&x_data[best_idx])?;
let convergence_curve = self.compute_convergence_curve(&history);
Ok(OptimizationResult {
best_parameters: best_params,
best_score,
history,
convergence_curve,
})
}
#[cfg(feature = "dwave")]
fn evolutionary_optimization<F>(
&self,
_objective: F,
_validation_problems: &[CompiledModel],
_population_size: usize,
_mutation_rate: f64,
) -> Result<OptimizationResult, String>
where
F: Fn(&HashMap<String, f64>) -> Box<dyn Sampler>,
{
Err("Evolutionary optimization not yet implemented".to_string())
}
fn sample_parameters(&self, rng: &mut impl Rng) -> Result<HashMap<String, f64>, String> {
let mut params = HashMap::new();
for (name, space) in &self.search_space {
let value = match space {
ParameterSpace::Continuous {
min,
max,
log_scale,
} => {
if *log_scale {
let log_min = min.ln();
let log_max = max.ln();
let log_val = rng.random_range(log_min..log_max);
log_val.exp()
} else {
rng.random_range(*min..*max)
}
}
ParameterSpace::Discrete { values } => values[rng.random_range(0..values.len())],
ParameterSpace::Categorical { options } => {
rng.random_range(0..options.len()) as f64
}
};
params.insert(name.clone(), value);
}
Ok(params)
}
fn generate_grid(&self, resolution: usize) -> Result<Vec<HashMap<String, f64>>, String> {
let mut grid_points = Vec::new();
let total_points = resolution.pow(self.search_space.len() as u32);
let mut rng = thread_rng();
for _ in 0..total_points.min(self.num_trials) {
grid_points.push(self.sample_parameters(&mut rng)?);
}
Ok(grid_points)
}
#[cfg(feature = "scirs")]
fn params_to_array(
&self,
params: &HashMap<String, f64>,
) -> Result<scirs2_core::ndarray::Array1<f64>, String> {
let mut values = Vec::new();
let mut names: Vec<_> = self.search_space.keys().collect();
names.sort();
for name in names {
values.push(params.get(name).copied().unwrap_or(0.0));
}
Ok(scirs2_core::ndarray::Array1::from_vec(values))
}
#[cfg(feature = "scirs")]
fn array_to_params(
&self,
array: &scirs2_core::ndarray::Array1<f64>,
) -> Result<HashMap<String, f64>, String> {
let mut params = HashMap::new();
let mut names: Vec<_> = self.search_space.keys().collect();
names.sort();
for (i, name) in names.iter().enumerate() {
params.insert((*name).clone(), array[i]);
}
Ok(params)
}
#[cfg(feature = "dwave")]
fn evaluate_sampler(
&self,
mut sampler: Box<dyn Sampler>,
problems: &[CompiledModel],
) -> Result<f64, String> {
let mut scores = Vec::new();
for problem in problems {
let mut qubo = problem.to_qubo();
let start = Instant::now();
let qubo_tuple = (qubo.to_dense_matrix(), qubo.variable_map());
let mut results = sampler
.run_qubo(&qubo_tuple, 100)
.map_err(|e| format!("Sampler error: {e:?}"))?;
let elapsed = start.elapsed();
let mut best_energy = results.first().map_or(f64::INFINITY, |r| r.energy);
let time_penalty = elapsed.as_secs_f64();
let mut score = 0.1f64.mul_add(time_penalty, best_energy);
scores.push(score);
}
Ok(scores.iter().sum::<f64>() / scores.len() as f64)
}
fn compute_convergence_curve(&self, history: &[TrialResult]) -> Vec<f64> {
let mut curve = Vec::new();
let mut best_so_far = f64::INFINITY;
for trial in history {
best_so_far = best_so_far.min(trial.score);
curve.push(best_so_far);
}
curve
}
}
#[derive(Debug, Clone)]
pub struct OptimizationResult {
pub best_parameters: HashMap<String, f64>,
pub best_score: f64,
pub history: Vec<TrialResult>,
pub convergence_curve: Vec<f64>,
}
#[derive(Debug, Clone)]
pub struct TrialResult {
pub parameters: HashMap<String, f64>,
pub score: f64,
pub iteration: usize,
}
pub struct EnsembleSampler {
samplers: Vec<Box<dyn Sampler>>,
method: EnsembleMethod,
weights: Option<Vec<f64>>,
}
#[derive(Debug, Clone)]
pub enum EnsembleMethod {
Voting,
WeightedVoting,
BestOf,
Sequential,
Parallel,
}
impl EnsembleSampler {
pub fn new(samplers: Vec<Box<dyn Sampler>>, method: EnsembleMethod) -> Self {
Self {
samplers,
method,
weights: None,
}
}
pub fn with_weights(mut self, weights: Vec<f64>) -> Self {
self.weights = Some(weights);
self
}
}
impl Sampler for EnsembleSampler {
fn run_qubo(
&self,
qubo: &(Array2<f64>, HashMap<String, usize>),
shots: usize,
) -> SamplerResult<Vec<SampleResult>> {
match &self.method {
EnsembleMethod::Voting => self.voting_ensemble(qubo, shots),
EnsembleMethod::WeightedVoting => self.weighted_voting_ensemble(qubo, shots),
EnsembleMethod::BestOf => self.best_of_ensemble(qubo, shots),
EnsembleMethod::Sequential => self.sequential_ensemble(qubo, shots),
EnsembleMethod::Parallel => self.parallel_ensemble(qubo, shots),
}
}
fn run_hobo(
&self,
hobo: &(Array<f64, IxDyn>, HashMap<String, usize>),
shots: usize,
) -> SamplerResult<Vec<SampleResult>> {
match &self.method {
EnsembleMethod::Voting => self.voting_ensemble_hobo(hobo, shots),
_ => Err(SamplerError::InvalidParameter(
"HOBO ensemble not fully implemented".to_string(),
)),
}
}
}
impl EnsembleSampler {
fn voting_ensemble(
&self,
qubo: &(Array2<f64>, HashMap<String, usize>),
shots: usize,
) -> SamplerResult<Vec<SampleResult>> {
let shots_per_sampler = shots / self.samplers.len();
let mut all_results = Vec::new();
for sampler in &self.samplers {
let results = sampler.run_qubo(qubo, shots_per_sampler)?;
all_results.extend(results);
}
let mut vote_counts: HashMap<Vec<bool>, (f64, usize)> = HashMap::new();
for result in all_results {
let state: Vec<bool> = qubo.1.keys().map(|var| result.assignments[var]).collect();
let entry = vote_counts.entry(state).or_insert((result.energy, 0));
entry.1 += result.occurrences;
}
let mut final_results: Vec<SampleResult> = vote_counts
.into_iter()
.map(|(state, (energy, count))| {
let assignments: HashMap<String, bool> = qubo
.1
.iter()
.zip(state.iter())
.map(|((var, _), &val)| (var.clone(), val))
.collect();
SampleResult {
assignments,
energy,
occurrences: count,
}
})
.collect();
final_results.sort_by(|a, b| {
a.energy
.partial_cmp(&b.energy)
.unwrap_or(std::cmp::Ordering::Equal)
});
Ok(final_results)
}
fn weighted_voting_ensemble(
&self,
qubo: &(Array2<f64>, HashMap<String, usize>),
shots: usize,
) -> SamplerResult<Vec<SampleResult>> {
let weights = self.weights.as_ref().ok_or_else(|| {
SamplerError::InvalidParameter("Weights not set for weighted voting".to_string())
})?;
if weights.len() != self.samplers.len() {
return Err(SamplerError::InvalidParameter(
"Number of weights must match number of samplers".to_string(),
));
}
let total_weight: f64 = weights.iter().sum();
let normalized: Vec<f64> = weights.iter().map(|&w| w / total_weight).collect();
let mut all_results = Vec::new();
for (sampler, &weight) in self.samplers.iter().zip(normalized.iter()) {
let sampler_shots = (shots as f64 * weight).round() as usize;
if sampler_shots > 0 {
let results = sampler.run_qubo(qubo, sampler_shots)?;
all_results.extend(results);
}
}
self.aggregate_results(all_results, &qubo.1)
}
fn best_of_ensemble(
&self,
qubo: &(Array2<f64>, HashMap<String, usize>),
shots: usize,
) -> SamplerResult<Vec<SampleResult>> {
let shots_per_sampler = shots / self.samplers.len();
let mut best_results = Vec::new();
let mut best_energy = f64::INFINITY;
for sampler in &self.samplers {
let results = sampler.run_qubo(qubo, shots_per_sampler)?;
if let Some(best) = results.first() {
if best.energy < best_energy {
best_energy = best.energy;
best_results = results;
}
}
}
Ok(best_results)
}
fn sequential_ensemble(
&self,
qubo: &(Array2<f64>, HashMap<String, usize>),
shots: usize,
) -> SamplerResult<Vec<SampleResult>> {
if self.samplers.is_empty() {
return Ok(Vec::new());
}
let mut current_best = self.samplers[0].run_qubo(qubo, shots)?;
for sampler in self.samplers.iter().skip(1) {
let refined = sampler.run_qubo(qubo, shots / self.samplers.len())?;
current_best.extend(refined);
current_best.sort_by(|a, b| {
a.energy
.partial_cmp(&b.energy)
.unwrap_or(std::cmp::Ordering::Equal)
});
current_best.truncate(shots);
}
Ok(current_best)
}
fn parallel_ensemble(
&self,
qubo: &(Array2<f64>, HashMap<String, usize>),
shots: usize,
) -> SamplerResult<Vec<SampleResult>> {
let shots_per_sampler = shots / self.samplers.len();
let _handles: Vec<std::thread::JoinHandle<()>> = Vec::new();
let mut all_results = Vec::new();
for sampler in &self.samplers {
let results = sampler.run_qubo(qubo, shots_per_sampler)?;
all_results.extend(results);
}
self.aggregate_results(all_results, &qubo.1)
}
fn aggregate_results(
&self,
results: Vec<SampleResult>,
var_map: &HashMap<String, usize>,
) -> SamplerResult<Vec<SampleResult>> {
let mut aggregated: HashMap<Vec<bool>, (f64, usize)> = HashMap::new();
for result in results {
let state: Vec<bool> = var_map.keys().map(|var| result.assignments[var]).collect();
let entry = aggregated.entry(state).or_insert((result.energy, 0));
entry.0 = entry.0.min(result.energy);
entry.1 += result.occurrences;
}
let mut final_results: Vec<SampleResult> = aggregated
.into_iter()
.map(|(state, (energy, count))| {
let assignments: HashMap<String, bool> = var_map
.iter()
.zip(state.iter())
.map(|((var, _), &val)| (var.clone(), val))
.collect();
SampleResult {
assignments,
energy,
occurrences: count,
}
})
.collect();
final_results.sort_by(|a, b| {
a.energy
.partial_cmp(&b.energy)
.unwrap_or(std::cmp::Ordering::Equal)
});
Ok(final_results)
}
fn voting_ensemble_hobo(
&self,
hobo: &(Array<f64, IxDyn>, HashMap<String, usize>),
shots: usize,
) -> SamplerResult<Vec<SampleResult>> {
let shots_per_sampler = shots / self.samplers.len();
let mut all_results = Vec::new();
for sampler in &self.samplers {
let results = sampler.run_hobo(hobo, shots_per_sampler)?;
all_results.extend(results);
}
self.aggregate_results(all_results, &hobo.1)
}
}
pub struct AdaptiveSampler<S: Sampler> {
base_sampler: S,
strategy: AdaptationStrategy,
history: Arc<Mutex<PerformanceHistory>>,
}
#[derive(Debug, Clone)]
pub enum AdaptationStrategy {
TemperatureAdaptive {
initial_range: (f64, f64),
adaptation_rate: f64,
},
PopulationAdaptive {
min_size: usize,
max_size: usize,
growth_rate: f64,
},
BanditAdaptive {
strategies: Vec<String>,
exploration_rate: f64,
},
RLAdaptive {
state_features: Vec<String>,
action_space: Vec<String>,
},
}
#[derive(Default)]
struct PerformanceHistory {
energies: Vec<f64>,
times: Vec<Duration>,
improvements: Vec<f64>,
parameters: Vec<HashMap<String, f64>>,
}
impl<S: Sampler> AdaptiveSampler<S> {
pub fn new(base_sampler: S, strategy: AdaptationStrategy) -> Self {
Self {
base_sampler,
strategy,
history: Arc::new(Mutex::new(PerformanceHistory::default())),
}
}
fn adapt_parameters(&self) -> HashMap<String, f64> {
let history = self
.history
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
match &self.strategy {
AdaptationStrategy::TemperatureAdaptive {
initial_range,
adaptation_rate,
} => {
let mut params = HashMap::new();
let (min_temp, max_temp) = initial_range;
let temp = if history.improvements.len() > 10 {
let recent_improvements: f64 =
history.improvements.iter().rev().take(10).sum::<f64>() / 10.0;
if recent_improvements < 0.1 {
min_temp + (max_temp - min_temp) * (1.0 - adaptation_rate)
} else {
min_temp + (max_temp - min_temp) * adaptation_rate
}
} else {
(min_temp + max_temp) / 2.0
};
params.insert("temperature".to_string(), temp);
params
}
_ => HashMap::new(),
}
}
}
impl<S: Sampler> Sampler for AdaptiveSampler<S> {
fn run_qubo(
&self,
qubo: &(Array2<f64>, HashMap<String, usize>),
shots: usize,
) -> SamplerResult<Vec<SampleResult>> {
let params = self.adapt_parameters();
let start = Instant::now();
let results = self.base_sampler.run_qubo(qubo, shots)?;
let elapsed = start.elapsed();
if let Some(best) = results.first() {
let mut history = self
.history
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
let improvement = if let Some(&last) = history.energies.last() {
(last - best.energy) / last.abs().max(1.0)
} else {
1.0
};
history.energies.push(best.energy);
history.times.push(elapsed);
history.improvements.push(improvement);
history.parameters.push(params);
}
Ok(results)
}
fn run_hobo(
&self,
hobo: &(Array<f64, IxDyn>, HashMap<String, usize>),
shots: usize,
) -> SamplerResult<Vec<SampleResult>> {
self.base_sampler.run_hobo(hobo, shots)
}
}
pub struct SamplerCrossValidation {
n_folds: usize,
metric: EvaluationMetric,
}
#[derive(Debug, Clone)]
pub enum EvaluationMetric {
BestEnergy,
TopKAverage(usize),
TimeToSolution(f64),
SuccessProbability(f64),
}
impl SamplerCrossValidation {
pub const fn new(n_folds: usize, metric: EvaluationMetric) -> Self {
Self { n_folds, metric }
}
#[cfg(feature = "dwave")]
pub fn evaluate<S: Sampler>(
&self,
sampler: &S,
problems: &[CompiledModel],
shots_per_problem: usize,
) -> Result<CrossValidationResult, String> {
let n_problems = problems.len();
let fold_size = n_problems / self.n_folds;
let mut fold_scores = Vec::new();
for fold in 0..self.n_folds {
let test_start = fold * fold_size;
let test_end = if fold == self.n_folds - 1 {
n_problems
} else {
(fold + 1) * fold_size
};
let test_problems = &problems[test_start..test_end];
let mut scores = Vec::new();
for problem in test_problems {
let mut score = self.evaluate_single(sampler, problem, shots_per_problem)?;
scores.push(score);
}
let fold_score = scores.iter().sum::<f64>() / scores.len() as f64;
fold_scores.push(fold_score);
}
let mean_score = fold_scores.iter().sum::<f64>() / fold_scores.len() as f64;
let variance = fold_scores
.iter()
.map(|&s| (s - mean_score).powi(2))
.sum::<f64>()
/ fold_scores.len() as f64;
Ok(CrossValidationResult {
mean_score,
std_error: variance.sqrt(),
fold_scores,
})
}
#[cfg(feature = "dwave")]
fn evaluate_single<S: Sampler>(
&self,
sampler: &S,
problem: &CompiledModel,
shots: usize,
) -> Result<f64, String> {
let mut qubo = problem.to_qubo();
let qubo_tuple = (qubo.to_dense_matrix(), qubo.variable_map());
let start = Instant::now();
let mut results = sampler
.run_qubo(&qubo_tuple, shots)
.map_err(|e| format!("Sampler error: {e:?}"))?;
let elapsed = start.elapsed();
match &self.metric {
EvaluationMetric::BestEnergy => Ok(results.first().map_or(f64::INFINITY, |r| r.energy)),
EvaluationMetric::TopKAverage(k) => {
let sum: f64 = results.iter().take(*k).map(|r| r.energy).sum();
Ok(sum / (*k).min(results.len()) as f64)
}
EvaluationMetric::TimeToSolution(threshold) => {
let found = results.iter().any(|r| r.energy <= *threshold);
Ok(if found {
elapsed.as_secs_f64()
} else {
f64::INFINITY
})
}
EvaluationMetric::SuccessProbability(threshold) => {
let successes = results
.iter()
.filter(|r| r.energy <= *threshold)
.map(|r| r.occurrences)
.sum::<usize>();
Ok(successes as f64 / shots as f64)
}
}
}
}
#[derive(Debug, Clone)]
pub struct CrossValidationResult {
pub mean_score: f64,
pub std_error: f64,
pub fold_scores: Vec<f64>,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::sampler::SASampler;
#[test]
fn test_plugin_manager() {
let manager = PluginManager::new();
assert_eq!(manager.list_plugins().len(), 0);
}
#[test]
fn test_hyperparameter_space() {
let mut optimizer = HyperparameterOptimizer::new(OptimizationMethod::RandomSearch, 10);
optimizer.add_parameter(
"temperature",
ParameterSpace::Continuous {
min: 0.1,
max: 10.0,
log_scale: true,
},
);
optimizer.add_parameter(
"sweeps",
ParameterSpace::Discrete {
values: vec![100.0, 500.0, 1000.0],
},
);
}
#[test]
fn test_ensemble_sampler() {
let samplers: Vec<Box<dyn Sampler>> = vec![
Box::new(SASampler::new(Some(42))),
Box::new(SASampler::new(Some(43))),
];
let ensemble = EnsembleSampler::new(samplers, EnsembleMethod::Voting);
}
}