1#[cfg(feature = "dwave")]
20mod client {
21 use reqwest::Client;
22 use serde::{Deserialize, Serialize};
23 use std::collections::HashMap;
24 use std::fmt::Write;
25 use std::time::{Duration, Instant};
26 use thiserror::Error;
27 use tokio::runtime::Runtime;
28
29 use crate::embedding::{Embedding, HardwareGraph, MinorMiner};
30 use crate::ising::{IsingError, IsingModel, QuboModel};
31
32 #[derive(Error, Debug)]
34 pub enum DWaveError {
35 #[error("Ising error: {0}")]
37 IsingError(#[from] IsingError),
38
39 #[error("Network error: {0}")]
41 NetworkError(#[from] reqwest::Error),
42
43 #[error("Response parsing error: {0}")]
45 ParseError(#[from] serde_json::Error),
46
47 #[error("D-Wave API error: {0}")]
49 ApiError(String),
50
51 #[error("Authentication error: {0}")]
53 AuthError(String),
54
55 #[error("Runtime error: {0}")]
57 RuntimeError(String),
58
59 #[error("Problem formulation error: {0}")]
61 ProblemError(String),
62
63 #[error("Embedding error: {0}")]
65 EmbeddingError(String),
66
67 #[error("Hybrid solver error: {0}")]
69 HybridSolverError(String),
70
71 #[error("Problem status error: {0}")]
73 StatusError(String),
74
75 #[error("Batch operation error: {0}")]
77 BatchError(String),
78
79 #[error("Solver configuration error: {0}")]
81 SolverConfigError(String),
82
83 #[error("Operation timed out: {0}")]
85 TimeoutError(String),
86 }
87
88 pub type DWaveResult<T> = Result<T, DWaveError>;
90
91 #[derive(Debug, Clone, Serialize, Deserialize)]
93 pub struct SolverInfo {
94 pub id: String,
96
97 pub name: String,
99
100 pub description: String,
102
103 pub num_qubits: usize,
105
106 pub connectivity: SolverConnectivity,
108
109 pub properties: SolverProperties,
111 }
112
113 #[derive(Debug, Clone, Serialize, Deserialize)]
115 pub struct SolverConnectivity {
116 #[serde(rename = "type")]
118 pub type_: String,
119
120 #[serde(flatten)]
122 pub params: serde_json::Value,
123 }
124
125 #[derive(Debug, Clone, Serialize, Deserialize)]
127 pub struct SolverProperties {
128 pub parameters: serde_json::Value,
130
131 #[serde(flatten)]
133 pub other: serde_json::Value,
134 }
135
136 #[derive(Debug, Clone, Serialize, Deserialize)]
138 pub struct ProblemParams {
139 pub num_reads: usize,
141
142 pub annealing_time: usize,
144
145 #[serde(rename = "programming_thermalization")]
147 pub programming_therm: usize,
148
149 #[serde(rename = "readout_thermalization")]
151 pub readout_therm: usize,
152
153 #[serde(rename = "flux_biases", skip_serializing_if = "Option::is_none")]
155 pub flux_biases: Option<Vec<f64>>,
156
157 #[serde(rename = "flux_bias", skip_serializing_if = "Option::is_none")]
159 pub flux_bias_map: Option<serde_json::Map<String, serde_json::Value>>,
160
161 #[serde(flatten)]
163 pub other: serde_json::Value,
164 }
165
166 impl Default for ProblemParams {
167 fn default() -> Self {
168 Self {
169 num_reads: 1000,
170 annealing_time: 20,
171 programming_therm: 1000,
172 readout_therm: 0,
173 flux_biases: None,
174 flux_bias_map: None,
175 other: serde_json::Value::Object(serde_json::Map::new()),
176 }
177 }
178 }
179
180 #[derive(Debug, Clone, Serialize, Deserialize)]
182 pub struct Problem {
183 #[serde(rename = "linear")]
185 pub linear_terms: serde_json::Value,
186
187 #[serde(rename = "quadratic")]
189 pub quadratic_terms: serde_json::Value,
190
191 #[serde(rename = "type")]
193 pub type_: String,
194
195 pub solver: String,
197
198 pub params: ProblemParams,
200 }
201
202 #[derive(Debug, Clone, Serialize, Deserialize)]
204 pub struct Solution {
205 pub energies: Vec<f64>,
207
208 pub occurrences: Vec<usize>,
210
211 pub solutions: Vec<Vec<i8>>,
213
214 pub num_samples: usize,
216
217 pub problem_id: String,
219
220 pub solver: String,
222
223 pub timing: serde_json::Value,
225 }
226
227 #[derive(Debug, Clone, Serialize, Deserialize)]
229 pub enum SolverType {
230 #[serde(rename = "qpu")]
232 QuantumProcessor,
233 #[serde(rename = "hybrid")]
235 Hybrid,
236 #[serde(rename = "dqm")]
238 DiscreteQuadraticModel,
239 #[serde(rename = "cqm")]
241 ConstrainedQuadraticModel,
242 #[serde(rename = "software")]
244 Software,
245 }
246
247 #[derive(Debug, Clone, PartialEq, Eq)]
249 pub enum SolverCategory {
250 QPU,
252 Hybrid,
254 Software,
256 All,
258 }
259
260 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
262 pub enum ProblemStatus {
263 #[serde(rename = "IN_PROGRESS")]
265 InProgress,
266 #[serde(rename = "COMPLETED")]
268 Completed,
269 #[serde(rename = "FAILED")]
271 Failed,
272 #[serde(rename = "CANCELLED")]
274 Cancelled,
275 #[serde(rename = "PENDING")]
277 Pending,
278 }
279
280 #[derive(Debug, Clone, Serialize, Deserialize)]
282 pub struct ProblemInfo {
283 pub id: String,
285 pub status: ProblemStatus,
287 pub submitted_on: String,
289 pub solver: String,
291 #[serde(rename = "type")]
293 pub problem_type: String,
294 pub params: serde_json::Value,
296 #[serde(flatten)]
298 pub metadata: serde_json::Value,
299 }
300
301 #[derive(Debug, Clone, Serialize, Deserialize)]
303 pub struct LeapSolverInfo {
304 pub id: String,
306 pub name: String,
308 pub description: String,
310 #[serde(rename = "category")]
312 pub solver_type: SolverType,
313 pub status: String,
315 pub properties: serde_json::Value,
317 pub problem_types: Vec<String>,
319 pub avg_load: Option<f64>,
321 pub available: bool,
323 }
324
325 #[derive(Debug, Clone, Serialize, Deserialize)]
327 pub struct AnnealingSchedule {
328 pub schedule: Vec<(f64, f64)>,
330 }
331
332 impl AnnealingSchedule {
333 pub fn linear(annealing_time: f64) -> Self {
335 Self {
336 schedule: vec![
337 (0.0, 1.0), (annealing_time, 0.0), ],
340 }
341 }
342
343 pub fn pause_and_ramp(annealing_time: f64, pause_start: f64, pause_duration: f64) -> Self {
345 Self {
346 schedule: vec![
347 (0.0, 1.0),
348 (pause_start, 1.0 - pause_start / annealing_time),
349 (
350 pause_start + pause_duration,
351 1.0 - pause_start / annealing_time,
352 ),
353 (annealing_time, 0.0),
354 ],
355 }
356 }
357
358 pub fn custom(points: Vec<(f64, f64)>) -> Self {
360 Self { schedule: points }
361 }
362 }
363
364 #[derive(Debug, Clone, Serialize, Deserialize)]
366 pub struct AdvancedProblemParams {
367 pub num_reads: usize,
369 #[serde(skip_serializing_if = "Option::is_none")]
371 pub anneal_schedule: Option<AnnealingSchedule>,
372 #[serde(skip_serializing_if = "Option::is_none")]
374 pub programming_thermalization: Option<usize>,
375 #[serde(skip_serializing_if = "Option::is_none")]
377 pub readout_thermalization: Option<usize>,
378 #[serde(skip_serializing_if = "Option::is_none")]
380 pub auto_scale: Option<bool>,
381 #[serde(skip_serializing_if = "Option::is_none")]
383 pub chain_strength: Option<f64>,
384 #[serde(skip_serializing_if = "Option::is_none")]
386 pub flux_biases: Option<HashMap<String, f64>>,
387 #[serde(flatten)]
389 pub extra: HashMap<String, serde_json::Value>,
390 }
391
392 impl Default for AdvancedProblemParams {
393 fn default() -> Self {
394 Self {
395 num_reads: 1000,
396 anneal_schedule: None,
397 programming_thermalization: Some(1000),
398 readout_thermalization: Some(0),
399 auto_scale: Some(true),
400 chain_strength: None,
401 flux_biases: None,
402 extra: HashMap::new(),
403 }
404 }
405 }
406
407 #[derive(Debug, Clone, Serialize, Deserialize)]
409 pub struct HybridSolverParams {
410 #[serde(skip_serializing_if = "Option::is_none")]
412 pub time_limit: Option<f64>,
413 #[serde(skip_serializing_if = "Option::is_none")]
415 pub max_variables: Option<usize>,
416 #[serde(flatten)]
418 pub extra: HashMap<String, serde_json::Value>,
419 }
420
421 impl Default for HybridSolverParams {
422 fn default() -> Self {
423 Self {
424 time_limit: Some(5.0),
425 max_variables: None,
426 extra: HashMap::new(),
427 }
428 }
429 }
430
431 #[derive(Debug, Clone)]
433 pub struct ProblemMetrics {
434 pub total_time: Duration,
436 pub queue_time: Duration,
438 pub access_time: Duration,
440 pub programming_time: Duration,
442 pub sampling_time: Duration,
444 pub readout_time: Duration,
446 pub best_energy: f64,
448 pub num_valid_solutions: usize,
450 pub chain_break_fraction: Option<f64>,
452 }
453
454 #[derive(Debug)]
456 pub struct BatchSubmissionResult {
457 pub problem_ids: Vec<String>,
459 pub statuses: Vec<Result<String, DWaveError>>,
461 pub submission_time: Duration,
463 }
464
465 #[derive(Debug, Clone)]
467 pub struct SolverSelector {
468 pub category: SolverCategory,
470 pub min_qubits: Option<usize>,
472 pub max_queue_time: Option<f64>,
474 pub online_only: bool,
476 pub name_pattern: Option<String>,
478 pub topology_preference: Option<String>,
480 }
481
482 impl Default for SolverSelector {
483 fn default() -> Self {
484 Self {
485 category: SolverCategory::All,
486 min_qubits: None,
487 max_queue_time: None,
488 online_only: true,
489 name_pattern: None,
490 topology_preference: None,
491 }
492 }
493 }
494
495 #[derive(Debug, Clone)]
497 pub struct EmbeddingConfig {
498 pub auto_embed: bool,
500 pub timeout: Duration,
502 pub chain_strength_method: ChainStrengthMethod,
504 pub custom_embedding: Option<Embedding>,
506 pub optimization_level: usize,
508 }
509
510 #[derive(Debug, Clone)]
512 pub enum ChainStrengthMethod {
513 Auto,
515 Fixed(f64),
517 Adaptive(f64), }
520
521 impl Default for EmbeddingConfig {
522 fn default() -> Self {
523 Self {
524 auto_embed: true,
525 timeout: Duration::from_secs(30),
526 chain_strength_method: ChainStrengthMethod::Auto,
527 custom_embedding: None,
528 optimization_level: 1,
529 }
530 }
531 }
532
533 #[derive(Debug)]
535 pub struct DWaveClient {
536 client: Client,
538
539 endpoint: String,
541
542 token: String,
544
545 runtime: Runtime,
547
548 default_solver_selector: SolverSelector,
550
551 default_embedding_config: EmbeddingConfig,
553
554 max_retries: usize,
556
557 request_timeout: Duration,
559
560 problem_timeout: Duration,
562 }
563
564 impl DWaveClient {
565 pub fn new(token: impl Into<String>, endpoint: Option<String>) -> DWaveResult<Self> {
567 Self::with_config(
568 token,
569 endpoint,
570 SolverSelector::default(),
571 EmbeddingConfig::default(),
572 )
573 }
574
575 pub fn with_config(
577 token: impl Into<String>,
578 endpoint: Option<String>,
579 solver_selector: SolverSelector,
580 embedding_config: EmbeddingConfig,
581 ) -> DWaveResult<Self> {
582 let client = Client::builder()
584 .timeout(Duration::from_secs(300)) .build()
586 .map_err(DWaveError::NetworkError)?;
587
588 let runtime = Runtime::new().map_err(|e| DWaveError::RuntimeError(e.to_string()))?;
590
591 let endpoint =
593 endpoint.unwrap_or_else(|| "https://cloud.dwavesys.com/sapi/v2".to_string());
594
595 Ok(Self {
596 client,
597 endpoint,
598 token: token.into(),
599 runtime,
600 default_solver_selector: solver_selector,
601 default_embedding_config: embedding_config,
602 max_retries: 3,
603 request_timeout: Duration::from_secs(300),
604 problem_timeout: Duration::from_secs(1800), })
606 }
607
608 pub fn get_solvers(&self) -> DWaveResult<Vec<SolverInfo>> {
610 let url = format!("{}/solvers/remote", self.endpoint);
612
613 self.runtime.block_on(async {
615 let response = self
616 .client
617 .get(&url)
618 .header("Authorization", format!("token {}", self.token))
619 .send()
620 .await?;
621
622 if !response.status().is_success() {
624 let status = response.status();
625 let error_text = response.text().await?;
626 return Err(DWaveError::ApiError(format!(
627 "Error getting solvers: {status} - {error_text}"
628 )));
629 }
630
631 let solvers: Vec<SolverInfo> = response.json().await?;
633 Ok(solvers)
634 })
635 }
636
637 pub fn submit_ising(
639 &self,
640 model: &IsingModel,
641 solver_id: &str,
642 params: ProblemParams,
643 ) -> DWaveResult<Solution> {
644 let mut linear_terms = serde_json::Map::new();
646 for (qubit, bias) in model.biases() {
647 let value = serde_json::to_value(bias).map_err(|e| {
648 DWaveError::ProblemError(format!("Failed to serialize bias: {e}"))
649 })?;
650 linear_terms.insert(qubit.to_string(), value);
651 }
652
653 let mut quadratic_terms = serde_json::Map::new();
654 for coupling in model.couplings() {
655 let key = format!("{},{}", coupling.i, coupling.j);
656 let value = serde_json::to_value(coupling.strength).map_err(|e| {
657 DWaveError::ProblemError(format!("Failed to serialize coupling: {e}"))
658 })?;
659 quadratic_terms.insert(key, value);
660 }
661
662 let problem = Problem {
664 linear_terms: serde_json::Value::Object(linear_terms),
665 quadratic_terms: serde_json::Value::Object(quadratic_terms),
666 type_: "ising".to_string(),
667 solver: solver_id.to_string(),
668 params,
669 };
670
671 self.submit_problem(&problem)
673 }
674
675 pub fn submit_qubo(
677 &self,
678 model: &QuboModel,
679 solver_id: &str,
680 params: ProblemParams,
681 ) -> DWaveResult<Solution> {
682 let mut linear_terms = serde_json::Map::new();
684 for (var, value) in model.linear_terms() {
685 let json_value = serde_json::to_value(value).map_err(|e| {
686 DWaveError::ProblemError(format!("Failed to serialize linear term: {e}"))
687 })?;
688 linear_terms.insert(var.to_string(), json_value);
689 }
690
691 let mut quadratic_terms = serde_json::Map::new();
692 for (var1, var2, value) in model.quadratic_terms() {
693 let key = format!("{var1},{var2}");
694 let json_value = serde_json::to_value(value).map_err(|e| {
695 DWaveError::ProblemError(format!("Failed to serialize quadratic term: {e}"))
696 })?;
697 quadratic_terms.insert(key, json_value);
698 }
699
700 let problem = Problem {
702 linear_terms: serde_json::Value::Object(linear_terms),
703 quadratic_terms: serde_json::Value::Object(quadratic_terms),
704 type_: "qubo".to_string(),
705 solver: solver_id.to_string(),
706 params,
707 };
708
709 self.submit_problem(&problem)
711 }
712
713 pub fn submit_ising_with_flux_bias(
715 &self,
716 model: &IsingModel,
717 solver_id: &str,
718 params: ProblemParams,
719 flux_biases: &std::collections::HashMap<usize, f64>,
720 ) -> DWaveResult<Solution> {
721 let mut params_with_flux = params;
722
723 let mut flux_map = serde_json::Map::new();
725 for (qubit, &flux_bias) in flux_biases {
726 let value = serde_json::to_value(flux_bias).map_err(|e| {
727 DWaveError::ProblemError(format!("Failed to serialize flux bias: {e}"))
728 })?;
729 flux_map.insert(qubit.to_string(), value);
730 }
731 params_with_flux.flux_bias_map = Some(flux_map);
732
733 self.submit_ising(model, solver_id, params_with_flux)
734 }
735
736 fn submit_problem(&self, problem: &Problem) -> DWaveResult<Solution> {
738 let url = format!("{}/problems", self.endpoint);
740
741 self.runtime.block_on(async {
743 let response = self
745 .client
746 .post(&url)
747 .header("Authorization", format!("token {}", self.token))
748 .header("Content-Type", "application/json")
749 .json(problem)
750 .send()
751 .await?;
752
753 if !response.status().is_success() {
755 let status = response.status();
756 let error_text = response.text().await?;
757 return Err(DWaveError::ApiError(format!(
758 "Error submitting problem: {status} - {error_text}"
759 )));
760 }
761
762 let submit_response: serde_json::Value = response.json().await?;
764 let problem_id = submit_response["id"].as_str().ok_or_else(|| {
765 let error_msg = String::from("Failed to extract problem ID from response");
767 DWaveError::ApiError(error_msg)
769 })?;
770
771 let result_url = format!("{}/problems/{}", self.endpoint, problem_id);
773 let mut attempts = 0;
774 const MAX_ATTEMPTS: usize = 60; while attempts < MAX_ATTEMPTS {
777 let status_response = self
779 .client
780 .get(&result_url)
781 .header("Authorization", format!("token {}", self.token))
782 .send()
783 .await?;
784
785 if !status_response.status().is_success() {
787 let status = status_response.status();
788 let error_text = status_response.text().await?;
789 return Err(DWaveError::ApiError(format!(
790 "Error getting problem status: {status} - {error_text}"
791 )));
792 }
793
794 let status: serde_json::Value = status_response.json().await?;
796
797 if let Some(state) = status["state"].as_str() {
799 if state == "COMPLETED" {
800 return Ok(Solution {
802 energies: serde_json::from_value(status["energies"].clone())?,
803 occurrences: serde_json::from_value(status["occurrences"].clone())?,
804 solutions: serde_json::from_value(status["solutions"].clone())?,
805 num_samples: status["num_samples"].as_u64().unwrap_or(0) as usize,
806 problem_id: problem_id.to_string(),
807 solver: problem.solver.clone(),
808 timing: status["timing"].clone(),
809 });
810 } else if state == "FAILED" {
811 let error = status["error"].as_str().unwrap_or("Unknown error");
812 return Err(DWaveError::ApiError(format!("Problem failed: {error}")));
813 }
814 }
815
816 tokio::time::sleep(Duration::from_secs(5)).await;
818 attempts += 1;
819 }
820
821 Err(DWaveError::ApiError(
822 "Timeout waiting for problem solution".into(),
823 ))
824 })
825 }
826
827 pub fn get_leap_solvers(&self) -> DWaveResult<Vec<LeapSolverInfo>> {
829 let url = format!("{}/solvers/remote", self.endpoint);
830
831 self.runtime.block_on(async {
832 let response = self
833 .client
834 .get(&url)
835 .header("Authorization", format!("token {}", self.token))
836 .send()
837 .await?;
838
839 if !response.status().is_success() {
840 let status = response.status();
841 let error_text = response.text().await?;
842 return Err(DWaveError::ApiError(format!(
843 "Error getting Leap solvers: {status} - {error_text}"
844 )));
845 }
846
847 let solvers: Vec<LeapSolverInfo> = response.json().await?;
848 Ok(solvers)
849 })
850 }
851
852 pub fn select_solver(
854 &self,
855 selector: Option<&SolverSelector>,
856 ) -> DWaveResult<LeapSolverInfo> {
857 let selector = selector.unwrap_or(&self.default_solver_selector);
858 let solvers = self.get_leap_solvers()?;
859
860 let filtered_solvers: Vec<_> = solvers
861 .into_iter()
862 .filter(|solver| {
863 let category_match = match selector.category {
865 SolverCategory::QPU => {
866 matches!(solver.solver_type, SolverType::QuantumProcessor)
867 }
868 SolverCategory::Hybrid => matches!(solver.solver_type, SolverType::Hybrid),
869 SolverCategory::Software => {
870 matches!(solver.solver_type, SolverType::Software)
871 }
872 SolverCategory::All => true,
873 };
874
875 let availability_match = !selector.online_only || solver.available;
877
878 let name_match = selector
880 .name_pattern
881 .as_ref()
882 .map(|pattern| solver.name.contains(pattern))
883 .unwrap_or(true);
884
885 let queue_match = selector
887 .max_queue_time
888 .map(|max_time| solver.avg_load.unwrap_or(0.0) <= max_time)
889 .unwrap_or(true);
890
891 category_match && availability_match && name_match && queue_match
892 })
893 .collect();
894
895 if filtered_solvers.is_empty() {
896 return Err(DWaveError::SolverConfigError(
897 "No solvers match the selection criteria".to_string(),
898 ));
899 }
900
901 let mut best_solver = filtered_solvers[0].clone();
903 for solver in &filtered_solvers[1..] {
904 let current_load = best_solver.avg_load.unwrap_or(f64::INFINITY);
905 let candidate_load = solver.avg_load.unwrap_or(f64::INFINITY);
906 if candidate_load < current_load {
907 best_solver = solver.clone();
908 }
909 }
910
911 Ok(best_solver)
912 }
913
914 pub fn submit_ising_with_embedding(
916 &self,
917 model: &IsingModel,
918 solver_id: Option<&str>,
919 params: Option<AdvancedProblemParams>,
920 embedding_config: Option<&EmbeddingConfig>,
921 ) -> DWaveResult<Solution> {
922 let embedding_config = embedding_config.unwrap_or(&self.default_embedding_config);
923
924 let solver = if let Some(id) = solver_id {
926 self.get_leap_solvers()?
927 .into_iter()
928 .find(|s| s.id == id)
929 .ok_or_else(|| {
930 DWaveError::SolverConfigError(format!("Solver {id} not found"))
931 })?
932 } else {
933 self.select_solver(None)?
934 };
935
936 if matches!(solver.solver_type, SolverType::QuantumProcessor) {
938 self.submit_with_auto_embedding(model, &solver, params, embedding_config)
939 } else {
940 let legacy_params = if let Some(p) = params {
942 let flux_bias_map = if let Some(fb) = p.flux_biases {
943 let mut map = serde_json::Map::new();
944 for (k, v) in fb {
945 let value = serde_json::to_value(v).map_err(|e| {
946 DWaveError::ProblemError(format!(
947 "Failed to serialize flux bias: {e}"
948 ))
949 })?;
950 map.insert(k, value);
951 }
952 Some(map)
953 } else {
954 None
955 };
956 ProblemParams {
957 num_reads: p.num_reads,
958 annealing_time: 20,
959 programming_therm: p.programming_thermalization.unwrap_or(1000),
960 readout_therm: p.readout_thermalization.unwrap_or(0),
961 flux_biases: None, flux_bias_map,
963 other: serde_json::Value::Object(serde_json::Map::new()),
964 }
965 } else {
966 ProblemParams::default()
967 };
968
969 self.submit_ising(model, &solver.id, legacy_params)
970 }
971 }
972
973 fn submit_with_auto_embedding(
975 &self,
976 model: &IsingModel,
977 solver: &LeapSolverInfo,
978 params: Option<AdvancedProblemParams>,
979 embedding_config: &EmbeddingConfig,
980 ) -> DWaveResult<Solution> {
981 let params = params.unwrap_or_default();
982
983 let mut logical_edges = Vec::new();
985 for coupling in model.couplings() {
986 logical_edges.push((coupling.i, coupling.j));
987 }
988
989 let hardware_graph = self.get_solver_topology(&solver.id)?;
991
992 let embedding = if let Some(custom_emb) = &embedding_config.custom_embedding {
994 custom_emb.clone()
995 } else {
996 let embedder = MinorMiner {
997 max_tries: 10 * embedding_config.optimization_level,
998 ..Default::default()
999 };
1000 embedder
1001 .find_embedding(&logical_edges, model.num_qubits, &hardware_graph)
1002 .map_err(|e| DWaveError::EmbeddingError(e.to_string()))?
1003 };
1004
1005 let chain_strength =
1007 Self::calculate_chain_strength(model, &embedding_config.chain_strength_method);
1008
1009 let embedded_problem = Self::embed_problem(model, &embedding, chain_strength)?;
1011
1012 self.submit_embedded_problem(&embedded_problem, solver, params)
1014 }
1015
1016 fn get_solver_topology(&self, solver_id: &str) -> DWaveResult<HardwareGraph> {
1018 let url = format!("{}/solvers/remote/{}", self.endpoint, solver_id);
1019
1020 let topology_info = self.runtime.block_on(async {
1021 let response = self
1022 .client
1023 .get(&url)
1024 .header("Authorization", format!("token {}", self.token))
1025 .send()
1026 .await?;
1027
1028 if !response.status().is_success() {
1029 let status = response.status();
1030 let error_text = response.text().await?;
1031 return Err(DWaveError::ApiError(format!(
1032 "Error getting solver topology: {status} - {error_text}"
1033 )));
1034 }
1035
1036 let solver_data: serde_json::Value = response.json().await?;
1037 Ok(solver_data)
1038 })?;
1039
1040 let properties = &topology_info["properties"];
1042
1043 if let Some(edges) = properties["couplers"].as_array() {
1044 let mut hardware_edges = Vec::new();
1045 for edge in edges {
1046 if let (Some(i), Some(j)) = (edge[0].as_u64(), edge[1].as_u64()) {
1047 hardware_edges.push((i as usize, j as usize));
1048 }
1049 }
1050
1051 let num_qubits = properties["qubits"]
1052 .as_array()
1053 .map(|arr| arr.len())
1054 .unwrap_or(0);
1055
1056 Ok(HardwareGraph::new_custom(num_qubits, hardware_edges))
1057 } else {
1058 Err(DWaveError::SolverConfigError(
1059 "Could not parse solver topology".to_string(),
1060 ))
1061 }
1062 }
1063
1064 fn calculate_chain_strength(model: &IsingModel, method: &ChainStrengthMethod) -> f64 {
1066 match method {
1067 ChainStrengthMethod::Auto => {
1068 let max_coupling = model
1070 .couplings()
1071 .iter()
1072 .map(|c| c.strength.abs())
1073 .fold(0.0, f64::max);
1074
1075 let max_bias = (0..model.num_qubits)
1076 .filter_map(|i| model.get_bias(i).ok())
1077 .fold(0.0_f64, |acc, bias| acc.max(bias.abs()));
1078
1079 2.0 * (max_coupling.max(max_bias))
1080 }
1081 ChainStrengthMethod::Fixed(value) => *value,
1082 ChainStrengthMethod::Adaptive(multiplier) => {
1083 let avg_coupling = model
1084 .couplings()
1085 .iter()
1086 .map(|c| c.strength.abs())
1087 .sum::<f64>()
1088 / model.couplings().len().max(1) as f64;
1089
1090 multiplier * avg_coupling
1091 }
1092 }
1093 }
1094
1095 fn embed_problem(
1097 model: &IsingModel,
1098 embedding: &Embedding,
1099 chain_strength: f64,
1100 ) -> DWaveResult<IsingModel> {
1101 let mut embedded_model = IsingModel::new(0); let max_qubit = embedding
1105 .chains
1106 .values()
1107 .flat_map(|chain| chain.iter())
1108 .max()
1109 .copied()
1110 .unwrap_or(0);
1111
1112 embedded_model = IsingModel::new(max_qubit + 1);
1113
1114 for (var, chain) in &embedding.chains {
1116 if let Ok(bias) = model.get_bias(*var) {
1117 if bias != 0.0 {
1118 let bias_per_qubit = bias / chain.len() as f64;
1120 for &qubit in chain {
1121 embedded_model
1122 .set_bias(qubit, bias_per_qubit)
1123 .map_err(|e| DWaveError::EmbeddingError(e.to_string()))?;
1124 }
1125 }
1126 }
1127 }
1128
1129 for coupling in model.couplings() {
1131 if let (Some(chain1), Some(chain2)) = (
1132 embedding.chains.get(&coupling.i),
1133 embedding.chains.get(&coupling.j),
1134 ) {
1135 for &q1 in chain1 {
1137 for &q2 in chain2 {
1138 embedded_model
1139 .set_coupling(q1, q2, coupling.strength)
1140 .map_err(|e| DWaveError::EmbeddingError(e.to_string()))?;
1141 }
1142 }
1143 }
1144 }
1145
1146 for chain in embedding.chains.values() {
1148 for window in chain.windows(2) {
1149 if let [q1, q2] = window {
1150 embedded_model
1151 .set_coupling(*q1, *q2, -chain_strength)
1152 .map_err(|e| DWaveError::EmbeddingError(e.to_string()))?;
1153 }
1154 }
1155 }
1156
1157 Ok(embedded_model)
1158 }
1159
1160 fn submit_embedded_problem(
1162 &self,
1163 embedded_model: &IsingModel,
1164 solver: &LeapSolverInfo,
1165 params: AdvancedProblemParams,
1166 ) -> DWaveResult<Solution> {
1167 let flux_bias_map = if let Some(fb) = params.flux_biases {
1169 let mut map = serde_json::Map::new();
1170 for (k, v) in fb {
1171 let value = serde_json::to_value(v).map_err(|e| {
1172 DWaveError::ProblemError(format!("Failed to serialize flux bias: {e}"))
1173 })?;
1174 map.insert(k, value);
1175 }
1176 Some(map)
1177 } else {
1178 None
1179 };
1180
1181 let legacy_params = ProblemParams {
1183 num_reads: params.num_reads,
1184 annealing_time: params
1185 .anneal_schedule
1186 .as_ref()
1187 .and_then(|schedule| schedule.schedule.last())
1188 .map(|(time, _)| *time as usize)
1189 .unwrap_or(20),
1190 programming_therm: params.programming_thermalization.unwrap_or(1000),
1191 readout_therm: params.readout_thermalization.unwrap_or(0),
1192 flux_biases: None, flux_bias_map,
1194 other: serde_json::Value::Object(serde_json::Map::new()),
1195 };
1196
1197 self.submit_ising(embedded_model, &solver.id, legacy_params)
1198 }
1199
1200 pub fn submit_hybrid(
1202 &self,
1203 model: &IsingModel,
1204 solver_id: Option<&str>,
1205 params: Option<HybridSolverParams>,
1206 ) -> DWaveResult<Solution> {
1207 let params = params.unwrap_or_default();
1208
1209 let solver = if let Some(id) = solver_id {
1211 self.get_leap_solvers()?
1212 .into_iter()
1213 .find(|s| s.id == id)
1214 .ok_or_else(|| {
1215 DWaveError::SolverConfigError(format!("Solver {id} not found"))
1216 })?
1217 } else {
1218 let hybrid_selector = SolverSelector {
1219 category: SolverCategory::Hybrid,
1220 ..Default::default()
1221 };
1222 self.select_solver(Some(&hybrid_selector))?
1223 };
1224
1225 let mut linear_terms = serde_json::Map::new();
1227 for (qubit, bias) in model.biases() {
1228 let value = serde_json::to_value(bias).map_err(|e| {
1229 DWaveError::ProblemError(format!("Failed to serialize bias: {e}"))
1230 })?;
1231 linear_terms.insert(qubit.to_string(), value);
1232 }
1233
1234 let mut quadratic_terms = serde_json::Map::new();
1235 for coupling in model.couplings() {
1236 let key = format!("{},{}", coupling.i, coupling.j);
1237 let value = serde_json::to_value(coupling.strength).map_err(|e| {
1238 DWaveError::ProblemError(format!("Failed to serialize coupling: {e}"))
1239 })?;
1240 quadratic_terms.insert(key, value);
1241 }
1242
1243 let mut hybrid_params = params.extra.clone();
1245 if let Some(time_limit) = params.time_limit {
1246 let value = serde_json::to_value(time_limit).map_err(|e| {
1247 DWaveError::ProblemError(format!("Failed to serialize time_limit: {e}"))
1248 })?;
1249 hybrid_params.insert("time_limit".to_string(), value);
1250 }
1251
1252 let problem = Problem {
1253 linear_terms: serde_json::Value::Object(linear_terms),
1254 quadratic_terms: serde_json::Value::Object(quadratic_terms),
1255 type_: "ising".to_string(),
1256 solver: solver.id,
1257 params: ProblemParams {
1258 num_reads: 1, annealing_time: 1,
1260 programming_therm: 0,
1261 readout_therm: 0,
1262 flux_biases: None,
1263 flux_bias_map: None,
1264 other: serde_json::Value::Object(
1265 hybrid_params.into_iter().map(|(k, v)| (k, v)).collect(),
1266 ),
1267 },
1268 };
1269
1270 self.submit_problem(&problem)
1271 }
1272
1273 pub fn get_problem_status(&self, problem_id: &str) -> DWaveResult<ProblemInfo> {
1275 let url = format!("{}/problems/{}", self.endpoint, problem_id);
1276
1277 self.runtime.block_on(async {
1278 let response = self
1279 .client
1280 .get(&url)
1281 .header("Authorization", format!("token {}", self.token))
1282 .send()
1283 .await?;
1284
1285 if !response.status().is_success() {
1286 let status = response.status();
1287 let error_text = response.text().await?;
1288 return Err(DWaveError::ApiError(format!(
1289 "Error getting problem status: {} - {}",
1290 status, error_text
1291 )));
1292 }
1293
1294 let problem_info: ProblemInfo = response.json().await?;
1295 Ok(problem_info)
1296 })
1297 }
1298
1299 pub fn cancel_problem(&self, problem_id: &str) -> DWaveResult<()> {
1301 let url = format!("{}/problems/{}/cancel", self.endpoint, problem_id);
1302
1303 self.runtime.block_on(async {
1304 let response = self
1305 .client
1306 .delete(&url)
1307 .header("Authorization", format!("token {}", self.token))
1308 .send()
1309 .await?;
1310
1311 if !response.status().is_success() {
1312 let status = response.status();
1313 let error_text = response.text().await?;
1314 return Err(DWaveError::ApiError(format!(
1315 "Error cancelling problem: {} - {}",
1316 status, error_text
1317 )));
1318 }
1319
1320 Ok(())
1321 })
1322 }
1323
1324 pub fn submit_batch(
1326 &self,
1327 problems: Vec<(&IsingModel, Option<&str>, Option<AdvancedProblemParams>)>,
1328 ) -> DWaveResult<BatchSubmissionResult> {
1329 let start_time = Instant::now();
1330 let mut problem_ids = Vec::new();
1331 let mut statuses = Vec::new();
1332
1333 for (model, solver_id, params) in problems {
1334 match self.submit_ising_with_embedding(model, solver_id, params, None) {
1335 Ok(solution) => {
1336 problem_ids.push(solution.problem_id.clone());
1337 statuses.push(Ok(solution.problem_id));
1338 }
1339 Err(e) => {
1340 problem_ids.push(String::new());
1341 statuses.push(Err(e));
1342 }
1343 }
1344 }
1345
1346 Ok(BatchSubmissionResult {
1347 problem_ids,
1348 statuses,
1349 submission_time: start_time.elapsed(),
1350 })
1351 }
1352
1353 pub fn get_problem_metrics(&self, problem_id: &str) -> DWaveResult<ProblemMetrics> {
1355 let solution = self.get_problem_result(problem_id)?;
1356 let timing = &solution.timing;
1357
1358 let queue_time =
1360 Duration::from_micros(timing["qpu_access_overhead_time"].as_u64().unwrap_or(0));
1361 let programming_time =
1362 Duration::from_micros(timing["qpu_programming_time"].as_u64().unwrap_or(0));
1363 let sampling_time =
1364 Duration::from_micros(timing["qpu_sampling_time"].as_u64().unwrap_or(0));
1365 let readout_time =
1366 Duration::from_micros(timing["qpu_readout_time"].as_u64().unwrap_or(0));
1367
1368 let total_time = queue_time + programming_time + sampling_time + readout_time;
1369 let access_time = programming_time + sampling_time + readout_time;
1370
1371 let best_energy = solution
1372 .energies
1373 .iter()
1374 .min_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
1375 .copied()
1376 .unwrap_or(f64::INFINITY);
1377
1378 Ok(ProblemMetrics {
1379 total_time,
1380 queue_time,
1381 access_time,
1382 programming_time,
1383 sampling_time,
1384 readout_time,
1385 best_energy,
1386 num_valid_solutions: solution.solutions.len(),
1387 chain_break_fraction: timing["chain_break_fraction"].as_f64(),
1388 })
1389 }
1390
1391 pub fn get_problem_result(&self, problem_id: &str) -> DWaveResult<Solution> {
1393 let start_time = Instant::now();
1394
1395 loop {
1396 let status = self.get_problem_status(problem_id)?;
1397
1398 match status.status {
1399 ProblemStatus::Completed => {
1400 return self.get_solution_data(problem_id);
1402 }
1403 ProblemStatus::Failed => {
1404 return Err(DWaveError::StatusError(format!(
1405 "Problem {} failed",
1406 problem_id
1407 )));
1408 }
1409 ProblemStatus::Cancelled => {
1410 return Err(DWaveError::StatusError(format!(
1411 "Problem {} was cancelled",
1412 problem_id
1413 )));
1414 }
1415 ProblemStatus::InProgress | ProblemStatus::Pending => {
1416 if start_time.elapsed() > self.problem_timeout {
1417 return Err(DWaveError::TimeoutError(format!(
1418 "Timeout waiting for problem {} completion",
1419 problem_id
1420 )));
1421 }
1422 std::thread::sleep(Duration::from_secs(2));
1424 }
1425 }
1426 }
1427 }
1428
1429 fn get_solution_data(&self, problem_id: &str) -> DWaveResult<Solution> {
1431 let url = format!("{}/problems/{}", self.endpoint, problem_id);
1432
1433 self.runtime.block_on(async {
1434 let response = self
1435 .client
1436 .get(&url)
1437 .header("Authorization", format!("token {}", self.token))
1438 .send()
1439 .await?;
1440
1441 if !response.status().is_success() {
1442 let status = response.status();
1443 let error_text = response.text().await?;
1444 return Err(DWaveError::ApiError(format!(
1445 "Error getting solution data: {} - {}",
1446 status, error_text
1447 )));
1448 }
1449
1450 let data: serde_json::Value = response.json().await?;
1451
1452 Ok(Solution {
1453 energies: serde_json::from_value(data["energies"].clone())?,
1454 occurrences: serde_json::from_value(data["occurrences"].clone())?,
1455 solutions: serde_json::from_value(data["solutions"].clone())?,
1456 num_samples: data["num_samples"].as_u64().unwrap_or(0) as usize,
1457 problem_id: problem_id.to_string(),
1458 solver: data["solver"].as_str().unwrap_or("unknown").to_string(),
1459 timing: data["timing"].clone(),
1460 })
1461 })
1462 }
1463
1464 pub fn list_problems(&self, limit: Option<usize>) -> DWaveResult<Vec<ProblemInfo>> {
1466 let mut url = format!("{}/problems", self.endpoint);
1467 if let Some(limit) = limit {
1468 let _ = write!(url, "?limit={}", limit);
1470 }
1471
1472 self.runtime.block_on(async {
1473 let response = self
1474 .client
1475 .get(&url)
1476 .header("Authorization", format!("token {}", self.token))
1477 .send()
1478 .await?;
1479
1480 if !response.status().is_success() {
1481 let status = response.status();
1482 let error_text = response.text().await?;
1483 return Err(DWaveError::ApiError(format!(
1484 "Error listing problems: {} - {}",
1485 status, error_text
1486 )));
1487 }
1488
1489 let problems: Vec<ProblemInfo> = response.json().await?;
1490 Ok(problems)
1491 })
1492 }
1493
1494 pub fn get_usage_info(&self) -> DWaveResult<serde_json::Value> {
1496 let url = format!("{}/usage", self.endpoint);
1497
1498 self.runtime.block_on(async {
1499 let response = self
1500 .client
1501 .get(&url)
1502 .header("Authorization", format!("token {}", self.token))
1503 .send()
1504 .await?;
1505
1506 if !response.status().is_success() {
1507 let status = response.status();
1508 let error_text = response.text().await?;
1509 return Err(DWaveError::ApiError(format!(
1510 "Error getting usage info: {} - {}",
1511 status, error_text
1512 )));
1513 }
1514
1515 let usage: serde_json::Value = response.json().await?;
1516 Ok(usage)
1517 })
1518 }
1519 }
1520}
1521
1522#[cfg(feature = "dwave")]
1523pub use client::*;
1524
1525#[cfg(not(feature = "dwave"))]
1526mod placeholder {
1527 use thiserror::Error;
1528
1529 #[derive(Error, Debug)]
1531 pub enum DWaveError {
1532 #[error("D-Wave feature not enabled. Recompile with '--features dwave'")]
1534 NotEnabled,
1535 }
1536
1537 pub type DWaveResult<T> = Result<T, DWaveError>;
1539
1540 #[derive(Debug, Clone)]
1542 pub struct DWaveClient {
1543 _private: (),
1544 }
1545
1546 impl DWaveClient {
1547 pub fn new(_token: impl Into<String>, _endpoint: Option<String>) -> DWaveResult<Self> {
1549 Err(DWaveError::NotEnabled)
1550 }
1551 }
1552
1553 #[derive(Debug, Clone)]
1555 pub struct ProblemParams {
1556 pub num_reads: usize,
1558 pub annealing_time: usize,
1560 pub programming_therm: usize,
1562 pub readout_therm: usize,
1564 }
1565
1566 #[derive(Debug, Clone)]
1568 pub enum SolverType {
1569 QuantumProcessor,
1570 Hybrid,
1571 Software,
1572 }
1573
1574 #[derive(Debug, Clone)]
1575 pub enum SolverCategory {
1576 QPU,
1577 Hybrid,
1578 Software,
1579 All,
1580 }
1581
1582 #[derive(Debug, Clone)]
1583 pub enum ProblemStatus {
1584 InProgress,
1585 Completed,
1586 Failed,
1587 Cancelled,
1588 Pending,
1589 }
1590
1591 #[derive(Debug, Clone)]
1592 pub struct SolverSelector;
1593
1594 #[derive(Debug, Clone)]
1595 pub struct EmbeddingConfig;
1596
1597 #[derive(Debug, Clone)]
1598 pub struct AdvancedProblemParams;
1599
1600 #[derive(Debug, Clone)]
1601 pub struct HybridSolverParams;
1602
1603 #[derive(Debug, Clone)]
1604 pub struct LeapSolverInfo;
1605
1606 #[derive(Debug, Clone)]
1607 pub struct ProblemInfo;
1608
1609 #[derive(Debug, Clone)]
1610 pub struct AnnealingSchedule;
1611
1612 #[derive(Debug, Clone)]
1613 pub struct ProblemMetrics;
1614
1615 #[derive(Debug, Clone)]
1616 pub struct BatchSubmissionResult;
1617
1618 #[derive(Debug, Clone)]
1619 pub enum ChainStrengthMethod {
1620 Auto,
1621 Fixed(f64),
1622 Adaptive(f64),
1623 }
1624
1625 impl Default for ProblemParams {
1626 fn default() -> Self {
1627 Self {
1628 num_reads: 1000,
1629 annealing_time: 20,
1630 programming_therm: 1000,
1631 readout_therm: 0,
1632 }
1633 }
1634 }
1635}
1636
1637#[cfg(not(feature = "dwave"))]
1638pub use placeholder::*;
1639
1640#[must_use]
1642pub const fn is_available() -> bool {
1643 cfg!(feature = "dwave")
1644}
1645
1646use std::fmt::Write;