1#[cfg(feature = "dwave")]
6pub use client::*;
7#[cfg(not(feature = "dwave"))]
8pub use placeholder::*;
9use std::fmt::Write;
10
11#[cfg(feature = "dwave")]
12mod client {
13 use crate::embedding::{Embedding, HardwareGraph, MinorMiner};
14 use crate::ising::{IsingError, IsingModel, QuboModel};
15 use reqwest::Client;
16 use serde::{Deserialize, Serialize};
17 use std::collections::HashMap;
18 use std::fmt::Write;
19 use std::time::{Duration, Instant};
20 use thiserror::Error;
21 use tokio::runtime::Runtime;
22 #[derive(Error, Debug)]
24 pub enum DWaveError {
25 #[error("Ising error: {0}")]
27 IsingError(#[from] IsingError),
28 #[error("Network error: {0}")]
30 NetworkError(#[from] reqwest::Error),
31 #[error("Response parsing error: {0}")]
33 ParseError(#[from] serde_json::Error),
34 #[error("D-Wave API error: {0}")]
36 ApiError(String),
37 #[error("Authentication error: {0}")]
39 AuthError(String),
40 #[error("Runtime error: {0}")]
42 RuntimeError(String),
43 #[error("Problem formulation error: {0}")]
45 ProblemError(String),
46 #[error("Embedding error: {0}")]
48 EmbeddingError(String),
49 #[error("Hybrid solver error: {0}")]
51 HybridSolverError(String),
52 #[error("Problem status error: {0}")]
54 StatusError(String),
55 #[error("Batch operation error: {0}")]
57 BatchError(String),
58 #[error("Solver configuration error: {0}")]
60 SolverConfigError(String),
61 #[error("Operation timed out: {0}")]
63 TimeoutError(String),
64 }
65 pub type DWaveResult<T> = Result<T, DWaveError>;
67 #[derive(Debug, Clone, Serialize, Deserialize)]
69 pub struct SolverInfo {
70 pub id: String,
72 pub name: String,
74 pub description: String,
76 pub num_qubits: usize,
78 pub connectivity: SolverConnectivity,
80 pub properties: SolverProperties,
82 }
83 #[derive(Debug, Clone, Serialize, Deserialize)]
85 pub struct SolverConnectivity {
86 #[serde(rename = "type")]
88 pub type_: String,
89 #[serde(flatten)]
91 pub params: serde_json::Value,
92 }
93 #[derive(Debug, Clone, Serialize, Deserialize)]
95 pub struct SolverProperties {
96 pub parameters: serde_json::Value,
98 #[serde(flatten)]
100 pub other: serde_json::Value,
101 }
102 #[derive(Debug, Clone, Serialize, Deserialize)]
104 pub struct ProblemParams {
105 pub num_reads: usize,
107 pub annealing_time: usize,
109 #[serde(rename = "programming_thermalization")]
111 pub programming_therm: usize,
112 #[serde(rename = "readout_thermalization")]
114 pub readout_therm: usize,
115 #[serde(rename = "flux_biases", skip_serializing_if = "Option::is_none")]
117 pub flux_biases: Option<Vec<f64>>,
118 #[serde(rename = "flux_bias", skip_serializing_if = "Option::is_none")]
120 pub flux_bias_map: Option<serde_json::Map<String, serde_json::Value>>,
121 #[serde(flatten)]
123 pub other: serde_json::Value,
124 }
125 impl Default for ProblemParams {
126 fn default() -> Self {
127 Self {
128 num_reads: 1000,
129 annealing_time: 20,
130 programming_therm: 1000,
131 readout_therm: 0,
132 flux_biases: None,
133 flux_bias_map: None,
134 other: serde_json::Value::Object(serde_json::Map::new()),
135 }
136 }
137 }
138 #[derive(Debug, Clone, Serialize, Deserialize)]
140 pub struct Problem {
141 #[serde(rename = "linear")]
143 pub linear_terms: serde_json::Value,
144 #[serde(rename = "quadratic")]
146 pub quadratic_terms: serde_json::Value,
147 #[serde(rename = "type")]
149 pub type_: String,
150 pub solver: String,
152 pub params: ProblemParams,
154 }
155 #[derive(Debug, Clone, Serialize, Deserialize)]
157 pub struct Solution {
158 pub energies: Vec<f64>,
160 pub occurrences: Vec<usize>,
162 pub solutions: Vec<Vec<i8>>,
164 pub num_samples: usize,
166 pub problem_id: String,
168 pub solver: String,
170 pub timing: serde_json::Value,
172 }
173 #[derive(Debug, Clone, Serialize, Deserialize)]
175 pub enum SolverType {
176 #[serde(rename = "qpu")]
178 QuantumProcessor,
179 #[serde(rename = "hybrid")]
181 Hybrid,
182 #[serde(rename = "dqm")]
184 DiscreteQuadraticModel,
185 #[serde(rename = "cqm")]
187 ConstrainedQuadraticModel,
188 #[serde(rename = "software")]
190 Software,
191 }
192 #[derive(Debug, Clone, PartialEq, Eq)]
194 pub enum SolverCategory {
195 QPU,
197 Hybrid,
199 Software,
201 All,
203 }
204 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
206 pub enum ProblemStatus {
207 #[serde(rename = "IN_PROGRESS")]
209 InProgress,
210 #[serde(rename = "COMPLETED")]
212 Completed,
213 #[serde(rename = "FAILED")]
215 Failed,
216 #[serde(rename = "CANCELLED")]
218 Cancelled,
219 #[serde(rename = "PENDING")]
221 Pending,
222 }
223 #[derive(Debug, Clone, Serialize, Deserialize)]
225 pub struct ProblemInfo {
226 pub id: String,
228 pub status: ProblemStatus,
230 pub submitted_on: String,
232 pub solver: String,
234 #[serde(rename = "type")]
236 pub problem_type: String,
237 pub params: serde_json::Value,
239 #[serde(flatten)]
241 pub metadata: serde_json::Value,
242 }
243 #[derive(Debug, Clone, Serialize, Deserialize)]
245 pub struct LeapSolverInfo {
246 pub id: String,
248 pub name: String,
250 pub description: String,
252 #[serde(rename = "category")]
254 pub solver_type: SolverType,
255 pub status: String,
257 pub properties: serde_json::Value,
259 pub problem_types: Vec<String>,
261 pub avg_load: Option<f64>,
263 pub available: bool,
265 }
266 #[derive(Debug, Clone, Serialize, Deserialize)]
268 pub struct AnnealingSchedule {
269 pub schedule: Vec<(f64, f64)>,
271 }
272 impl AnnealingSchedule {
273 pub fn linear(annealing_time: f64) -> Self {
275 Self {
276 schedule: vec![(0.0, 1.0), (annealing_time, 0.0)],
277 }
278 }
279 pub fn pause_and_ramp(annealing_time: f64, pause_start: f64, pause_duration: f64) -> Self {
281 Self {
282 schedule: vec![
283 (0.0, 1.0),
284 (pause_start, 1.0 - pause_start / annealing_time),
285 (
286 pause_start + pause_duration,
287 1.0 - pause_start / annealing_time,
288 ),
289 (annealing_time, 0.0),
290 ],
291 }
292 }
293 pub fn custom(points: Vec<(f64, f64)>) -> Self {
295 Self { schedule: points }
296 }
297 }
298 #[derive(Debug, Clone, Serialize, Deserialize)]
300 pub struct AdvancedProblemParams {
301 pub num_reads: usize,
303 #[serde(skip_serializing_if = "Option::is_none")]
305 pub anneal_schedule: Option<AnnealingSchedule>,
306 #[serde(skip_serializing_if = "Option::is_none")]
308 pub programming_thermalization: Option<usize>,
309 #[serde(skip_serializing_if = "Option::is_none")]
311 pub readout_thermalization: Option<usize>,
312 #[serde(skip_serializing_if = "Option::is_none")]
314 pub auto_scale: Option<bool>,
315 #[serde(skip_serializing_if = "Option::is_none")]
317 pub chain_strength: Option<f64>,
318 #[serde(skip_serializing_if = "Option::is_none")]
320 pub flux_biases: Option<HashMap<String, f64>>,
321 #[serde(flatten)]
323 pub extra: HashMap<String, serde_json::Value>,
324 }
325 impl Default for AdvancedProblemParams {
326 fn default() -> Self {
327 Self {
328 num_reads: 1000,
329 anneal_schedule: None,
330 programming_thermalization: Some(1000),
331 readout_thermalization: Some(0),
332 auto_scale: Some(true),
333 chain_strength: None,
334 flux_biases: None,
335 extra: HashMap::new(),
336 }
337 }
338 }
339 #[derive(Debug, Clone, Serialize, Deserialize)]
341 pub struct HybridSolverParams {
342 #[serde(skip_serializing_if = "Option::is_none")]
344 pub time_limit: Option<f64>,
345 #[serde(skip_serializing_if = "Option::is_none")]
347 pub max_variables: Option<usize>,
348 #[serde(flatten)]
350 pub extra: HashMap<String, serde_json::Value>,
351 }
352 impl Default for HybridSolverParams {
353 fn default() -> Self {
354 Self {
355 time_limit: Some(5.0),
356 max_variables: None,
357 extra: HashMap::new(),
358 }
359 }
360 }
361 #[derive(Debug, Clone)]
363 pub struct ProblemMetrics {
364 pub total_time: Duration,
366 pub queue_time: Duration,
368 pub access_time: Duration,
370 pub programming_time: Duration,
372 pub sampling_time: Duration,
374 pub readout_time: Duration,
376 pub best_energy: f64,
378 pub num_valid_solutions: usize,
380 pub chain_break_fraction: Option<f64>,
382 }
383 #[derive(Debug)]
385 pub struct BatchSubmissionResult {
386 pub problem_ids: Vec<String>,
388 pub statuses: Vec<Result<String, DWaveError>>,
390 pub submission_time: Duration,
392 }
393 #[derive(Debug, Clone)]
395 pub struct SolverSelector {
396 pub category: SolverCategory,
398 pub min_qubits: Option<usize>,
400 pub max_queue_time: Option<f64>,
402 pub online_only: bool,
404 pub name_pattern: Option<String>,
406 pub topology_preference: Option<String>,
408 }
409 impl Default for SolverSelector {
410 fn default() -> Self {
411 Self {
412 category: SolverCategory::All,
413 min_qubits: None,
414 max_queue_time: None,
415 online_only: true,
416 name_pattern: None,
417 topology_preference: None,
418 }
419 }
420 }
421 #[derive(Debug, Clone)]
423 pub struct EmbeddingConfig {
424 pub auto_embed: bool,
426 pub timeout: Duration,
428 pub chain_strength_method: ChainStrengthMethod,
430 pub custom_embedding: Option<Embedding>,
432 pub optimization_level: usize,
434 }
435 #[derive(Debug, Clone)]
437 pub enum ChainStrengthMethod {
438 Auto,
440 Fixed(f64),
442 Adaptive(f64),
444 }
445 impl Default for EmbeddingConfig {
446 fn default() -> Self {
447 Self {
448 auto_embed: true,
449 timeout: Duration::from_secs(30),
450 chain_strength_method: ChainStrengthMethod::Auto,
451 custom_embedding: None,
452 optimization_level: 1,
453 }
454 }
455 }
456 #[derive(Debug)]
458 pub struct DWaveClient {
459 client: Client,
461 endpoint: String,
463 token: String,
465 runtime: Runtime,
467 default_solver_selector: SolverSelector,
469 default_embedding_config: EmbeddingConfig,
471 max_retries: usize,
473 request_timeout: Duration,
475 problem_timeout: Duration,
477 }
478 impl DWaveClient {
479 pub fn new(token: impl Into<String>, endpoint: Option<String>) -> DWaveResult<Self> {
481 Self::with_config(
482 token,
483 endpoint,
484 SolverSelector::default(),
485 EmbeddingConfig::default(),
486 )
487 }
488 pub fn with_config(
490 token: impl Into<String>,
491 endpoint: Option<String>,
492 solver_selector: SolverSelector,
493 embedding_config: EmbeddingConfig,
494 ) -> DWaveResult<Self> {
495 let client = Client::builder()
496 .timeout(Duration::from_secs(300))
497 .build()
498 .map_err(DWaveError::NetworkError)?;
499 let runtime = Runtime::new().map_err(|e| DWaveError::RuntimeError(e.to_string()))?;
500 let endpoint =
501 endpoint.unwrap_or_else(|| "https://cloud.dwavesys.com/sapi/v2".to_string());
502 Ok(Self {
503 client,
504 endpoint,
505 token: token.into(),
506 runtime,
507 default_solver_selector: solver_selector,
508 default_embedding_config: embedding_config,
509 max_retries: 3,
510 request_timeout: Duration::from_secs(300),
511 problem_timeout: Duration::from_secs(1800),
512 })
513 }
514 pub fn get_solvers(&self) -> DWaveResult<Vec<SolverInfo>> {
516 let url = format!("{}/solvers/remote", self.endpoint);
517 self.runtime.block_on(async {
518 let response = self
519 .client
520 .get(&url)
521 .header("Authorization", format!("token {}", self.token))
522 .send()
523 .await?;
524 if !response.status().is_success() {
525 let status = response.status();
526 let error_text = response.text().await?;
527 return Err(DWaveError::ApiError(format!(
528 "Error getting solvers: {status} - {error_text}"
529 )));
530 }
531 let solvers: Vec<SolverInfo> = response.json().await?;
532 Ok(solvers)
533 })
534 }
535 pub fn submit_ising(
537 &self,
538 model: &IsingModel,
539 solver_id: &str,
540 params: ProblemParams,
541 ) -> DWaveResult<Solution> {
542 let mut linear_terms = serde_json::Map::new();
543 for (qubit, bias) in model.biases() {
544 let value = serde_json::to_value(bias).map_err(|e| {
545 DWaveError::ProblemError(format!("Failed to serialize bias: {e}"))
546 })?;
547 linear_terms.insert(qubit.to_string(), value);
548 }
549 let mut quadratic_terms = serde_json::Map::new();
550 for coupling in model.couplings() {
551 let key = format!("{},{}", coupling.i, coupling.j);
552 let value = serde_json::to_value(coupling.strength).map_err(|e| {
553 DWaveError::ProblemError(format!("Failed to serialize coupling: {e}"))
554 })?;
555 quadratic_terms.insert(key, value);
556 }
557 let problem = Problem {
558 linear_terms: serde_json::Value::Object(linear_terms),
559 quadratic_terms: serde_json::Value::Object(quadratic_terms),
560 type_: "ising".to_string(),
561 solver: solver_id.to_string(),
562 params,
563 };
564 self.submit_problem(&problem)
565 }
566 pub fn submit_qubo(
568 &self,
569 model: &QuboModel,
570 solver_id: &str,
571 params: ProblemParams,
572 ) -> DWaveResult<Solution> {
573 let mut linear_terms = serde_json::Map::new();
574 for (var, value) in model.linear_terms() {
575 let json_value = serde_json::to_value(value).map_err(|e| {
576 DWaveError::ProblemError(format!("Failed to serialize linear term: {e}"))
577 })?;
578 linear_terms.insert(var.to_string(), json_value);
579 }
580 let mut quadratic_terms = serde_json::Map::new();
581 for (var1, var2, value) in model.quadratic_terms() {
582 let key = format!("{var1},{var2}");
583 let json_value = serde_json::to_value(value).map_err(|e| {
584 DWaveError::ProblemError(format!("Failed to serialize quadratic term: {e}"))
585 })?;
586 quadratic_terms.insert(key, json_value);
587 }
588 let problem = Problem {
589 linear_terms: serde_json::Value::Object(linear_terms),
590 quadratic_terms: serde_json::Value::Object(quadratic_terms),
591 type_: "qubo".to_string(),
592 solver: solver_id.to_string(),
593 params,
594 };
595 self.submit_problem(&problem)
596 }
597 pub fn submit_ising_with_flux_bias(
599 &self,
600 model: &IsingModel,
601 solver_id: &str,
602 params: ProblemParams,
603 flux_biases: &std::collections::HashMap<usize, f64>,
604 ) -> DWaveResult<Solution> {
605 let mut params_with_flux = params;
606 let mut flux_map = serde_json::Map::new();
607 for (qubit, &flux_bias) in flux_biases {
608 let value = serde_json::to_value(flux_bias).map_err(|e| {
609 DWaveError::ProblemError(format!("Failed to serialize flux bias: {e}"))
610 })?;
611 flux_map.insert(qubit.to_string(), value);
612 }
613 params_with_flux.flux_bias_map = Some(flux_map);
614 self.submit_ising(model, solver_id, params_with_flux)
615 }
616 fn submit_problem(&self, problem: &Problem) -> DWaveResult<Solution> {
618 let url = format!("{}/problems", self.endpoint);
619 self.runtime.block_on(async {
620 let response = self
621 .client
622 .post(&url)
623 .header("Authorization", format!("token {}", self.token))
624 .header("Content-Type", "application/json")
625 .json(problem)
626 .send()
627 .await?;
628 if !response.status().is_success() {
629 let status = response.status();
630 let error_text = response.text().await?;
631 return Err(DWaveError::ApiError(format!(
632 "Error submitting problem: {status} - {error_text}"
633 )));
634 }
635 let submit_response: serde_json::Value = response.json().await?;
636 let problem_id = submit_response["id"].as_str().ok_or_else(|| {
637 let error_msg = String::from("Failed to extract problem ID from response");
638 DWaveError::ApiError(error_msg)
639 })?;
640 let result_url = format!("{}/problems/{}", self.endpoint, problem_id);
641 let mut attempts = 0;
642 const MAX_ATTEMPTS: usize = 60;
643 while attempts < MAX_ATTEMPTS {
644 let status_response = self
645 .client
646 .get(&result_url)
647 .header("Authorization", format!("token {}", self.token))
648 .send()
649 .await?;
650 if !status_response.status().is_success() {
651 let status = status_response.status();
652 let error_text = status_response.text().await?;
653 return Err(DWaveError::ApiError(format!(
654 "Error getting problem status: {status} - {error_text}"
655 )));
656 }
657 let status: serde_json::Value = status_response.json().await?;
658 if let Some(state) = status["state"].as_str() {
659 if state == "COMPLETED" {
660 return Ok(Solution {
661 energies: serde_json::from_value(status["energies"].clone())?,
662 occurrences: serde_json::from_value(status["occurrences"].clone())?,
663 solutions: serde_json::from_value(status["solutions"].clone())?,
664 num_samples: status["num_samples"].as_u64().unwrap_or(0) as usize,
665 problem_id: problem_id.to_string(),
666 solver: problem.solver.clone(),
667 timing: status["timing"].clone(),
668 });
669 } else if state == "FAILED" {
670 let error = status["error"].as_str().unwrap_or("Unknown error");
671 return Err(DWaveError::ApiError(format!("Problem failed: {error}")));
672 }
673 }
674 tokio::time::sleep(Duration::from_secs(5)).await;
675 attempts += 1;
676 }
677 Err(DWaveError::ApiError(
678 "Timeout waiting for problem solution".into(),
679 ))
680 })
681 }
682 pub fn get_leap_solvers(&self) -> DWaveResult<Vec<LeapSolverInfo>> {
684 let url = format!("{}/solvers/remote", self.endpoint);
685 self.runtime.block_on(async {
686 let response = self
687 .client
688 .get(&url)
689 .header("Authorization", format!("token {}", self.token))
690 .send()
691 .await?;
692 if !response.status().is_success() {
693 let status = response.status();
694 let error_text = response.text().await?;
695 return Err(DWaveError::ApiError(format!(
696 "Error getting Leap solvers: {status} - {error_text}"
697 )));
698 }
699 let solvers: Vec<LeapSolverInfo> = response.json().await?;
700 Ok(solvers)
701 })
702 }
703 pub fn select_solver(
705 &self,
706 selector: Option<&SolverSelector>,
707 ) -> DWaveResult<LeapSolverInfo> {
708 let selector = selector.unwrap_or(&self.default_solver_selector);
709 let solvers = self.get_leap_solvers()?;
710 let filtered_solvers: Vec<_> = solvers
711 .into_iter()
712 .filter(|solver| {
713 let category_match = match selector.category {
714 SolverCategory::QPU => {
715 matches!(solver.solver_type, SolverType::QuantumProcessor)
716 }
717 SolverCategory::Hybrid => {
718 matches!(solver.solver_type, SolverType::Hybrid)
719 }
720 SolverCategory::Software => {
721 matches!(solver.solver_type, SolverType::Software)
722 }
723 SolverCategory::All => true,
724 };
725 let availability_match = !selector.online_only || solver.available;
726 let name_match = selector
727 .name_pattern
728 .as_ref()
729 .map(|pattern| solver.name.contains(pattern))
730 .unwrap_or(true);
731 let queue_match = selector
732 .max_queue_time
733 .map(|max_time| solver.avg_load.unwrap_or(0.0) <= max_time)
734 .unwrap_or(true);
735 category_match && availability_match && name_match && queue_match
736 })
737 .collect();
738 if filtered_solvers.is_empty() {
739 return Err(DWaveError::SolverConfigError(
740 "No solvers match the selection criteria".to_string(),
741 ));
742 }
743 let mut best_solver = filtered_solvers[0].clone();
744 for solver in &filtered_solvers[1..] {
745 let current_load = best_solver.avg_load.unwrap_or(f64::INFINITY);
746 let candidate_load = solver.avg_load.unwrap_or(f64::INFINITY);
747 if candidate_load < current_load {
748 best_solver = solver.clone();
749 }
750 }
751 Ok(best_solver)
752 }
753 pub fn submit_ising_with_embedding(
755 &self,
756 model: &IsingModel,
757 solver_id: Option<&str>,
758 params: Option<AdvancedProblemParams>,
759 embedding_config: Option<&EmbeddingConfig>,
760 ) -> DWaveResult<Solution> {
761 let embedding_config = embedding_config.unwrap_or(&self.default_embedding_config);
762 let solver = if let Some(id) = solver_id {
763 self.get_leap_solvers()?
764 .into_iter()
765 .find(|s| s.id == id)
766 .ok_or_else(|| {
767 DWaveError::SolverConfigError(format!("Solver {id} not found"))
768 })?
769 } else {
770 self.select_solver(None)?
771 };
772 if matches!(solver.solver_type, SolverType::QuantumProcessor) {
773 self.submit_with_auto_embedding(model, &solver, params, embedding_config)
774 } else {
775 let legacy_params = if let Some(p) = params {
776 let flux_bias_map = if let Some(fb) = p.flux_biases {
777 let mut map = serde_json::Map::new();
778 for (k, v) in fb {
779 let value = serde_json::to_value(v).map_err(|e| {
780 DWaveError::ProblemError(format!(
781 "Failed to serialize flux bias: {e}"
782 ))
783 })?;
784 map.insert(k, value);
785 }
786 Some(map)
787 } else {
788 None
789 };
790 ProblemParams {
791 num_reads: p.num_reads,
792 annealing_time: 20,
793 programming_therm: p.programming_thermalization.unwrap_or(1000),
794 readout_therm: p.readout_thermalization.unwrap_or(0),
795 flux_biases: None,
796 flux_bias_map,
797 other: serde_json::Value::Object(serde_json::Map::new()),
798 }
799 } else {
800 ProblemParams::default()
801 };
802 self.submit_ising(model, &solver.id, legacy_params)
803 }
804 }
805 fn submit_with_auto_embedding(
807 &self,
808 model: &IsingModel,
809 solver: &LeapSolverInfo,
810 params: Option<AdvancedProblemParams>,
811 embedding_config: &EmbeddingConfig,
812 ) -> DWaveResult<Solution> {
813 let params = params.unwrap_or_default();
814 let mut logical_edges = Vec::new();
815 for coupling in model.couplings() {
816 logical_edges.push((coupling.i, coupling.j));
817 }
818 let hardware_graph = self.get_solver_topology(&solver.id)?;
819 let embedding = if let Some(custom_emb) = &embedding_config.custom_embedding {
820 custom_emb.clone()
821 } else {
822 let embedder = MinorMiner {
823 max_tries: 10 * embedding_config.optimization_level,
824 ..Default::default()
825 };
826 embedder
827 .find_embedding(&logical_edges, model.num_qubits, &hardware_graph)
828 .map_err(|e| DWaveError::EmbeddingError(e.to_string()))?
829 };
830 let chain_strength =
831 Self::calculate_chain_strength(model, &embedding_config.chain_strength_method);
832 let embedded_problem = Self::embed_problem(model, &embedding, chain_strength)?;
833 let physical_solution =
834 self.submit_embedded_problem(&embedded_problem, solver, params)?;
835 Self::decode_embedded_solution(&physical_solution, &embedding, model)
836 }
837 fn decode_embedded_solution(
856 physical_solution: &Solution,
857 embedding: &Embedding,
858 model: &IsingModel,
859 ) -> DWaveResult<Solution> {
860 let num_logical = model.num_qubits;
861 let mut decoded_solutions = Vec::with_capacity(physical_solution.solutions.len());
862 let mut decoded_energies = Vec::with_capacity(physical_solution.solutions.len());
863 let mut broken_chain_instances = 0usize;
864 let mut total_chain_instances = 0usize;
865
866 for physical_sample in &physical_solution.solutions {
867 let mut logical_sample = vec![1i8; num_logical];
868 for (var, slot) in logical_sample.iter_mut().enumerate().take(num_logical) {
869 let Some(chain) = embedding.chains.get(&var) else {
870 continue;
874 };
875
876 let mut plus = 0usize;
877 let mut minus = 0usize;
878 for &qubit in chain {
879 match physical_sample.get(qubit).copied() {
880 Some(v) if v > 0 => plus += 1,
881 Some(_) => minus += 1,
882 None => {}
883 }
884 }
885
886 total_chain_instances += 1;
887 if plus > 0 && minus > 0 {
888 broken_chain_instances += 1;
889 }
890
891 *slot = match plus.cmp(&minus) {
892 std::cmp::Ordering::Greater => 1,
893 std::cmp::Ordering::Less => -1,
894 std::cmp::Ordering::Equal => {
895 let bias = model.get_bias(var).unwrap_or(0.0);
896 if bias > 0.0 {
897 -1
898 } else {
899 1
900 }
901 }
902 };
903 }
904
905 let energy = model.energy(&logical_sample)?;
906 decoded_energies.push(energy);
907 decoded_solutions.push(logical_sample);
908 }
909
910 let chain_break_fraction = if total_chain_instances == 0 {
911 0.0
912 } else {
913 broken_chain_instances as f64 / total_chain_instances as f64
914 };
915
916 let mut timing = physical_solution.timing.clone();
917 if let serde_json::Value::Object(map) = &mut timing {
918 map.insert(
919 "decoded_chain_break_fraction".to_string(),
920 serde_json::json!(chain_break_fraction),
921 );
922 }
923
924 Ok(Solution {
925 energies: decoded_energies,
926 occurrences: physical_solution.occurrences.clone(),
927 solutions: decoded_solutions,
928 num_samples: physical_solution.num_samples,
929 problem_id: physical_solution.problem_id.clone(),
930 solver: physical_solution.solver.clone(),
931 timing,
932 })
933 }
934 fn get_solver_topology(&self, solver_id: &str) -> DWaveResult<HardwareGraph> {
936 let url = format!("{}/solvers/remote/{}", self.endpoint, solver_id);
937 let topology_info = self.runtime.block_on(async {
938 let response = self
939 .client
940 .get(&url)
941 .header("Authorization", format!("token {}", self.token))
942 .send()
943 .await?;
944 if !response.status().is_success() {
945 let status = response.status();
946 let error_text = response.text().await?;
947 return Err(DWaveError::ApiError(format!(
948 "Error getting solver topology: {status} - {error_text}"
949 )));
950 }
951 let solver_data: serde_json::Value = response.json().await?;
952 Ok(solver_data)
953 })?;
954 let properties = &topology_info["properties"];
955 if let Some(edges) = properties["couplers"].as_array() {
956 let mut hardware_edges = Vec::new();
957 for edge in edges {
958 if let (Some(i), Some(j)) = (edge[0].as_u64(), edge[1].as_u64()) {
959 hardware_edges.push((i as usize, j as usize));
960 }
961 }
962 let num_qubits = properties["qubits"]
963 .as_array()
964 .map(|arr| arr.len())
965 .unwrap_or(0);
966 Ok(HardwareGraph::new_custom(num_qubits, hardware_edges))
967 } else {
968 Err(DWaveError::SolverConfigError(
969 "Could not parse solver topology".to_string(),
970 ))
971 }
972 }
973 fn calculate_chain_strength(model: &IsingModel, method: &ChainStrengthMethod) -> f64 {
975 match method {
976 ChainStrengthMethod::Auto => {
977 let max_coupling = model
978 .couplings()
979 .iter()
980 .map(|c| c.strength.abs())
981 .fold(0.0, f64::max);
982 let max_bias = (0..model.num_qubits)
983 .filter_map(|i| model.get_bias(i).ok())
984 .fold(0.0_f64, |acc, bias| acc.max(bias.abs()));
985 2.0 * (max_coupling.max(max_bias))
986 }
987 ChainStrengthMethod::Fixed(value) => *value,
988 ChainStrengthMethod::Adaptive(multiplier) => {
989 let avg_coupling = model
990 .couplings()
991 .iter()
992 .map(|c| c.strength.abs())
993 .sum::<f64>()
994 / model.couplings().len().max(1) as f64;
995 multiplier * avg_coupling
996 }
997 }
998 }
999 fn embed_problem(
1001 model: &IsingModel,
1002 embedding: &Embedding,
1003 chain_strength: f64,
1004 ) -> DWaveResult<IsingModel> {
1005 let mut embedded_model = IsingModel::new(0);
1006 let max_qubit = embedding
1007 .chains
1008 .values()
1009 .flat_map(|chain| chain.iter())
1010 .max()
1011 .copied()
1012 .unwrap_or(0);
1013 embedded_model = IsingModel::new(max_qubit + 1);
1014 for (var, chain) in &embedding.chains {
1015 if let Ok(bias) = model.get_bias(*var) {
1016 if bias != 0.0 {
1017 let bias_per_qubit = bias / chain.len() as f64;
1018 for &qubit in chain {
1019 embedded_model
1020 .set_bias(qubit, bias_per_qubit)
1021 .map_err(|e| DWaveError::EmbeddingError(e.to_string()))?;
1022 }
1023 }
1024 }
1025 }
1026 for coupling in model.couplings() {
1027 if let (Some(chain1), Some(chain2)) = (
1028 embedding.chains.get(&coupling.i),
1029 embedding.chains.get(&coupling.j),
1030 ) {
1031 for &q1 in chain1 {
1032 for &q2 in chain2 {
1033 embedded_model
1034 .set_coupling(q1, q2, coupling.strength)
1035 .map_err(|e| DWaveError::EmbeddingError(e.to_string()))?;
1036 }
1037 }
1038 }
1039 }
1040 for chain in embedding.chains.values() {
1041 for window in chain.windows(2) {
1042 if let [q1, q2] = window {
1043 embedded_model
1044 .set_coupling(*q1, *q2, -chain_strength)
1045 .map_err(|e| DWaveError::EmbeddingError(e.to_string()))?;
1046 }
1047 }
1048 }
1049 Ok(embedded_model)
1050 }
1051 fn submit_embedded_problem(
1053 &self,
1054 embedded_model: &IsingModel,
1055 solver: &LeapSolverInfo,
1056 params: AdvancedProblemParams,
1057 ) -> DWaveResult<Solution> {
1058 let flux_bias_map = if let Some(fb) = params.flux_biases {
1059 let mut map = serde_json::Map::new();
1060 for (k, v) in fb {
1061 let value = serde_json::to_value(v).map_err(|e| {
1062 DWaveError::ProblemError(format!("Failed to serialize flux bias: {e}"))
1063 })?;
1064 map.insert(k, value);
1065 }
1066 Some(map)
1067 } else {
1068 None
1069 };
1070 let legacy_params = ProblemParams {
1071 num_reads: params.num_reads,
1072 annealing_time: params
1073 .anneal_schedule
1074 .as_ref()
1075 .and_then(|schedule| schedule.schedule.last())
1076 .map(|(time, _)| *time as usize)
1077 .unwrap_or(20),
1078 programming_therm: params.programming_thermalization.unwrap_or(1000),
1079 readout_therm: params.readout_thermalization.unwrap_or(0),
1080 flux_biases: None,
1081 flux_bias_map,
1082 other: serde_json::Value::Object(serde_json::Map::new()),
1083 };
1084 self.submit_ising(embedded_model, &solver.id, legacy_params)
1085 }
1086 pub fn submit_hybrid(
1088 &self,
1089 model: &IsingModel,
1090 solver_id: Option<&str>,
1091 params: Option<HybridSolverParams>,
1092 ) -> DWaveResult<Solution> {
1093 let params = params.unwrap_or_default();
1094 let solver = if let Some(id) = solver_id {
1095 self.get_leap_solvers()?
1096 .into_iter()
1097 .find(|s| s.id == id)
1098 .ok_or_else(|| {
1099 DWaveError::SolverConfigError(format!("Solver {id} not found"))
1100 })?
1101 } else {
1102 let hybrid_selector = SolverSelector {
1103 category: SolverCategory::Hybrid,
1104 ..Default::default()
1105 };
1106 self.select_solver(Some(&hybrid_selector))?
1107 };
1108 let mut linear_terms = serde_json::Map::new();
1109 for (qubit, bias) in model.biases() {
1110 let value = serde_json::to_value(bias).map_err(|e| {
1111 DWaveError::ProblemError(format!("Failed to serialize bias: {e}"))
1112 })?;
1113 linear_terms.insert(qubit.to_string(), value);
1114 }
1115 let mut quadratic_terms = serde_json::Map::new();
1116 for coupling in model.couplings() {
1117 let key = format!("{},{}", coupling.i, coupling.j);
1118 let value = serde_json::to_value(coupling.strength).map_err(|e| {
1119 DWaveError::ProblemError(format!("Failed to serialize coupling: {e}"))
1120 })?;
1121 quadratic_terms.insert(key, value);
1122 }
1123 let mut hybrid_params = params.extra.clone();
1124 if let Some(time_limit) = params.time_limit {
1125 let value = serde_json::to_value(time_limit).map_err(|e| {
1126 DWaveError::ProblemError(format!("Failed to serialize time_limit: {e}"))
1127 })?;
1128 hybrid_params.insert("time_limit".to_string(), value);
1129 }
1130 let problem = Problem {
1131 linear_terms: serde_json::Value::Object(linear_terms),
1132 quadratic_terms: serde_json::Value::Object(quadratic_terms),
1133 type_: "ising".to_string(),
1134 solver: solver.id,
1135 params: ProblemParams {
1136 num_reads: 1,
1137 annealing_time: 1,
1138 programming_therm: 0,
1139 readout_therm: 0,
1140 flux_biases: None,
1141 flux_bias_map: None,
1142 other: serde_json::Value::Object(
1143 hybrid_params.into_iter().map(|(k, v)| (k, v)).collect(),
1144 ),
1145 },
1146 };
1147 self.submit_problem(&problem)
1148 }
1149 pub fn get_problem_status(&self, problem_id: &str) -> DWaveResult<ProblemInfo> {
1151 let url = format!("{}/problems/{}", self.endpoint, problem_id);
1152 self.runtime.block_on(async {
1153 let response = self
1154 .client
1155 .get(&url)
1156 .header("Authorization", format!("token {}", self.token))
1157 .send()
1158 .await?;
1159 if !response.status().is_success() {
1160 let status = response.status();
1161 let error_text = response.text().await?;
1162 return Err(DWaveError::ApiError(format!(
1163 "Error getting problem status: {} - {}",
1164 status, error_text
1165 )));
1166 }
1167 let problem_info: ProblemInfo = response.json().await?;
1168 Ok(problem_info)
1169 })
1170 }
1171 pub fn cancel_problem(&self, problem_id: &str) -> DWaveResult<()> {
1173 let url = format!("{}/problems/{}/cancel", self.endpoint, problem_id);
1174 self.runtime.block_on(async {
1175 let response = self
1176 .client
1177 .delete(&url)
1178 .header("Authorization", format!("token {}", self.token))
1179 .send()
1180 .await?;
1181 if !response.status().is_success() {
1182 let status = response.status();
1183 let error_text = response.text().await?;
1184 return Err(DWaveError::ApiError(format!(
1185 "Error cancelling problem: {} - {}",
1186 status, error_text
1187 )));
1188 }
1189 Ok(())
1190 })
1191 }
1192 pub fn submit_batch(
1194 &self,
1195 problems: Vec<(&IsingModel, Option<&str>, Option<AdvancedProblemParams>)>,
1196 ) -> DWaveResult<BatchSubmissionResult> {
1197 let start_time = Instant::now();
1198 let mut problem_ids = Vec::new();
1199 let mut statuses = Vec::new();
1200 for (model, solver_id, params) in problems {
1201 match self.submit_ising_with_embedding(model, solver_id, params, None) {
1202 Ok(solution) => {
1203 problem_ids.push(solution.problem_id.clone());
1204 statuses.push(Ok(solution.problem_id));
1205 }
1206 Err(e) => {
1207 problem_ids.push(String::new());
1208 statuses.push(Err(e));
1209 }
1210 }
1211 }
1212 Ok(BatchSubmissionResult {
1213 problem_ids,
1214 statuses,
1215 submission_time: start_time.elapsed(),
1216 })
1217 }
1218 pub fn get_problem_metrics(&self, problem_id: &str) -> DWaveResult<ProblemMetrics> {
1220 let solution = self.get_problem_result(problem_id)?;
1221 let timing = &solution.timing;
1222 let queue_time =
1223 Duration::from_micros(timing["qpu_access_overhead_time"].as_u64().unwrap_or(0));
1224 let programming_time =
1225 Duration::from_micros(timing["qpu_programming_time"].as_u64().unwrap_or(0));
1226 let sampling_time =
1227 Duration::from_micros(timing["qpu_sampling_time"].as_u64().unwrap_or(0));
1228 let readout_time =
1229 Duration::from_micros(timing["qpu_readout_time"].as_u64().unwrap_or(0));
1230 let total_time = queue_time + programming_time + sampling_time + readout_time;
1231 let access_time = programming_time + sampling_time + readout_time;
1232 let best_energy = solution
1233 .energies
1234 .iter()
1235 .min_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
1236 .copied()
1237 .unwrap_or(f64::INFINITY);
1238 Ok(ProblemMetrics {
1239 total_time,
1240 queue_time,
1241 access_time,
1242 programming_time,
1243 sampling_time,
1244 readout_time,
1245 best_energy,
1246 num_valid_solutions: solution.solutions.len(),
1247 chain_break_fraction: timing["chain_break_fraction"].as_f64(),
1248 })
1249 }
1250 pub fn get_problem_result(&self, problem_id: &str) -> DWaveResult<Solution> {
1252 let start_time = Instant::now();
1253 loop {
1254 let status = self.get_problem_status(problem_id)?;
1255 match status.status {
1256 ProblemStatus::Completed => {
1257 return self.get_solution_data(problem_id);
1258 }
1259 ProblemStatus::Failed => {
1260 return Err(DWaveError::StatusError(format!(
1261 "Problem {} failed",
1262 problem_id
1263 )));
1264 }
1265 ProblemStatus::Cancelled => {
1266 return Err(DWaveError::StatusError(format!(
1267 "Problem {} was cancelled",
1268 problem_id
1269 )));
1270 }
1271 ProblemStatus::InProgress | ProblemStatus::Pending => {
1272 if start_time.elapsed() > self.problem_timeout {
1273 return Err(DWaveError::TimeoutError(format!(
1274 "Timeout waiting for problem {} completion",
1275 problem_id
1276 )));
1277 }
1278 std::thread::sleep(Duration::from_secs(2));
1279 }
1280 }
1281 }
1282 }
1283 fn get_solution_data(&self, problem_id: &str) -> DWaveResult<Solution> {
1285 let url = format!("{}/problems/{}", self.endpoint, problem_id);
1286 self.runtime.block_on(async {
1287 let response = self
1288 .client
1289 .get(&url)
1290 .header("Authorization", format!("token {}", self.token))
1291 .send()
1292 .await?;
1293 if !response.status().is_success() {
1294 let status = response.status();
1295 let error_text = response.text().await?;
1296 return Err(DWaveError::ApiError(format!(
1297 "Error getting solution data: {} - {}",
1298 status, error_text
1299 )));
1300 }
1301 let data: serde_json::Value = response.json().await?;
1302 Ok(Solution {
1303 energies: serde_json::from_value(data["energies"].clone())?,
1304 occurrences: serde_json::from_value(data["occurrences"].clone())?,
1305 solutions: serde_json::from_value(data["solutions"].clone())?,
1306 num_samples: data["num_samples"].as_u64().unwrap_or(0) as usize,
1307 problem_id: problem_id.to_string(),
1308 solver: data["solver"].as_str().unwrap_or("unknown").to_string(),
1309 timing: data["timing"].clone(),
1310 })
1311 })
1312 }
1313 pub fn list_problems(&self, limit: Option<usize>) -> DWaveResult<Vec<ProblemInfo>> {
1315 let mut url = format!("{}/problems", self.endpoint);
1316 if let Some(limit) = limit {
1317 let _ = write!(url, "?limit={}", limit);
1318 }
1319 self.runtime.block_on(async {
1320 let response = self
1321 .client
1322 .get(&url)
1323 .header("Authorization", format!("token {}", self.token))
1324 .send()
1325 .await?;
1326 if !response.status().is_success() {
1327 let status = response.status();
1328 let error_text = response.text().await?;
1329 return Err(DWaveError::ApiError(format!(
1330 "Error listing problems: {} - {}",
1331 status, error_text
1332 )));
1333 }
1334 let problems: Vec<ProblemInfo> = response.json().await?;
1335 Ok(problems)
1336 })
1337 }
1338 pub fn get_usage_info(&self) -> DWaveResult<serde_json::Value> {
1340 let url = format!("{}/usage", self.endpoint);
1341 self.runtime.block_on(async {
1342 let response = self
1343 .client
1344 .get(&url)
1345 .header("Authorization", format!("token {}", self.token))
1346 .send()
1347 .await?;
1348 if !response.status().is_success() {
1349 let status = response.status();
1350 let error_text = response.text().await?;
1351 return Err(DWaveError::ApiError(format!(
1352 "Error getting usage info: {} - {}",
1353 status, error_text
1354 )));
1355 }
1356 let usage: serde_json::Value = response.json().await?;
1357 Ok(usage)
1358 })
1359 }
1360 }
1361
1362 #[cfg(test)]
1363 mod tests {
1364 use super::*;
1365
1366 #[test]
1367 fn decode_embedded_solution_unembeds_chains_and_recomputes_real_energy() {
1368 let mut model = IsingModel::new(2);
1373 model.set_bias(0, 0.3).expect("set_bias should succeed");
1374 model.set_bias(1, -0.5).expect("set_bias should succeed");
1375 model
1376 .set_coupling(0, 1, -0.2)
1377 .expect("set_coupling should succeed");
1378
1379 let mut embedding = Embedding::new();
1380 embedding
1381 .add_chain(0, vec![0, 1, 2])
1382 .expect("add_chain should succeed");
1383 embedding
1384 .add_chain(1, vec![3, 4])
1385 .expect("add_chain should succeed");
1386
1387 let physical_solution = Solution {
1388 energies: vec![-999.0], occurrences: vec![1],
1392 solutions: vec![vec![1, 1, 1, 1, -1]],
1393 num_samples: 1,
1394 problem_id: "test-problem".to_string(),
1395 solver: "test-solver".to_string(),
1396 timing: serde_json::json!({}),
1397 };
1398
1399 let decoded =
1400 DWaveClient::decode_embedded_solution(&physical_solution, &embedding, &model)
1401 .expect("decoding should succeed");
1402
1403 assert_eq!(decoded.solutions.len(), 1);
1404 assert_eq!(
1405 decoded.solutions[0].len(),
1406 2,
1407 "must be re-indexed to logical variables"
1408 );
1409 assert_eq!(decoded.solutions[0], vec![1, 1]);
1412
1413 assert!((decoded.energies[0] - (-0.4)).abs() < 1e-9);
1417
1418 let reported = decoded.timing["decoded_chain_break_fraction"]
1421 .as_f64()
1422 .expect("chain break fraction should be present");
1423 assert!((reported - 0.5).abs() < 1e-9);
1424 }
1425
1426 #[test]
1427 fn decode_embedded_solution_reports_zero_breaks_for_fully_agreeing_chains() {
1428 let mut model = IsingModel::new(1);
1429 model.set_bias(0, 1.0).expect("set_bias should succeed");
1430
1431 let mut embedding = Embedding::new();
1432 embedding
1433 .add_chain(0, vec![0, 1, 2])
1434 .expect("add_chain should succeed");
1435
1436 let physical_solution = Solution {
1437 energies: vec![0.0],
1438 occurrences: vec![1],
1439 solutions: vec![vec![-1, -1, -1]],
1440 num_samples: 1,
1441 problem_id: "test-problem".to_string(),
1442 solver: "test-solver".to_string(),
1443 timing: serde_json::json!({}),
1444 };
1445
1446 let decoded =
1447 DWaveClient::decode_embedded_solution(&physical_solution, &embedding, &model)
1448 .expect("decoding should succeed");
1449
1450 assert_eq!(decoded.solutions[0], vec![-1]);
1451 assert!((decoded.energies[0] - (-1.0)).abs() < 1e-9);
1452 let reported = decoded.timing["decoded_chain_break_fraction"]
1453 .as_f64()
1454 .expect("chain break fraction should be present");
1455 assert!(reported.abs() < 1e-9);
1456 }
1457 }
1458}
1459#[cfg(not(feature = "dwave"))]
1460mod placeholder {
1461 use thiserror::Error;
1462 #[derive(Error, Debug)]
1464 pub enum DWaveError {
1465 #[error("D-Wave feature not enabled. Recompile with '--features dwave'")]
1467 NotEnabled,
1468 }
1469 pub type DWaveResult<T> = Result<T, DWaveError>;
1471 #[derive(Debug, Clone)]
1473 pub struct DWaveClient {
1474 _private: (),
1475 }
1476 impl DWaveClient {
1477 pub fn new(_token: impl Into<String>, _endpoint: Option<String>) -> DWaveResult<Self> {
1479 Err(DWaveError::NotEnabled)
1480 }
1481 }
1482 #[derive(Debug, Clone)]
1484 pub struct ProblemParams {
1485 pub num_reads: usize,
1487 pub annealing_time: usize,
1489 pub programming_therm: usize,
1491 pub readout_therm: usize,
1493 }
1494 #[derive(Debug, Clone)]
1496 pub enum SolverType {
1497 QuantumProcessor,
1498 Hybrid,
1499 Software,
1500 }
1501 #[derive(Debug, Clone)]
1502 pub enum SolverCategory {
1503 QPU,
1504 Hybrid,
1505 Software,
1506 All,
1507 }
1508 #[derive(Debug, Clone)]
1509 pub enum ProblemStatus {
1510 InProgress,
1511 Completed,
1512 Failed,
1513 Cancelled,
1514 Pending,
1515 }
1516 #[derive(Debug, Clone)]
1517 pub struct SolverSelector;
1518 #[derive(Debug, Clone)]
1519 pub struct EmbeddingConfig;
1520 #[derive(Debug, Clone)]
1521 pub struct AdvancedProblemParams;
1522 #[derive(Debug, Clone)]
1523 pub struct HybridSolverParams;
1524 #[derive(Debug, Clone)]
1525 pub struct LeapSolverInfo;
1526 #[derive(Debug, Clone)]
1527 pub struct ProblemInfo;
1528 #[derive(Debug, Clone)]
1529 pub struct AnnealingSchedule;
1530 #[derive(Debug, Clone)]
1531 pub struct ProblemMetrics;
1532 #[derive(Debug, Clone)]
1533 pub struct BatchSubmissionResult;
1534 #[derive(Debug, Clone)]
1535 pub enum ChainStrengthMethod {
1536 Auto,
1537 Fixed(f64),
1538 Adaptive(f64),
1539 }
1540 impl Default for ProblemParams {
1541 fn default() -> Self {
1542 Self {
1543 num_reads: 1000,
1544 annealing_time: 20,
1545 programming_therm: 1000,
1546 readout_therm: 0,
1547 }
1548 }
1549 }
1550}
1551#[must_use]
1553pub const fn is_available() -> bool {
1554 cfg!(feature = "dwave")
1555}