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 self.submit_embedded_problem(&embedded_problem, solver, params)
834 }
835 fn get_solver_topology(&self, solver_id: &str) -> DWaveResult<HardwareGraph> {
837 let url = format!("{}/solvers/remote/{}", self.endpoint, solver_id);
838 let topology_info = self.runtime.block_on(async {
839 let response = self
840 .client
841 .get(&url)
842 .header("Authorization", format!("token {}", self.token))
843 .send()
844 .await?;
845 if !response.status().is_success() {
846 let status = response.status();
847 let error_text = response.text().await?;
848 return Err(DWaveError::ApiError(format!(
849 "Error getting solver topology: {status} - {error_text}"
850 )));
851 }
852 let solver_data: serde_json::Value = response.json().await?;
853 Ok(solver_data)
854 })?;
855 let properties = &topology_info["properties"];
856 if let Some(edges) = properties["couplers"].as_array() {
857 let mut hardware_edges = Vec::new();
858 for edge in edges {
859 if let (Some(i), Some(j)) = (edge[0].as_u64(), edge[1].as_u64()) {
860 hardware_edges.push((i as usize, j as usize));
861 }
862 }
863 let num_qubits = properties["qubits"]
864 .as_array()
865 .map(|arr| arr.len())
866 .unwrap_or(0);
867 Ok(HardwareGraph::new_custom(num_qubits, hardware_edges))
868 } else {
869 Err(DWaveError::SolverConfigError(
870 "Could not parse solver topology".to_string(),
871 ))
872 }
873 }
874 fn calculate_chain_strength(model: &IsingModel, method: &ChainStrengthMethod) -> f64 {
876 match method {
877 ChainStrengthMethod::Auto => {
878 let max_coupling = model
879 .couplings()
880 .iter()
881 .map(|c| c.strength.abs())
882 .fold(0.0, f64::max);
883 let max_bias = (0..model.num_qubits)
884 .filter_map(|i| model.get_bias(i).ok())
885 .fold(0.0_f64, |acc, bias| acc.max(bias.abs()));
886 2.0 * (max_coupling.max(max_bias))
887 }
888 ChainStrengthMethod::Fixed(value) => *value,
889 ChainStrengthMethod::Adaptive(multiplier) => {
890 let avg_coupling = model
891 .couplings()
892 .iter()
893 .map(|c| c.strength.abs())
894 .sum::<f64>()
895 / model.couplings().len().max(1) as f64;
896 multiplier * avg_coupling
897 }
898 }
899 }
900 fn embed_problem(
902 model: &IsingModel,
903 embedding: &Embedding,
904 chain_strength: f64,
905 ) -> DWaveResult<IsingModel> {
906 let mut embedded_model = IsingModel::new(0);
907 let max_qubit = embedding
908 .chains
909 .values()
910 .flat_map(|chain| chain.iter())
911 .max()
912 .copied()
913 .unwrap_or(0);
914 embedded_model = IsingModel::new(max_qubit + 1);
915 for (var, chain) in &embedding.chains {
916 if let Ok(bias) = model.get_bias(*var) {
917 if bias != 0.0 {
918 let bias_per_qubit = bias / chain.len() as f64;
919 for &qubit in chain {
920 embedded_model
921 .set_bias(qubit, bias_per_qubit)
922 .map_err(|e| DWaveError::EmbeddingError(e.to_string()))?;
923 }
924 }
925 }
926 }
927 for coupling in model.couplings() {
928 if let (Some(chain1), Some(chain2)) = (
929 embedding.chains.get(&coupling.i),
930 embedding.chains.get(&coupling.j),
931 ) {
932 for &q1 in chain1 {
933 for &q2 in chain2 {
934 embedded_model
935 .set_coupling(q1, q2, coupling.strength)
936 .map_err(|e| DWaveError::EmbeddingError(e.to_string()))?;
937 }
938 }
939 }
940 }
941 for chain in embedding.chains.values() {
942 for window in chain.windows(2) {
943 if let [q1, q2] = window {
944 embedded_model
945 .set_coupling(*q1, *q2, -chain_strength)
946 .map_err(|e| DWaveError::EmbeddingError(e.to_string()))?;
947 }
948 }
949 }
950 Ok(embedded_model)
951 }
952 fn submit_embedded_problem(
954 &self,
955 embedded_model: &IsingModel,
956 solver: &LeapSolverInfo,
957 params: AdvancedProblemParams,
958 ) -> DWaveResult<Solution> {
959 let flux_bias_map = if let Some(fb) = params.flux_biases {
960 let mut map = serde_json::Map::new();
961 for (k, v) in fb {
962 let value = serde_json::to_value(v).map_err(|e| {
963 DWaveError::ProblemError(format!("Failed to serialize flux bias: {e}"))
964 })?;
965 map.insert(k, value);
966 }
967 Some(map)
968 } else {
969 None
970 };
971 let legacy_params = ProblemParams {
972 num_reads: params.num_reads,
973 annealing_time: params
974 .anneal_schedule
975 .as_ref()
976 .and_then(|schedule| schedule.schedule.last())
977 .map(|(time, _)| *time as usize)
978 .unwrap_or(20),
979 programming_therm: params.programming_thermalization.unwrap_or(1000),
980 readout_therm: params.readout_thermalization.unwrap_or(0),
981 flux_biases: None,
982 flux_bias_map,
983 other: serde_json::Value::Object(serde_json::Map::new()),
984 };
985 self.submit_ising(embedded_model, &solver.id, legacy_params)
986 }
987 pub fn submit_hybrid(
989 &self,
990 model: &IsingModel,
991 solver_id: Option<&str>,
992 params: Option<HybridSolverParams>,
993 ) -> DWaveResult<Solution> {
994 let params = params.unwrap_or_default();
995 let solver = if let Some(id) = solver_id {
996 self.get_leap_solvers()?
997 .into_iter()
998 .find(|s| s.id == id)
999 .ok_or_else(|| {
1000 DWaveError::SolverConfigError(format!("Solver {id} not found"))
1001 })?
1002 } else {
1003 let hybrid_selector = SolverSelector {
1004 category: SolverCategory::Hybrid,
1005 ..Default::default()
1006 };
1007 self.select_solver(Some(&hybrid_selector))?
1008 };
1009 let mut linear_terms = serde_json::Map::new();
1010 for (qubit, bias) in model.biases() {
1011 let value = serde_json::to_value(bias).map_err(|e| {
1012 DWaveError::ProblemError(format!("Failed to serialize bias: {e}"))
1013 })?;
1014 linear_terms.insert(qubit.to_string(), value);
1015 }
1016 let mut quadratic_terms = serde_json::Map::new();
1017 for coupling in model.couplings() {
1018 let key = format!("{},{}", coupling.i, coupling.j);
1019 let value = serde_json::to_value(coupling.strength).map_err(|e| {
1020 DWaveError::ProblemError(format!("Failed to serialize coupling: {e}"))
1021 })?;
1022 quadratic_terms.insert(key, value);
1023 }
1024 let mut hybrid_params = params.extra.clone();
1025 if let Some(time_limit) = params.time_limit {
1026 let value = serde_json::to_value(time_limit).map_err(|e| {
1027 DWaveError::ProblemError(format!("Failed to serialize time_limit: {e}"))
1028 })?;
1029 hybrid_params.insert("time_limit".to_string(), value);
1030 }
1031 let problem = Problem {
1032 linear_terms: serde_json::Value::Object(linear_terms),
1033 quadratic_terms: serde_json::Value::Object(quadratic_terms),
1034 type_: "ising".to_string(),
1035 solver: solver.id,
1036 params: ProblemParams {
1037 num_reads: 1,
1038 annealing_time: 1,
1039 programming_therm: 0,
1040 readout_therm: 0,
1041 flux_biases: None,
1042 flux_bias_map: None,
1043 other: serde_json::Value::Object(
1044 hybrid_params.into_iter().map(|(k, v)| (k, v)).collect(),
1045 ),
1046 },
1047 };
1048 self.submit_problem(&problem)
1049 }
1050 pub fn get_problem_status(&self, problem_id: &str) -> DWaveResult<ProblemInfo> {
1052 let url = format!("{}/problems/{}", self.endpoint, problem_id);
1053 self.runtime.block_on(async {
1054 let response = self
1055 .client
1056 .get(&url)
1057 .header("Authorization", format!("token {}", self.token))
1058 .send()
1059 .await?;
1060 if !response.status().is_success() {
1061 let status = response.status();
1062 let error_text = response.text().await?;
1063 return Err(DWaveError::ApiError(format!(
1064 "Error getting problem status: {} - {}",
1065 status, error_text
1066 )));
1067 }
1068 let problem_info: ProblemInfo = response.json().await?;
1069 Ok(problem_info)
1070 })
1071 }
1072 pub fn cancel_problem(&self, problem_id: &str) -> DWaveResult<()> {
1074 let url = format!("{}/problems/{}/cancel", self.endpoint, problem_id);
1075 self.runtime.block_on(async {
1076 let response = self
1077 .client
1078 .delete(&url)
1079 .header("Authorization", format!("token {}", self.token))
1080 .send()
1081 .await?;
1082 if !response.status().is_success() {
1083 let status = response.status();
1084 let error_text = response.text().await?;
1085 return Err(DWaveError::ApiError(format!(
1086 "Error cancelling problem: {} - {}",
1087 status, error_text
1088 )));
1089 }
1090 Ok(())
1091 })
1092 }
1093 pub fn submit_batch(
1095 &self,
1096 problems: Vec<(&IsingModel, Option<&str>, Option<AdvancedProblemParams>)>,
1097 ) -> DWaveResult<BatchSubmissionResult> {
1098 let start_time = Instant::now();
1099 let mut problem_ids = Vec::new();
1100 let mut statuses = Vec::new();
1101 for (model, solver_id, params) in problems {
1102 match self.submit_ising_with_embedding(model, solver_id, params, None) {
1103 Ok(solution) => {
1104 problem_ids.push(solution.problem_id.clone());
1105 statuses.push(Ok(solution.problem_id));
1106 }
1107 Err(e) => {
1108 problem_ids.push(String::new());
1109 statuses.push(Err(e));
1110 }
1111 }
1112 }
1113 Ok(BatchSubmissionResult {
1114 problem_ids,
1115 statuses,
1116 submission_time: start_time.elapsed(),
1117 })
1118 }
1119 pub fn get_problem_metrics(&self, problem_id: &str) -> DWaveResult<ProblemMetrics> {
1121 let solution = self.get_problem_result(problem_id)?;
1122 let timing = &solution.timing;
1123 let queue_time =
1124 Duration::from_micros(timing["qpu_access_overhead_time"].as_u64().unwrap_or(0));
1125 let programming_time =
1126 Duration::from_micros(timing["qpu_programming_time"].as_u64().unwrap_or(0));
1127 let sampling_time =
1128 Duration::from_micros(timing["qpu_sampling_time"].as_u64().unwrap_or(0));
1129 let readout_time =
1130 Duration::from_micros(timing["qpu_readout_time"].as_u64().unwrap_or(0));
1131 let total_time = queue_time + programming_time + sampling_time + readout_time;
1132 let access_time = programming_time + sampling_time + readout_time;
1133 let best_energy = solution
1134 .energies
1135 .iter()
1136 .min_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
1137 .copied()
1138 .unwrap_or(f64::INFINITY);
1139 Ok(ProblemMetrics {
1140 total_time,
1141 queue_time,
1142 access_time,
1143 programming_time,
1144 sampling_time,
1145 readout_time,
1146 best_energy,
1147 num_valid_solutions: solution.solutions.len(),
1148 chain_break_fraction: timing["chain_break_fraction"].as_f64(),
1149 })
1150 }
1151 pub fn get_problem_result(&self, problem_id: &str) -> DWaveResult<Solution> {
1153 let start_time = Instant::now();
1154 loop {
1155 let status = self.get_problem_status(problem_id)?;
1156 match status.status {
1157 ProblemStatus::Completed => {
1158 return self.get_solution_data(problem_id);
1159 }
1160 ProblemStatus::Failed => {
1161 return Err(DWaveError::StatusError(format!(
1162 "Problem {} failed",
1163 problem_id
1164 )));
1165 }
1166 ProblemStatus::Cancelled => {
1167 return Err(DWaveError::StatusError(format!(
1168 "Problem {} was cancelled",
1169 problem_id
1170 )));
1171 }
1172 ProblemStatus::InProgress | ProblemStatus::Pending => {
1173 if start_time.elapsed() > self.problem_timeout {
1174 return Err(DWaveError::TimeoutError(format!(
1175 "Timeout waiting for problem {} completion",
1176 problem_id
1177 )));
1178 }
1179 std::thread::sleep(Duration::from_secs(2));
1180 }
1181 }
1182 }
1183 }
1184 fn get_solution_data(&self, problem_id: &str) -> DWaveResult<Solution> {
1186 let url = format!("{}/problems/{}", self.endpoint, problem_id);
1187 self.runtime.block_on(async {
1188 let response = self
1189 .client
1190 .get(&url)
1191 .header("Authorization", format!("token {}", self.token))
1192 .send()
1193 .await?;
1194 if !response.status().is_success() {
1195 let status = response.status();
1196 let error_text = response.text().await?;
1197 return Err(DWaveError::ApiError(format!(
1198 "Error getting solution data: {} - {}",
1199 status, error_text
1200 )));
1201 }
1202 let data: serde_json::Value = response.json().await?;
1203 Ok(Solution {
1204 energies: serde_json::from_value(data["energies"].clone())?,
1205 occurrences: serde_json::from_value(data["occurrences"].clone())?,
1206 solutions: serde_json::from_value(data["solutions"].clone())?,
1207 num_samples: data["num_samples"].as_u64().unwrap_or(0) as usize,
1208 problem_id: problem_id.to_string(),
1209 solver: data["solver"].as_str().unwrap_or("unknown").to_string(),
1210 timing: data["timing"].clone(),
1211 })
1212 })
1213 }
1214 pub fn list_problems(&self, limit: Option<usize>) -> DWaveResult<Vec<ProblemInfo>> {
1216 let mut url = format!("{}/problems", self.endpoint);
1217 if let Some(limit) = limit {
1218 let _ = write!(url, "?limit={}", limit);
1219 }
1220 self.runtime.block_on(async {
1221 let response = self
1222 .client
1223 .get(&url)
1224 .header("Authorization", format!("token {}", self.token))
1225 .send()
1226 .await?;
1227 if !response.status().is_success() {
1228 let status = response.status();
1229 let error_text = response.text().await?;
1230 return Err(DWaveError::ApiError(format!(
1231 "Error listing problems: {} - {}",
1232 status, error_text
1233 )));
1234 }
1235 let problems: Vec<ProblemInfo> = response.json().await?;
1236 Ok(problems)
1237 })
1238 }
1239 pub fn get_usage_info(&self) -> DWaveResult<serde_json::Value> {
1241 let url = format!("{}/usage", self.endpoint);
1242 self.runtime.block_on(async {
1243 let response = self
1244 .client
1245 .get(&url)
1246 .header("Authorization", format!("token {}", self.token))
1247 .send()
1248 .await?;
1249 if !response.status().is_success() {
1250 let status = response.status();
1251 let error_text = response.text().await?;
1252 return Err(DWaveError::ApiError(format!(
1253 "Error getting usage info: {} - {}",
1254 status, error_text
1255 )));
1256 }
1257 let usage: serde_json::Value = response.json().await?;
1258 Ok(usage)
1259 })
1260 }
1261 }
1262}
1263#[cfg(not(feature = "dwave"))]
1264mod placeholder {
1265 use thiserror::Error;
1266 #[derive(Error, Debug)]
1268 pub enum DWaveError {
1269 #[error("D-Wave feature not enabled. Recompile with '--features dwave'")]
1271 NotEnabled,
1272 }
1273 pub type DWaveResult<T> = Result<T, DWaveError>;
1275 #[derive(Debug, Clone)]
1277 pub struct DWaveClient {
1278 _private: (),
1279 }
1280 impl DWaveClient {
1281 pub fn new(_token: impl Into<String>, _endpoint: Option<String>) -> DWaveResult<Self> {
1283 Err(DWaveError::NotEnabled)
1284 }
1285 }
1286 #[derive(Debug, Clone)]
1288 pub struct ProblemParams {
1289 pub num_reads: usize,
1291 pub annealing_time: usize,
1293 pub programming_therm: usize,
1295 pub readout_therm: usize,
1297 }
1298 #[derive(Debug, Clone)]
1300 pub enum SolverType {
1301 QuantumProcessor,
1302 Hybrid,
1303 Software,
1304 }
1305 #[derive(Debug, Clone)]
1306 pub enum SolverCategory {
1307 QPU,
1308 Hybrid,
1309 Software,
1310 All,
1311 }
1312 #[derive(Debug, Clone)]
1313 pub enum ProblemStatus {
1314 InProgress,
1315 Completed,
1316 Failed,
1317 Cancelled,
1318 Pending,
1319 }
1320 #[derive(Debug, Clone)]
1321 pub struct SolverSelector;
1322 #[derive(Debug, Clone)]
1323 pub struct EmbeddingConfig;
1324 #[derive(Debug, Clone)]
1325 pub struct AdvancedProblemParams;
1326 #[derive(Debug, Clone)]
1327 pub struct HybridSolverParams;
1328 #[derive(Debug, Clone)]
1329 pub struct LeapSolverInfo;
1330 #[derive(Debug, Clone)]
1331 pub struct ProblemInfo;
1332 #[derive(Debug, Clone)]
1333 pub struct AnnealingSchedule;
1334 #[derive(Debug, Clone)]
1335 pub struct ProblemMetrics;
1336 #[derive(Debug, Clone)]
1337 pub struct BatchSubmissionResult;
1338 #[derive(Debug, Clone)]
1339 pub enum ChainStrengthMethod {
1340 Auto,
1341 Fixed(f64),
1342 Adaptive(f64),
1343 }
1344 impl Default for ProblemParams {
1345 fn default() -> Self {
1346 Self {
1347 num_reads: 1000,
1348 annealing_time: 20,
1349 programming_therm: 1000,
1350 readout_therm: 0,
1351 }
1352 }
1353 }
1354}
1355#[must_use]
1357pub const fn is_available() -> bool {
1358 cfg!(feature = "dwave")
1359}