quantrs2_tytan/sampler/hardware/
azure_quantum.rs1use scirs2_core::ndarray::{Array, Ix2};
7use scirs2_core::random::{thread_rng, Rng};
8use std::collections::HashMap;
9
10use quantrs2_anneal::QuboModel;
11
12use super::super::{SampleResult, Sampler, SamplerError, SamplerResult};
13
14#[derive(Debug, Clone)]
16pub enum AzureSolver {
17 SimulatedAnnealing,
19 ParallelTempering,
21 TabuSearch,
23 PopulationAnnealing,
25 SubstrateMonteCarlo,
27 IonQ,
29 Quantinuum,
31 Rigetti,
33}
34
35#[derive(Debug, Clone)]
37pub struct AzureQuantumConfig {
38 pub subscription_id: String,
40 pub resource_group: String,
42 pub workspace_name: String,
44 pub solver: AzureSolver,
46 pub timeout: u64,
48 pub solver_params: HashMap<String, String>,
50}
51
52impl Default for AzureQuantumConfig {
53 fn default() -> Self {
54 Self {
55 subscription_id: String::new(),
56 resource_group: String::new(),
57 workspace_name: String::new(),
58 solver: AzureSolver::SimulatedAnnealing,
59 timeout: 300,
60 solver_params: HashMap::new(),
61 }
62 }
63}
64
65pub struct AzureQuantumSampler {
70 config: AzureQuantumConfig,
71}
72
73impl AzureQuantumSampler {
74 #[must_use]
80 pub const fn new(config: AzureQuantumConfig) -> Self {
81 Self { config }
82 }
83
84 #[must_use]
92 pub fn with_workspace(
93 subscription_id: &str,
94 resource_group: &str,
95 workspace_name: &str,
96 ) -> Self {
97 Self {
98 config: AzureQuantumConfig {
99 subscription_id: subscription_id.to_string(),
100 resource_group: resource_group.to_string(),
101 workspace_name: workspace_name.to_string(),
102 ..Default::default()
103 },
104 }
105 }
106
107 #[must_use]
109 pub const fn with_solver(mut self, solver: AzureSolver) -> Self {
110 self.config.solver = solver;
111 self
112 }
113
114 #[must_use]
116 pub const fn with_timeout(mut self, timeout: u64) -> Self {
117 self.config.timeout = timeout;
118 self
119 }
120
121 #[must_use]
123 pub fn with_param(mut self, key: String, value: String) -> Self {
124 self.config.solver_params.insert(key, value);
125 self
126 }
127}
128
129impl Sampler for AzureQuantumSampler {
130 fn run_qubo(
131 &self,
132 qubo: &(Array<f64, Ix2>, HashMap<String, usize>),
133 shots: usize,
134 ) -> SamplerResult<Vec<SampleResult>> {
135 let (matrix, var_map) = qubo;
137
138 let n_vars = var_map.len();
140
141 match self.config.solver {
143 AzureSolver::IonQ => {
144 if n_vars > 29 {
145 return Err(SamplerError::InvalidParameter(
146 "IonQ currently supports up to 29 qubits".to_string(),
147 ));
148 }
149 }
150 AzureSolver::Quantinuum => {
151 if n_vars > 20 {
152 return Err(SamplerError::InvalidParameter(
153 "Quantinuum currently supports up to 20 qubits for this application"
154 .to_string(),
155 ));
156 }
157 }
158 AzureSolver::Rigetti => {
159 if n_vars > 40 {
160 return Err(SamplerError::InvalidParameter(
161 "Rigetti currently supports up to 40 qubits".to_string(),
162 ));
163 }
164 }
165 _ => {
166 if n_vars > 10000 {
168 return Err(SamplerError::InvalidParameter(
169 "Problem size exceeds Azure QIO limits".to_string(),
170 ));
171 }
172 }
173 }
174
175 let idx_to_var: HashMap<usize, String> = var_map
177 .iter()
178 .map(|(var, &idx)| (idx, var.clone()))
179 .collect();
180
181 let mut qubo_model = QuboModel::new(n_vars);
183
184 for i in 0..n_vars {
186 if matrix[[i, i]] != 0.0 {
187 qubo_model.set_linear(i, matrix[[i, i]])?;
188 }
189
190 for j in (i + 1)..n_vars {
191 if matrix[[i, j]] != 0.0 {
192 qubo_model.set_quadratic(i, j, matrix[[i, j]])?;
193 }
194 }
195 }
196
197 #[cfg(feature = "azure_quantum")]
199 {
200 let _azure_result = "placeholder";
208 }
209
210 let mut results = Vec::new();
212 let mut rng = thread_rng();
213
214 let unique_solutions = match self.config.solver {
216 AzureSolver::SimulatedAnnealing => shots.min(50),
217 AzureSolver::ParallelTempering => shots.min(100),
218 AzureSolver::TabuSearch => shots.min(30),
219 AzureSolver::PopulationAnnealing => shots.min(200),
220 AzureSolver::SubstrateMonteCarlo => shots.min(150),
221 AzureSolver::IonQ | AzureSolver::Quantinuum | AzureSolver::Rigetti => {
222 shots.min(1000)
224 }
225 };
226
227 for _ in 0..unique_solutions {
228 let assignments: HashMap<String, bool> = idx_to_var
229 .values()
230 .map(|name| (name.clone(), rng.gen::<bool>()))
231 .collect();
232
233 let mut energy = 0.0;
235 for (var_name, &val) in &assignments {
236 let i = var_map[var_name];
237 if val {
238 energy += matrix[[i, i]];
239 for (other_var, &other_val) in &assignments {
240 let j = var_map[other_var];
241 if i < j && other_val {
242 energy += matrix[[i, j]];
243 }
244 }
245 }
246 }
247
248 let occurrences = match self.config.solver {
250 AzureSolver::IonQ | AzureSolver::Quantinuum | AzureSolver::Rigetti => {
251 rng.gen_range(1..=(shots / unique_solutions + 10))
253 }
254 _ => {
255 1
257 }
258 };
259
260 results.push(SampleResult {
261 assignments,
262 energy,
263 occurrences,
264 });
265 }
266
267 results.sort_by(|a, b| {
269 a.energy
270 .partial_cmp(&b.energy)
271 .unwrap_or(std::cmp::Ordering::Equal)
272 });
273
274 results.truncate(shots.min(100));
276
277 Ok(results)
278 }
279
280 fn run_hobo(
281 &self,
282 hobo: &(
283 Array<f64, scirs2_core::ndarray::IxDyn>,
284 HashMap<String, usize>,
285 ),
286 shots: usize,
287 ) -> SamplerResult<Vec<SampleResult>> {
288 use scirs2_core::ndarray::Ix2;
289
290 if hobo.0.ndim() <= 2 {
292 let qubo_matrix = hobo.0.clone().into_dimensionality::<Ix2>().map_err(|e| {
294 SamplerError::InvalidParameter(format!(
295 "Failed to convert HOBO to QUBO dimensionality: {e}"
296 ))
297 })?;
298 let qubo = (qubo_matrix, hobo.1.clone());
299 self.run_qubo(&qubo, shots)
300 } else {
301 Err(SamplerError::InvalidParameter(
303 "Azure Quantum doesn't support HOBO problems directly. Use a quadratization technique first.".to_string()
304 ))
305 }
306 }
307}
308
309#[cfg(test)]
310mod tests {
311 use super::*;
312
313 #[test]
314 fn test_azure_quantum_config() {
315 let config = AzureQuantumConfig::default();
316 assert_eq!(config.timeout, 300);
317 assert!(matches!(config.solver, AzureSolver::SimulatedAnnealing));
318 }
319
320 #[test]
321 fn test_azure_quantum_sampler_creation() {
322 let sampler =
323 AzureQuantumSampler::with_workspace("test-subscription", "test-rg", "test-workspace")
324 .with_solver(AzureSolver::ParallelTempering)
325 .with_timeout(600)
326 .with_param("temperature".to_string(), "0.5".to_string());
327
328 assert_eq!(sampler.config.subscription_id, "test-subscription");
329 assert_eq!(sampler.config.resource_group, "test-rg");
330 assert_eq!(sampler.config.workspace_name, "test-workspace");
331 assert_eq!(sampler.config.timeout, 600);
332 assert!(matches!(
333 sampler.config.solver,
334 AzureSolver::ParallelTempering
335 ));
336 }
337
338 #[test]
339 fn test_azure_solver_types() {
340 let solvers = [
341 AzureSolver::SimulatedAnnealing,
342 AzureSolver::ParallelTempering,
343 AzureSolver::TabuSearch,
344 AzureSolver::PopulationAnnealing,
345 AzureSolver::SubstrateMonteCarlo,
346 AzureSolver::IonQ,
347 AzureSolver::Quantinuum,
348 AzureSolver::Rigetti,
349 ];
350
351 assert_eq!(solvers.len(), 8);
352 }
353}