quantrs2_ml/
anneal_integration.rs

1//! Integration with annealing module for QUBO problems in quantum ML
2//!
3//! This module provides seamless integration between quantum ML algorithms
4//! and the QuantRS2 annealing module, enabling optimization of quantum ML
5//! models using quantum annealing and classical optimization techniques.
6
7use crate::error::{MLError, Result};
8use ndarray::{Array1, Array2};
9use quantrs2_anneal::{ising::IsingModel, qubo::QuboBuilder, simulator::*};
10use std::collections::HashMap;
11
12/// QUBO formulation of quantum ML optimization problems
13pub struct QuantumMLQUBO {
14    /// QUBO matrix
15    qubo_matrix: Array2<f64>,
16    /// Problem description
17    description: String,
18    /// Variable mapping
19    variable_map: HashMap<String, usize>,
20    /// Objective function offset
21    offset: f64,
22}
23
24impl QuantumMLQUBO {
25    /// Create new QUBO formulation
26    pub fn new(size: usize, description: impl Into<String>) -> Self {
27        Self {
28            qubo_matrix: Array2::zeros((size, size)),
29            description: description.into(),
30            variable_map: HashMap::new(),
31            offset: 0.0,
32        }
33    }
34
35    /// Set QUBO coefficient
36    pub fn set_coefficient(&mut self, i: usize, j: usize, value: f64) -> Result<()> {
37        if i >= self.qubo_matrix.nrows() || j >= self.qubo_matrix.ncols() {
38            return Err(MLError::InvalidConfiguration(
39                "Index out of bounds".to_string(),
40            ));
41        }
42        self.qubo_matrix[[i, j]] = value;
43        Ok(())
44    }
45
46    /// Add variable mapping
47    pub fn add_variable(&mut self, name: impl Into<String>, index: usize) {
48        self.variable_map.insert(name.into(), index);
49    }
50
51    /// Get QUBO matrix
52    pub fn qubo_matrix(&self) -> &Array2<f64> {
53        &self.qubo_matrix
54    }
55
56    /// Convert to Ising problem
57    pub fn to_ising(&self) -> IsingProblem {
58        // Convert QUBO to Ising using standard transformation
59        let n = self.qubo_matrix.nrows();
60        let mut h = Array1::zeros(n);
61        let mut j = Array2::zeros((n, n));
62        let mut offset = self.offset;
63
64        // Standard QUBO to Ising transformation
65        for i in 0..n {
66            h[i] = self.qubo_matrix[[i, i]];
67            for k in 0..n {
68                if k != i {
69                    h[i] += 0.5 * self.qubo_matrix[[i, k]];
70                }
71            }
72            offset += 0.5 * self.qubo_matrix[[i, i]];
73        }
74
75        for i in 0..n {
76            for k in i + 1..n {
77                j[[i, k]] = 0.25 * self.qubo_matrix[[i, k]];
78            }
79        }
80
81        IsingProblem::new(h, j, offset)
82    }
83}
84
85/// Ising problem representation
86#[derive(Debug, Clone)]
87pub struct IsingProblem {
88    /// Local magnetic fields
89    pub h: Array1<f64>,
90    /// Coupling strengths
91    pub j: Array2<f64>,
92    /// Energy offset
93    pub offset: f64,
94}
95
96impl IsingProblem {
97    /// Create new Ising problem
98    pub fn new(h: Array1<f64>, j: Array2<f64>, offset: f64) -> Self {
99        Self { h, j, offset }
100    }
101
102    /// Compute energy of a configuration
103    pub fn energy(&self, spins: &[i8]) -> f64 {
104        let mut energy = self.offset;
105
106        // Linear terms
107        for (i, &spin) in spins.iter().enumerate() {
108            energy -= self.h[i] * spin as f64;
109        }
110
111        // Quadratic terms
112        for i in 0..spins.len() {
113            for j in i + 1..spins.len() {
114                energy -= self.j[[i, j]] * spins[i] as f64 * spins[j] as f64;
115            }
116        }
117
118        energy
119    }
120}
121
122/// Quantum ML annealing optimizer
123pub struct QuantumMLAnnealer {
124    /// Annealing parameters
125    params: AnnealingParams,
126    /// Problem embedding
127    embedding: Option<Embedding>,
128    /// Anneal client
129    client: Option<Box<dyn AnnealingClient>>,
130}
131
132/// Annealing parameters
133#[derive(Debug, Clone)]
134pub struct AnnealingParams {
135    /// Number of annealing sweeps
136    pub num_sweeps: usize,
137    /// Annealing schedule
138    pub schedule: AnnealingSchedule,
139    /// Temperature range
140    pub temperature_range: (f64, f64),
141    /// Number of chains
142    pub num_chains: usize,
143    /// Chain strength
144    pub chain_strength: f64,
145}
146
147impl Default for AnnealingParams {
148    fn default() -> Self {
149        Self {
150            num_sweeps: 1000,
151            schedule: AnnealingSchedule::Linear,
152            temperature_range: (0.01, 10.0),
153            num_chains: 1,
154            chain_strength: 1.0,
155        }
156    }
157}
158
159/// Annealing schedule types
160#[derive(Debug, Clone)]
161pub enum AnnealingSchedule {
162    /// Linear schedule
163    Linear,
164    /// Exponential schedule
165    Exponential,
166    /// Custom schedule
167    Custom(Vec<f64>),
168}
169
170/// Problem embedding for hardware
171#[derive(Debug, Clone)]
172pub struct Embedding {
173    /// Logical to physical qubit mapping
174    pub logical_to_physical: HashMap<usize, Vec<usize>>,
175    /// Physical to logical qubit mapping
176    pub physical_to_logical: HashMap<usize, usize>,
177}
178
179impl Embedding {
180    /// Create identity embedding
181    pub fn identity(num_qubits: usize) -> Self {
182        let logical_to_physical: HashMap<usize, Vec<usize>> =
183            (0..num_qubits).map(|i| (i, vec![i])).collect();
184        let physical_to_logical: HashMap<usize, usize> = (0..num_qubits).map(|i| (i, i)).collect();
185
186        Self {
187            logical_to_physical,
188            physical_to_logical,
189        }
190    }
191
192    /// Create embedding with chains
193    pub fn with_chains(chains: HashMap<usize, Vec<usize>>) -> Self {
194        let mut physical_to_logical = HashMap::new();
195        for (logical, physical_qubits) in &chains {
196            for &physical in physical_qubits {
197                physical_to_logical.insert(physical, *logical);
198            }
199        }
200
201        Self {
202            logical_to_physical: chains,
203            physical_to_logical,
204        }
205    }
206}
207
208/// Annealing client trait
209pub trait AnnealingClient: Send + Sync {
210    /// Solve QUBO problem
211    fn solve_qubo(&self, qubo: &QuantumMLQUBO, params: &AnnealingParams)
212        -> Result<AnnealingResult>;
213
214    /// Solve Ising problem
215    fn solve_ising(
216        &self,
217        ising: &IsingProblem,
218        params: &AnnealingParams,
219    ) -> Result<AnnealingResult>;
220
221    /// Get client name
222    fn name(&self) -> &str;
223
224    /// Get hardware capabilities
225    fn capabilities(&self) -> AnnealingCapabilities;
226}
227
228/// Annealing result
229#[derive(Debug, Clone)]
230pub struct AnnealingResult {
231    /// Solution (variable assignments)
232    pub solution: Array1<i8>,
233    /// Energy of solution
234    pub energy: f64,
235    /// Number of evaluations
236    pub num_evaluations: usize,
237    /// Timing information
238    pub timing: AnnealingTiming,
239    /// Additional metadata
240    pub metadata: HashMap<String, f64>,
241}
242
243/// Annealing timing information
244#[derive(Debug, Clone)]
245pub struct AnnealingTiming {
246    /// Total solve time
247    pub total_time: std::time::Duration,
248    /// Queue time (for cloud services)
249    pub queue_time: Option<std::time::Duration>,
250    /// Annealing time
251    pub anneal_time: Option<std::time::Duration>,
252}
253
254/// Annealing hardware capabilities
255#[derive(Debug, Clone)]
256pub struct AnnealingCapabilities {
257    /// Maximum number of variables
258    pub max_variables: usize,
259    /// Maximum number of couplers
260    pub max_couplers: usize,
261    /// Connectivity graph
262    pub connectivity: ConnectivityGraph,
263    /// Supported problems
264    pub supported_problems: Vec<ProblemType>,
265}
266
267/// Connectivity graph
268#[derive(Debug, Clone)]
269pub enum ConnectivityGraph {
270    /// Complete graph
271    Complete,
272    /// Chimera graph
273    Chimera {
274        rows: usize,
275        cols: usize,
276        shore: usize,
277    },
278    /// Pegasus graph
279    Pegasus { size: usize },
280    /// Custom graph
281    Custom(Array2<bool>),
282}
283
284/// Problem types
285#[derive(Debug, Clone, Copy)]
286pub enum ProblemType {
287    /// QUBO problems
288    QUBO,
289    /// Ising problems
290    Ising,
291    /// Constrained optimization
292    Constrained,
293}
294
295impl QuantumMLAnnealer {
296    /// Create new annealer
297    pub fn new() -> Self {
298        Self {
299            params: AnnealingParams::default(),
300            embedding: None,
301            client: None,
302        }
303    }
304
305    /// Set annealing parameters
306    pub fn with_params(mut self, params: AnnealingParams) -> Self {
307        self.params = params;
308        self
309    }
310
311    /// Set problem embedding
312    pub fn with_embedding(mut self, embedding: Embedding) -> Self {
313        self.embedding = Some(embedding);
314        self
315    }
316
317    /// Set annealing client
318    pub fn with_client(mut self, client: Box<dyn AnnealingClient>) -> Self {
319        self.client = Some(client);
320        self
321    }
322
323    /// Optimize quantum ML problem
324    pub fn optimize(&self, problem: QuantumMLOptimizationProblem) -> Result<OptimizationResult> {
325        // Convert ML problem to QUBO
326        let qubo = self.convert_to_qubo(&problem)?;
327
328        // Solve using annealing
329        let anneal_result = if let Some(ref client) = self.client {
330            client.solve_qubo(&qubo, &self.params)?
331        } else {
332            // Use classical simulated annealing
333            self.simulated_annealing(&qubo)?
334        };
335
336        // Convert back to ML solution
337        self.convert_to_ml_solution(&problem, &anneal_result)
338    }
339
340    /// Convert ML problem to QUBO formulation
341    fn convert_to_qubo(&self, problem: &QuantumMLOptimizationProblem) -> Result<QuantumMLQUBO> {
342        match problem {
343            QuantumMLOptimizationProblem::FeatureSelection(fs_problem) => {
344                self.feature_selection_to_qubo(fs_problem)
345            }
346            QuantumMLOptimizationProblem::HyperparameterOptimization(hp_problem) => {
347                self.hyperparameter_to_qubo(hp_problem)
348            }
349            QuantumMLOptimizationProblem::CircuitOptimization(circuit_problem) => {
350                self.circuit_optimization_to_qubo(circuit_problem)
351            }
352            QuantumMLOptimizationProblem::PortfolioOptimization(portfolio_problem) => {
353                self.portfolio_to_qubo(portfolio_problem)
354            }
355        }
356    }
357
358    /// Convert feature selection to QUBO
359    fn feature_selection_to_qubo(
360        &self,
361        problem: &FeatureSelectionProblem,
362    ) -> Result<QuantumMLQUBO> {
363        let num_features = problem.feature_importance.len();
364        let mut qubo = QuantumMLQUBO::new(num_features, "Feature Selection");
365
366        // Objective: maximize feature importance (minimize negative importance)
367        for i in 0..num_features {
368            qubo.set_coefficient(i, i, -problem.feature_importance[i])?;
369            qubo.add_variable(format!("feature_{}", i), i);
370        }
371
372        // Constraint: limit number of selected features
373        let penalty = problem.penalty_strength;
374        for i in 0..num_features {
375            for j in i + 1..num_features {
376                qubo.set_coefficient(i, j, penalty)?;
377            }
378        }
379
380        Ok(qubo)
381    }
382
383    /// Convert hyperparameter optimization to QUBO
384    fn hyperparameter_to_qubo(&self, problem: &HyperparameterProblem) -> Result<QuantumMLQUBO> {
385        let total_bits = problem
386            .parameter_encodings
387            .iter()
388            .map(|encoding| encoding.num_bits)
389            .sum();
390
391        let mut qubo = QuantumMLQUBO::new(total_bits, "Hyperparameter Optimization");
392
393        // Encode discrete hyperparameters as binary variables
394        let mut bit_offset = 0;
395        for (param_idx, encoding) in problem.parameter_encodings.iter().enumerate() {
396            for bit in 0..encoding.num_bits {
397                qubo.add_variable(format!("param_{}_bit_{}", param_idx, bit), bit_offset + bit);
398            }
399            bit_offset += encoding.num_bits;
400        }
401
402        // Objective would be based on cross-validation performance
403        // This is a simplified placeholder
404        for i in 0..total_bits {
405            qubo.set_coefficient(i, i, fastrand::f64() - 0.5)?;
406        }
407
408        Ok(qubo)
409    }
410
411    /// Convert circuit optimization to QUBO
412    fn circuit_optimization_to_qubo(
413        &self,
414        problem: &CircuitOptimizationProblem,
415    ) -> Result<QuantumMLQUBO> {
416        let num_positions = problem.gate_positions.len();
417        let num_gate_types = problem.gate_types.len();
418        let total_vars = num_positions * num_gate_types;
419
420        let mut qubo = QuantumMLQUBO::new(total_vars, "Circuit Optimization");
421
422        // Variables: x_{i,g} = 1 if gate type g is at position i
423        for pos in 0..num_positions {
424            for gate in 0..num_gate_types {
425                let var_idx = pos * num_gate_types + gate;
426                qubo.add_variable(format!("pos_{}_gate_{}", pos, gate), var_idx);
427
428                // Objective: minimize circuit depth weighted by gate costs
429                let cost = problem.gate_costs.get(&gate).copied().unwrap_or(1.0);
430                qubo.set_coefficient(var_idx, var_idx, cost)?;
431            }
432        }
433
434        // Constraint: exactly one gate type per position
435        let penalty = 10.0;
436        for pos in 0..num_positions {
437            for g1 in 0..num_gate_types {
438                for g2 in g1 + 1..num_gate_types {
439                    let var1 = pos * num_gate_types + g1;
440                    let var2 = pos * num_gate_types + g2;
441                    qubo.set_coefficient(var1, var2, penalty)?;
442                }
443            }
444        }
445
446        Ok(qubo)
447    }
448
449    /// Convert portfolio optimization to QUBO
450    fn portfolio_to_qubo(&self, problem: &PortfolioOptimizationProblem) -> Result<QuantumMLQUBO> {
451        let num_assets = problem.expected_returns.len();
452        let mut qubo = QuantumMLQUBO::new(num_assets, "Portfolio Optimization");
453
454        // Objective: maximize return - risk penalty
455        for i in 0..num_assets {
456            // Return term
457            qubo.set_coefficient(i, i, -problem.expected_returns[i])?;
458            qubo.add_variable(format!("asset_{}", i), i);
459        }
460
461        // Risk term (covariance)
462        for i in 0..num_assets {
463            for j in i..num_assets {
464                let risk_penalty = problem.risk_aversion * problem.covariance_matrix[[i, j]];
465                qubo.set_coefficient(i, j, risk_penalty)?;
466            }
467        }
468
469        Ok(qubo)
470    }
471
472    /// Simulated annealing fallback
473    fn simulated_annealing(&self, qubo: &QuantumMLQUBO) -> Result<AnnealingResult> {
474        let start_time = std::time::Instant::now();
475        let n = qubo.qubo_matrix.nrows();
476        let mut solution = Array1::from_vec(
477            (0..n)
478                .map(|_| if fastrand::bool() { 1 } else { -1 })
479                .collect(),
480        );
481        let mut best_energy = self.compute_qubo_energy(qubo, &solution);
482
483        let (t_start, t_end) = self.params.temperature_range;
484        let cooling_rate = (t_end / t_start).powf(1.0 / self.params.num_sweeps as f64);
485        let mut temperature = t_start;
486
487        for _sweep in 0..self.params.num_sweeps {
488            for i in 0..n {
489                // Flip spin
490                solution[i] *= -1;
491                let new_energy = self.compute_qubo_energy(qubo, &solution);
492
493                // Accept or reject move
494                if new_energy < best_energy
495                    || fastrand::f64() < ((best_energy - new_energy) / temperature).exp()
496                {
497                    best_energy = new_energy;
498                } else {
499                    // Reject move
500                    solution[i] *= -1;
501                }
502            }
503            temperature *= cooling_rate;
504        }
505
506        Ok(AnnealingResult {
507            solution,
508            energy: best_energy,
509            num_evaluations: self.params.num_sweeps * n,
510            timing: AnnealingTiming {
511                total_time: start_time.elapsed(),
512                queue_time: None,
513                anneal_time: Some(start_time.elapsed()),
514            },
515            metadata: HashMap::new(),
516        })
517    }
518
519    /// Compute QUBO energy
520    fn compute_qubo_energy(&self, qubo: &QuantumMLQUBO, solution: &Array1<i8>) -> f64 {
521        let mut energy = 0.0;
522        let n = solution.len();
523
524        for i in 0..n {
525            for j in 0..n {
526                energy += qubo.qubo_matrix[[i, j]] * (solution[i] as f64) * (solution[j] as f64);
527            }
528        }
529
530        energy
531    }
532
533    /// Convert annealing result to ML solution
534    fn convert_to_ml_solution(
535        &self,
536        problem: &QuantumMLOptimizationProblem,
537        result: &AnnealingResult,
538    ) -> Result<OptimizationResult> {
539        match problem {
540            QuantumMLOptimizationProblem::FeatureSelection(_) => {
541                let selected_features: Vec<usize> = result
542                    .solution
543                    .iter()
544                    .enumerate()
545                    .filter_map(|(i, &val)| if val > 0 { Some(i) } else { None })
546                    .collect();
547
548                Ok(OptimizationResult::FeatureSelection { selected_features })
549            }
550            QuantumMLOptimizationProblem::HyperparameterOptimization(hp_problem) => {
551                let mut parameters = Vec::new();
552                let mut bit_offset = 0;
553
554                for encoding in &hp_problem.parameter_encodings {
555                    let mut param_value = 0;
556                    for bit in 0..encoding.num_bits {
557                        if result.solution[bit_offset + bit] > 0 {
558                            param_value |= 1 << bit;
559                        }
560                    }
561                    parameters.push(param_value as f64);
562                    bit_offset += encoding.num_bits;
563                }
564
565                Ok(OptimizationResult::Hyperparameters { parameters })
566            }
567            QuantumMLOptimizationProblem::CircuitOptimization(circuit_problem) => {
568                let num_gate_types = circuit_problem.gate_types.len();
569                let mut circuit_design = Vec::new();
570
571                for pos in 0..circuit_problem.gate_positions.len() {
572                    for gate in 0..num_gate_types {
573                        let var_idx = pos * num_gate_types + gate;
574                        if result.solution[var_idx] > 0 {
575                            circuit_design.push(gate);
576                            break;
577                        }
578                    }
579                }
580
581                Ok(OptimizationResult::CircuitDesign { circuit_design })
582            }
583            QuantumMLOptimizationProblem::PortfolioOptimization(_) => {
584                let portfolio: Vec<f64> = result
585                    .solution
586                    .iter()
587                    .map(|&val| if val > 0 { 1.0 } else { 0.0 })
588                    .collect();
589
590                Ok(OptimizationResult::Portfolio { weights: portfolio })
591            }
592        }
593    }
594}
595
596/// Quantum ML optimization problems
597#[derive(Debug, Clone)]
598pub enum QuantumMLOptimizationProblem {
599    /// Feature selection
600    FeatureSelection(FeatureSelectionProblem),
601    /// Hyperparameter optimization
602    HyperparameterOptimization(HyperparameterProblem),
603    /// Circuit optimization
604    CircuitOptimization(CircuitOptimizationProblem),
605    /// Portfolio optimization
606    PortfolioOptimization(PortfolioOptimizationProblem),
607}
608
609/// Feature selection problem
610#[derive(Debug, Clone)]
611pub struct FeatureSelectionProblem {
612    /// Feature importance scores
613    pub feature_importance: Array1<f64>,
614    /// Penalty strength for number of features
615    pub penalty_strength: f64,
616    /// Maximum number of features
617    pub max_features: Option<usize>,
618}
619
620/// Hyperparameter optimization problem
621#[derive(Debug, Clone)]
622pub struct HyperparameterProblem {
623    /// Parameter encodings
624    pub parameter_encodings: Vec<ParameterEncoding>,
625    /// Cross-validation function (placeholder)
626    pub cv_function: String,
627}
628
629/// Parameter encoding for QUBO
630#[derive(Debug, Clone)]
631pub struct ParameterEncoding {
632    /// Parameter name
633    pub name: String,
634    /// Number of bits for encoding
635    pub num_bits: usize,
636    /// Value range
637    pub range: (f64, f64),
638}
639
640/// Circuit optimization problem
641#[derive(Debug, Clone)]
642pub struct CircuitOptimizationProblem {
643    /// Available gate positions
644    pub gate_positions: Vec<usize>,
645    /// Available gate types
646    pub gate_types: Vec<String>,
647    /// Cost of each gate type
648    pub gate_costs: HashMap<usize, f64>,
649    /// Connectivity constraints
650    pub connectivity: Array2<bool>,
651}
652
653/// Portfolio optimization problem
654#[derive(Debug, Clone)]
655pub struct PortfolioOptimizationProblem {
656    /// Expected returns
657    pub expected_returns: Array1<f64>,
658    /// Covariance matrix
659    pub covariance_matrix: Array2<f64>,
660    /// Risk aversion parameter
661    pub risk_aversion: f64,
662    /// Budget constraint
663    pub budget: f64,
664}
665
666/// Optimization result
667#[derive(Debug, Clone)]
668pub enum OptimizationResult {
669    /// Selected features
670    FeatureSelection { selected_features: Vec<usize> },
671    /// Optimized hyperparameters
672    Hyperparameters { parameters: Vec<f64> },
673    /// Optimized circuit design
674    CircuitDesign { circuit_design: Vec<usize> },
675    /// Portfolio weights
676    Portfolio { weights: Vec<f64> },
677}
678
679/// D-Wave quantum annealer client
680pub struct DWaveClient {
681    /// API token
682    token: String,
683    /// Solver name
684    solver: String,
685    /// Chain strength
686    chain_strength: f64,
687}
688
689impl DWaveClient {
690    /// Create new D-Wave client
691    pub fn new(token: impl Into<String>, solver: impl Into<String>) -> Self {
692        Self {
693            token: token.into(),
694            solver: solver.into(),
695            chain_strength: 1.0,
696        }
697    }
698
699    /// Set chain strength
700    pub fn with_chain_strength(mut self, strength: f64) -> Self {
701        self.chain_strength = strength;
702        self
703    }
704}
705
706impl AnnealingClient for DWaveClient {
707    fn solve_qubo(
708        &self,
709        qubo: &QuantumMLQUBO,
710        params: &AnnealingParams,
711    ) -> Result<AnnealingResult> {
712        // Placeholder - would make actual D-Wave API call
713        let start_time = std::time::Instant::now();
714        let n = qubo.qubo_matrix.nrows();
715        let solution = Array1::from_vec(
716            (0..n)
717                .map(|_| if fastrand::bool() { 1 } else { -1 })
718                .collect(),
719        );
720        let energy = 0.0; // Would compute actual energy
721
722        Ok(AnnealingResult {
723            solution,
724            energy,
725            num_evaluations: params.num_sweeps,
726            timing: AnnealingTiming {
727                total_time: start_time.elapsed(),
728                queue_time: Some(std::time::Duration::from_millis(100)),
729                anneal_time: Some(std::time::Duration::from_millis(20)),
730            },
731            metadata: HashMap::new(),
732        })
733    }
734
735    fn solve_ising(
736        &self,
737        ising: &IsingProblem,
738        params: &AnnealingParams,
739    ) -> Result<AnnealingResult> {
740        // Convert to QUBO and solve
741        let qubo = self.ising_to_qubo(ising);
742        self.solve_qubo(&qubo, params)
743    }
744
745    fn name(&self) -> &str {
746        "D-Wave"
747    }
748
749    fn capabilities(&self) -> AnnealingCapabilities {
750        AnnealingCapabilities {
751            max_variables: 5000,
752            max_couplers: 40000,
753            connectivity: ConnectivityGraph::Pegasus { size: 16 },
754            supported_problems: vec![ProblemType::QUBO, ProblemType::Ising],
755        }
756    }
757}
758
759impl DWaveClient {
760    fn ising_to_qubo(&self, ising: &IsingProblem) -> QuantumMLQUBO {
761        let n = ising.h.len();
762        let mut qubo = QuantumMLQUBO::new(n, "Ising to QUBO");
763
764        // Standard Ising to QUBO transformation
765        for i in 0..n {
766            qubo.set_coefficient(i, i, -2.0 * ising.h[i]).unwrap();
767        }
768
769        for i in 0..n {
770            for j in i + 1..n {
771                qubo.set_coefficient(i, j, -4.0 * ising.j[[i, j]]).unwrap();
772            }
773        }
774
775        qubo
776    }
777}
778
779/// Utility functions for annealing integration
780pub mod anneal_utils {
781    use super::*;
782
783    /// Create feature selection problem
784    pub fn create_feature_selection_problem(
785        num_features: usize,
786        max_features: usize,
787    ) -> FeatureSelectionProblem {
788        let feature_importance =
789            Array1::from_vec((0..num_features).map(|_| fastrand::f64()).collect());
790
791        FeatureSelectionProblem {
792            feature_importance,
793            penalty_strength: 0.1,
794            max_features: Some(max_features),
795        }
796    }
797
798    /// Create hyperparameter optimization problem
799    pub fn create_hyperparameter_problem(
800        param_names: Vec<String>,
801        param_ranges: Vec<(f64, f64)>,
802        bits_per_param: usize,
803    ) -> HyperparameterProblem {
804        let parameter_encodings = param_names
805            .into_iter()
806            .zip(param_ranges.into_iter())
807            .map(|(name, range)| ParameterEncoding {
808                name,
809                num_bits: bits_per_param,
810                range,
811            })
812            .collect();
813
814        HyperparameterProblem {
815            parameter_encodings,
816            cv_function: "accuracy".to_string(),
817        }
818    }
819
820    /// Create portfolio optimization problem
821    pub fn create_portfolio_problem(
822        num_assets: usize,
823        risk_aversion: f64,
824    ) -> PortfolioOptimizationProblem {
825        let expected_returns = Array1::from_vec(
826            (0..num_assets)
827                .map(|_| 0.05 + 0.1 * fastrand::f64())
828                .collect(),
829        );
830
831        let mut covariance_matrix = Array2::zeros((num_assets, num_assets));
832        for i in 0..num_assets {
833            for j in 0..num_assets {
834                let cov = if i == j {
835                    0.01 + 0.02 * fastrand::f64()
836                } else {
837                    0.005 * fastrand::f64()
838                };
839                covariance_matrix[[i, j]] = cov;
840            }
841        }
842
843        PortfolioOptimizationProblem {
844            expected_returns,
845            covariance_matrix,
846            risk_aversion,
847            budget: 1.0,
848        }
849    }
850}
851
852#[cfg(test)]
853mod tests {
854    use super::*;
855
856    #[test]
857    fn test_qubo_creation() {
858        let mut qubo = QuantumMLQUBO::new(3, "Test QUBO");
859        qubo.set_coefficient(0, 0, 1.0).unwrap();
860        qubo.set_coefficient(0, 1, -2.0).unwrap();
861
862        assert_eq!(qubo.qubo_matrix[[0, 0]], 1.0);
863        assert_eq!(qubo.qubo_matrix[[0, 1]], -2.0);
864    }
865
866    #[test]
867    fn test_ising_conversion() {
868        let mut qubo = QuantumMLQUBO::new(2, "Test");
869        qubo.set_coefficient(0, 0, 1.0).unwrap();
870        qubo.set_coefficient(1, 1, -1.0).unwrap();
871        qubo.set_coefficient(0, 1, 2.0).unwrap();
872
873        let ising = qubo.to_ising();
874        assert_eq!(ising.h.len(), 2);
875        assert_eq!(ising.j.shape(), [2, 2]);
876    }
877
878    #[test]
879    fn test_annealer_creation() {
880        let annealer = QuantumMLAnnealer::new();
881        assert_eq!(annealer.params.num_sweeps, 1000);
882    }
883
884    #[test]
885    fn test_embedding() {
886        let embedding = Embedding::identity(5);
887        assert_eq!(embedding.logical_to_physical.len(), 5);
888        assert_eq!(embedding.physical_to_logical.len(), 5);
889    }
890
891    #[test]
892    fn test_feature_selection_problem() {
893        let problem = anneal_utils::create_feature_selection_problem(10, 5);
894        assert_eq!(problem.feature_importance.len(), 10);
895        assert_eq!(problem.max_features, Some(5));
896    }
897}