Skip to main content

quantrs2_anneal/dwave/
functions.rs

1//! Auto-generated module
2//!
3//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5#[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    /// Errors that can occur when interacting with D-Wave API
23    #[derive(Error, Debug)]
24    pub enum DWaveError {
25        /// Error in the underlying Ising model
26        #[error("Ising error: {0}")]
27        IsingError(#[from] IsingError),
28        /// Error with the network request
29        #[error("Network error: {0}")]
30        NetworkError(#[from] reqwest::Error),
31        /// Error parsing the response
32        #[error("Response parsing error: {0}")]
33        ParseError(#[from] serde_json::Error),
34        /// Error with the D-Wave API response
35        #[error("D-Wave API error: {0}")]
36        ApiError(String),
37        /// Error with the authentication credentials
38        #[error("Authentication error: {0}")]
39        AuthError(String),
40        /// Error with the tokio runtime
41        #[error("Runtime error: {0}")]
42        RuntimeError(String),
43        /// Error with the problem formulation
44        #[error("Problem formulation error: {0}")]
45        ProblemError(String),
46        /// Error with embedding
47        #[error("Embedding error: {0}")]
48        EmbeddingError(String),
49        /// Error with hybrid solver
50        #[error("Hybrid solver error: {0}")]
51        HybridSolverError(String),
52        /// Error with problem status
53        #[error("Problem status error: {0}")]
54        StatusError(String),
55        /// Error with batch operations
56        #[error("Batch operation error: {0}")]
57        BatchError(String),
58        /// Error with solver configuration
59        #[error("Solver configuration error: {0}")]
60        SolverConfigError(String),
61        /// Timeout error
62        #[error("Operation timed out: {0}")]
63        TimeoutError(String),
64    }
65    /// Result type for D-Wave operations
66    pub type DWaveResult<T> = Result<T, DWaveError>;
67    /// D-Wave solver information
68    #[derive(Debug, Clone, Serialize, Deserialize)]
69    pub struct SolverInfo {
70        /// ID of the solver
71        pub id: String,
72        /// Name of the solver
73        pub name: String,
74        /// Description of the solver
75        pub description: String,
76        /// Number of qubits
77        pub num_qubits: usize,
78        /// Connectivity information
79        pub connectivity: SolverConnectivity,
80        /// Properties of the solver
81        pub properties: SolverProperties,
82    }
83    /// D-Wave solver connectivity
84    #[derive(Debug, Clone, Serialize, Deserialize)]
85    pub struct SolverConnectivity {
86        /// Type of connectivity (e.g., "chimera", "pegasus")
87        #[serde(rename = "type")]
88        pub type_: String,
89        /// Parameters for the connectivity
90        #[serde(flatten)]
91        pub params: serde_json::Value,
92    }
93    /// D-Wave solver properties
94    #[derive(Debug, Clone, Serialize, Deserialize)]
95    pub struct SolverProperties {
96        /// Supported parameters
97        pub parameters: serde_json::Value,
98        /// Additional properties
99        #[serde(flatten)]
100        pub other: serde_json::Value,
101    }
102    /// D-Wave problem submission parameters
103    #[derive(Debug, Clone, Serialize, Deserialize)]
104    pub struct ProblemParams {
105        /// Number of reads/samples to take
106        pub num_reads: usize,
107        /// Annealing time in microseconds
108        pub annealing_time: usize,
109        /// Programming thermalization in microseconds
110        #[serde(rename = "programming_thermalization")]
111        pub programming_therm: usize,
112        /// Read-out thermalization in microseconds
113        #[serde(rename = "readout_thermalization")]
114        pub readout_therm: usize,
115        /// Flux biases for each qubit (optional)
116        #[serde(rename = "flux_biases", skip_serializing_if = "Option::is_none")]
117        pub flux_biases: Option<Vec<f64>>,
118        /// Per-qubit flux bias values (optional, alternative format)
119        #[serde(rename = "flux_bias", skip_serializing_if = "Option::is_none")]
120        pub flux_bias_map: Option<serde_json::Map<String, serde_json::Value>>,
121        /// Additional parameters
122        #[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    /// D-Wave problem submission
139    #[derive(Debug, Clone, Serialize, Deserialize)]
140    pub struct Problem {
141        /// The linear terms (h_i values for Ising, Q_ii for QUBO)
142        #[serde(rename = "linear")]
143        pub linear_terms: serde_json::Value,
144        /// The quadratic terms (J_ij values for Ising, Q_ij for QUBO)
145        #[serde(rename = "quadratic")]
146        pub quadratic_terms: serde_json::Value,
147        /// The type of problem (ising or qubo)
148        #[serde(rename = "type")]
149        pub type_: String,
150        /// The solver to use
151        pub solver: String,
152        /// The parameters for the problem
153        pub params: ProblemParams,
154    }
155    /// D-Wave problem solution
156    #[derive(Debug, Clone, Serialize, Deserialize)]
157    pub struct Solution {
158        /// The energy of each sample
159        pub energies: Vec<f64>,
160        /// The occurrences of each sample
161        pub occurrences: Vec<usize>,
162        /// The solutions (spin values for Ising, binary values for QUBO)
163        pub solutions: Vec<Vec<i8>>,
164        /// The number of samples
165        pub num_samples: usize,
166        /// The problem ID
167        pub problem_id: String,
168        /// The solver used
169        pub solver: String,
170        /// The timing information
171        pub timing: serde_json::Value,
172    }
173    /// Enhanced solver types for Leap
174    #[derive(Debug, Clone, Serialize, Deserialize)]
175    pub enum SolverType {
176        /// Quantum annealing processor
177        #[serde(rename = "qpu")]
178        QuantumProcessor,
179        /// Hybrid classical-quantum solver
180        #[serde(rename = "hybrid")]
181        Hybrid,
182        /// Discrete Quadratic Model solver
183        #[serde(rename = "dqm")]
184        DiscreteQuadraticModel,
185        /// Constrained Quadratic Model solver
186        #[serde(rename = "cqm")]
187        ConstrainedQuadraticModel,
188        /// Software solver/simulator
189        #[serde(rename = "software")]
190        Software,
191    }
192    /// Solver category for filtering
193    #[derive(Debug, Clone, PartialEq, Eq)]
194    pub enum SolverCategory {
195        /// Quantum processing units
196        QPU,
197        /// Hybrid solvers
198        Hybrid,
199        /// Software-based solvers
200        Software,
201        /// All solver types
202        All,
203    }
204    /// Problem status tracking
205    #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
206    pub enum ProblemStatus {
207        /// Problem is being processed
208        #[serde(rename = "IN_PROGRESS")]
209        InProgress,
210        /// Problem completed successfully
211        #[serde(rename = "COMPLETED")]
212        Completed,
213        /// Problem failed
214        #[serde(rename = "FAILED")]
215        Failed,
216        /// Problem was cancelled
217        #[serde(rename = "CANCELLED")]
218        Cancelled,
219        /// Problem is pending
220        #[serde(rename = "PENDING")]
221        Pending,
222    }
223    /// Problem metadata and tracking
224    #[derive(Debug, Clone, Serialize, Deserialize)]
225    pub struct ProblemInfo {
226        /// Problem ID
227        pub id: String,
228        /// Problem status
229        pub status: ProblemStatus,
230        /// Submission time
231        pub submitted_on: String,
232        /// Solver used
233        pub solver: String,
234        /// Problem type
235        #[serde(rename = "type")]
236        pub problem_type: String,
237        /// Parameters used
238        pub params: serde_json::Value,
239        /// Additional metadata
240        #[serde(flatten)]
241        pub metadata: serde_json::Value,
242    }
243    /// Enhanced solver information with Leap features
244    #[derive(Debug, Clone, Serialize, Deserialize)]
245    pub struct LeapSolverInfo {
246        /// Solver ID
247        pub id: String,
248        /// Solver name
249        pub name: String,
250        /// Solver description
251        pub description: String,
252        /// Solver type
253        #[serde(rename = "category")]
254        pub solver_type: SolverType,
255        /// Status (online/offline)
256        pub status: String,
257        /// Solver properties
258        pub properties: serde_json::Value,
259        /// Supported problem types
260        pub problem_types: Vec<String>,
261        /// Average queue time
262        pub avg_load: Option<f64>,
263        /// Whether solver is available
264        pub available: bool,
265    }
266    /// Custom annealing schedule
267    #[derive(Debug, Clone, Serialize, Deserialize)]
268    pub struct AnnealingSchedule {
269        /// Time points in microseconds
270        pub schedule: Vec<(f64, f64)>,
271    }
272    impl AnnealingSchedule {
273        /// Create a linear annealing schedule
274        pub fn linear(annealing_time: f64) -> Self {
275            Self {
276                schedule: vec![(0.0, 1.0), (annealing_time, 0.0)],
277            }
278        }
279        /// Create a pause-and-ramp schedule
280        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        /// Create a custom schedule from points
294        pub fn custom(points: Vec<(f64, f64)>) -> Self {
295            Self { schedule: points }
296        }
297    }
298    /// Advanced problem parameters for Leap
299    #[derive(Debug, Clone, Serialize, Deserialize)]
300    pub struct AdvancedProblemParams {
301        /// Number of reads/samples
302        pub num_reads: usize,
303        /// Custom annealing schedule
304        #[serde(skip_serializing_if = "Option::is_none")]
305        pub anneal_schedule: Option<AnnealingSchedule>,
306        /// Programming thermalization
307        #[serde(skip_serializing_if = "Option::is_none")]
308        pub programming_thermalization: Option<usize>,
309        /// Readout thermalization
310        #[serde(skip_serializing_if = "Option::is_none")]
311        pub readout_thermalization: Option<usize>,
312        /// Auto-scale flag
313        #[serde(skip_serializing_if = "Option::is_none")]
314        pub auto_scale: Option<bool>,
315        /// Chain strength
316        #[serde(skip_serializing_if = "Option::is_none")]
317        pub chain_strength: Option<f64>,
318        /// Flux biases
319        #[serde(skip_serializing_if = "Option::is_none")]
320        pub flux_biases: Option<HashMap<String, f64>>,
321        /// Additional parameters
322        #[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    /// Hybrid solver parameters
340    #[derive(Debug, Clone, Serialize, Deserialize)]
341    pub struct HybridSolverParams {
342        /// Time limit in seconds
343        #[serde(skip_serializing_if = "Option::is_none")]
344        pub time_limit: Option<f64>,
345        /// Maximum number of variables
346        #[serde(skip_serializing_if = "Option::is_none")]
347        pub max_variables: Option<usize>,
348        /// Additional solver-specific parameters
349        #[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    /// Performance metrics for problems
362    #[derive(Debug, Clone)]
363    pub struct ProblemMetrics {
364        /// Total execution time
365        pub total_time: Duration,
366        /// Queue time
367        pub queue_time: Duration,
368        /// Solver access time
369        pub access_time: Duration,
370        /// Programming time
371        pub programming_time: Duration,
372        /// Sampling time
373        pub sampling_time: Duration,
374        /// Readout time
375        pub readout_time: Duration,
376        /// Best energy found
377        pub best_energy: f64,
378        /// Number of valid solutions
379        pub num_valid_solutions: usize,
380        /// Chain break fraction
381        pub chain_break_fraction: Option<f64>,
382    }
383    /// Batch problem submission result
384    #[derive(Debug)]
385    pub struct BatchSubmissionResult {
386        /// List of submitted problem IDs
387        pub problem_ids: Vec<String>,
388        /// Success/failure status for each problem
389        pub statuses: Vec<Result<String, DWaveError>>,
390        /// Total submission time
391        pub submission_time: Duration,
392    }
393    /// Solver selection criteria
394    #[derive(Debug, Clone)]
395    pub struct SolverSelector {
396        /// Solver category filter
397        pub category: SolverCategory,
398        /// Minimum number of qubits
399        pub min_qubits: Option<usize>,
400        /// Maximum queue time preference
401        pub max_queue_time: Option<f64>,
402        /// Prefer online solvers
403        pub online_only: bool,
404        /// Specific solver name pattern
405        pub name_pattern: Option<String>,
406        /// Topology preference
407        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    /// Problem embedding configuration
422    #[derive(Debug, Clone)]
423    pub struct EmbeddingConfig {
424        /// Use automatic embedding
425        pub auto_embed: bool,
426        /// Embedding timeout
427        pub timeout: Duration,
428        /// Chain strength calculation method
429        pub chain_strength_method: ChainStrengthMethod,
430        /// Custom embedding (if not auto-embedding)
431        pub custom_embedding: Option<Embedding>,
432        /// Embedding optimization level
433        pub optimization_level: usize,
434    }
435    /// Chain strength calculation methods
436    #[derive(Debug, Clone)]
437    pub enum ChainStrengthMethod {
438        /// Automatic calculation
439        Auto,
440        /// Fixed value
441        Fixed(f64),
442        /// Based on problem coupling strengths
443        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    /// Enhanced D-Wave Leap cloud service client
457    #[derive(Debug)]
458    pub struct DWaveClient {
459        /// The HTTP client for making API requests
460        client: Client,
461        /// The API endpoint
462        endpoint: String,
463        /// The API token
464        token: String,
465        /// The tokio runtime for async requests
466        runtime: Runtime,
467        /// Default solver selector
468        default_solver_selector: SolverSelector,
469        /// Default embedding configuration
470        default_embedding_config: EmbeddingConfig,
471        /// Retry configuration
472        max_retries: usize,
473        /// Request timeout
474        request_timeout: Duration,
475        /// Default problem timeout
476        problem_timeout: Duration,
477    }
478    impl DWaveClient {
479        /// Create a new enhanced D-Wave Leap client
480        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        /// Create a D-Wave client with custom configuration
489        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        /// Get a list of available solvers
515        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        /// Submit an Ising model to D-Wave
536        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        /// Submit a QUBO model to D-Wave
567        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        /// Submit an Ising model with flux bias optimization
598        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        /// Submit a problem to D-Wave
617        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        /// Get enhanced Leap solver information
683        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        /// Select optimal solver based on criteria
704        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        /// Submit problem with automatic embedding
754        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        /// Submit with automatic embedding for QPU solvers
806        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        /// Un-embed a physical-qubit [`Solution`] back into logical-variable
838        /// space.
839        ///
840        /// D-Wave's API returns samples indexed by physical qubit; each
841        /// logical variable's chain of physical qubits must agree in order to
842        /// represent that variable's true value. For every sample and every
843        /// logical variable, this takes the majority vote across the
844        /// variable's chain (ties broken by the sign of the variable's own
845        /// bias, favoring whichever spin value contributes the lower local
846        /// energy `bias * value`). A chain is counted as "broken" whenever it
847        /// contains both `+1` and `-1` physical qubits (i.e. did not fully
848        /// agree), and the fraction of broken chains across all samples and
849        /// variables is computed here directly from the decoded data (not
850        /// merely copied from whatever the remote API happens to report) and
851        /// recorded under `timing.decoded_chain_break_fraction`. The returned
852        /// [`Solution`]'s `solutions`/`energies` are re-indexed to
853        /// `model.num_qubits` logical variables, with energies recomputed
854        /// from the real [`IsingModel::energy`] of each decoded sample.
855        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                        // No chain for this logical variable in the embedding;
871                        // leave the default (+1) rather than fabricating a
872                        // majority from data that doesn't exist.
873                        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        /// Get solver topology information
935        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        /// Calculate appropriate chain strength
974        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        /// Embed logical problem onto physical hardware
1000        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        /// Submit an embedded problem
1052        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        /// Submit hybrid solver problem
1087        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        /// Get problem status
1150        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        /// Cancel a running problem
1172        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        /// Submit multiple problems in batch
1193        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        /// Get performance metrics for a completed problem
1219        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        /// Get problem result (blocking until completion)
1251        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        /// Get solution data for a completed problem
1284        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        /// List recent problems
1314        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        /// Get account usage information
1339        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            // Two logical variables: var0 has a unanimous 3-qubit chain,
1369            // var1 has a 2-qubit chain that is deliberately split (a real
1370            // chain break), which must be resolved via the bias tie-break
1371            // rather than silently passed through in physical-qubit space.
1372            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], // Deliberately wrong physical-space energy;
1389                // the decode step must recompute the real logical energy
1390                // rather than passing this through untouched.
1391                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            // var0's chain is unanimous +1; var1's chain is tied 1-vs-1 and
1410            // must be resolved via the bias tie-break (bias=-0.5 < 0 => +1).
1411            assert_eq!(decoded.solutions[0], vec![1, 1]);
1412
1413            // The energy must be the real logical-space energy
1414            // (0.3*1 + -0.5*1 + -0.2*1*1 = -0.4), not the fabricated -999.0
1415            // physical-space placeholder that was passed in.
1416            assert!((decoded.energies[0] - (-0.4)).abs() < 1e-9);
1417
1418            // One of the two chains (var1) was genuinely split -> 50% break rate,
1419            // computed locally rather than copied from the API's own timing.
1420            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    /// Error type for when D-Wave feature is not enabled
1463    #[derive(Error, Debug)]
1464    pub enum DWaveError {
1465        /// Error when trying to use D-Wave without the feature enabled
1466        #[error("D-Wave feature not enabled. Recompile with '--features dwave'")]
1467        NotEnabled,
1468    }
1469    /// Result type for D-Wave operations
1470    pub type DWaveResult<T> = Result<T, DWaveError>;
1471    /// Placeholder for D-Wave client
1472    #[derive(Debug, Clone)]
1473    pub struct DWaveClient {
1474        _private: (),
1475    }
1476    impl DWaveClient {
1477        /// Placeholder for D-Wave client creation
1478        pub fn new(_token: impl Into<String>, _endpoint: Option<String>) -> DWaveResult<Self> {
1479            Err(DWaveError::NotEnabled)
1480        }
1481    }
1482    /// Placeholder for D-Wave problem submission parameters
1483    #[derive(Debug, Clone)]
1484    pub struct ProblemParams {
1485        /// Number of reads/samples to take
1486        pub num_reads: usize,
1487        /// Annealing time in microseconds
1488        pub annealing_time: usize,
1489        /// Programming thermalization in microseconds
1490        pub programming_therm: usize,
1491        /// Read-out thermalization in microseconds
1492        pub readout_therm: usize,
1493    }
1494    /// Placeholder types for enhanced Leap functionality (feature disabled)
1495    #[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/// Check if D-Wave API support is enabled
1552#[must_use]
1553pub const fn is_available() -> bool {
1554    cfg!(feature = "dwave")
1555}