Skip to main content

quantrs2_tytan/sampler/
mod.rs

1//! Samplers for solving QUBO/HOBO problems.
2//!
3//! QuantRS2 Tytan provides five pure-Rust metaheuristic samplers for solving
4//! QUBO (Quadratic Unconstrained Binary Optimization) and HOBO (Higher-Order
5//! Binary Optimization) problems. All samplers implement the [`Sampler`] trait,
6//! which exposes a uniform `run_qubo` / `run_hobo` interface.
7//!
8//! The [`energy`] submodule provides SIMD-accelerated QUBO energy evaluation
9//! functions that serve as the shared inner loop for all samplers.
10//!
11//! # Sampler Comparison
12//!
13//! | Sampler | Type | Strengths | Typical problem size | Citation |
14//! |---------|------|-----------|----------------------|----------|
15//! | [`SASampler`] | Local search | Versatile, easy to tune | Small–large | Kirkpatrick 1983 |
16//! | [`GASampler`] | Evolutionary | Population diversity | Medium | Holland 1975 |
17//! | [`TabuSampler`] | Tabu search | Structured combinatorial | Small–medium | Glover 1989 |
18//! | [`SBSampler`] | Bifurcation dynamics | Large dense QUBO | Large | Goto 2019 |
19//! | [`PopulationAnnealingSampler`] | Population annealing | Ensemble, near-ground-state | Medium–large | Hukushima 2003 |
20//!
21//! # Choosing a Sampler
22//!
23//! - **Start with [`SASampler`]** for most problems. It is robust, has sensible
24//!   defaults, and handles both small and large instances.
25//! - **Use [`TabuSampler`]** for scheduling, routing, or other problems that have
26//!   a structured neighbourhood where recently-visited moves should be forbidden.
27//! - **Use [`SBSampler`]** for large, dense QUBO matrices (n ≥ 200) where SA is
28//!   too slow. The ballistic (`SBVariant::Ballistic`) variant converges faster;
29//!   discrete (`SBVariant::Discrete`) gives crisper spin assignments.
30//! - **Use [`PopulationAnnealingSampler`]** when you need an ensemble of
31//!   near-ground-state solutions or when the problem has a complex low-energy
32//!   landscape and you want thermodynamic statistics.
33//! - **Use [`GASampler`]** when the problem structure maps naturally to bitstring
34//!   chromosomes and crossover is likely to be productive (e.g., scheduling
35//!   problems with independent sub-problems).
36//!
37//! # Quick Example
38//!
39//! The following example solves a 3-variable Max-Cut QUBO (K3 graph) with the
40//! Simulated Annealing sampler:
41//!
42//! ```
43//! use quantrs2_tytan::sampler::{SASampler, Sampler};
44//! use scirs2_core::ndarray::Array;
45//! use std::collections::HashMap;
46//!
47//! // K3 Max-Cut: minimise -x0 - x1 - x2 + 2*x0*x1 + 2*x0*x2 + 2*x1*x2
48//! let mut q = Array::<f64, _>::zeros((3, 3));
49//! q[[0, 0]] = -1.0;
50//! q[[1, 1]] = -1.0;
51//! q[[2, 2]] = -1.0;
52//! q[[0, 1]] = 2.0;
53//! q[[0, 2]] = 2.0;
54//! q[[1, 2]] = 2.0;
55//!
56//! let mut var_map = HashMap::new();
57//! var_map.insert("x0".to_string(), 0);
58//! var_map.insert("x1".to_string(), 1);
59//! var_map.insert("x2".to_string(), 2);
60//!
61//! let sampler = SASampler::new(Some(42));
62//! let samples = sampler.run_qubo(&(q, var_map), 5).expect("SA sampler failed");
63//! assert!(!samples.is_empty());
64//! // Best solution is at index 0 (sorted by energy ascending)
65//! println!("Best energy: {}", samples[0].energy);
66//! ```
67
68pub mod energy;
69pub mod errors;
70pub mod genetic_algorithm;
71pub mod gpu;
72pub mod hardware;
73pub mod population_annealing;
74pub mod simulated_annealing;
75pub mod simulated_bifurcation;
76pub mod tabu_search;
77
78use scirs2_core::ndarray::Array;
79use std::collections::HashMap;
80
81pub use errors::{SamplerError, SamplerResult};
82
83/// A sample result from a QUBO/HOBO problem
84///
85/// This struct represents a single sample result from a QUBO/HOBO
86/// problem, including the variable assignments, energy, and occurrence count.
87#[derive(Debug, Clone)]
88pub struct SampleResult {
89    /// The variable assignments (name -> value mapping)
90    pub assignments: HashMap<String, bool>,
91    /// The energy (objective function value)
92    pub energy: f64,
93    /// The number of times this solution appeared
94    pub occurrences: usize,
95}
96
97/// Trait for samplers that can solve QUBO/HOBO problems
98pub trait Sampler {
99    /// Run the sampler on a QUBO problem
100    ///
101    /// # Arguments
102    ///
103    /// * `qubo` - The QUBO problem to solve: (matrix, variable mapping)
104    /// * `shots` - The number of samples to take
105    ///
106    /// # Returns
107    ///
108    /// A vector of sample results, sorted by energy (best solutions first)
109    fn run_qubo(
110        &self,
111        qubo: &(
112            Array<f64, scirs2_core::ndarray::Ix2>,
113            HashMap<String, usize>,
114        ),
115        shots: usize,
116    ) -> SamplerResult<Vec<SampleResult>>;
117
118    /// Run the sampler on a HOBO problem
119    ///
120    /// # Arguments
121    ///
122    /// * `hobo` - The HOBO problem to solve: (tensor, variable mapping)
123    /// * `shots` - The number of samples to take
124    ///
125    /// # Returns
126    ///
127    /// A vector of sample results, sorted by energy (best solutions first)
128    fn run_hobo(
129        &self,
130        hobo: &(
131            Array<f64, scirs2_core::ndarray::IxDyn>,
132            HashMap<String, usize>,
133        ),
134        shots: usize,
135    ) -> SamplerResult<Vec<SampleResult>>;
136}
137
138/// Evaluate energy for a QUBO problem given a binary state vector
139///
140/// # Arguments
141///
142/// * `state` - Binary state vector (solution)
143/// * `h_vector` - Linear coefficients (diagonal)
144/// * `j_matrix` - Quadratic coefficients (n_vars x n_vars matrix in flattened form)
145/// * `n_vars` - Number of variables
146///
147/// # Returns
148///
149/// The energy (objective function value) for the given state
150#[allow(dead_code)]
151pub(crate) fn evaluate_qubo_energy(
152    state: &[bool],
153    h_vector: &[f64],
154    j_matrix: &[f64],
155    n_vars: usize,
156) -> f64 {
157    let mut energy = 0.0;
158
159    // Linear terms
160    for i in 0..n_vars {
161        if state[i] {
162            energy += h_vector[i];
163        }
164    }
165
166    // Quadratic terms
167    for i in 0..n_vars {
168        if state[i] {
169            for j in 0..n_vars {
170                if state[j] {
171                    energy += j_matrix[i * n_vars + j];
172                }
173            }
174        }
175    }
176
177    energy
178}
179
180// Re-export main sampler implementations
181pub use genetic_algorithm::GASampler;
182pub use gpu::ArminSampler;
183pub use hardware::{DWaveSampler, MIKASAmpler};
184pub use population_annealing::PopulationAnnealingSampler;
185pub use simulated_annealing::SASampler;
186pub use simulated_bifurcation::{SBSampler, SBVariant};
187pub use tabu_search::TabuSampler;
188
189// Re-export energy module public interface
190pub use energy::{
191    build_dense_q, build_dense_q_from_array, compute_influence, compute_influence_simd,
192    energy_delta, energy_delta_from_array, energy_delta_simd, energy_full, energy_full_from_array,
193    energy_full_simd, update_influence, update_influence_simd,
194};