#[cfg(feature = "dwave")]
pub use client::*;
#[cfg(not(feature = "dwave"))]
pub use placeholder::*;
use std::fmt::Write;
#[cfg(feature = "dwave")]
mod client {
use crate::embedding::{Embedding, HardwareGraph, MinorMiner};
use crate::ising::{IsingError, IsingModel, QuboModel};
use reqwest::Client;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fmt::Write;
use std::time::{Duration, Instant};
use thiserror::Error;
use tokio::runtime::Runtime;
#[derive(Error, Debug)]
pub enum DWaveError {
#[error("Ising error: {0}")]
IsingError(#[from] IsingError),
#[error("Network error: {0}")]
NetworkError(#[from] reqwest::Error),
#[error("Response parsing error: {0}")]
ParseError(#[from] serde_json::Error),
#[error("D-Wave API error: {0}")]
ApiError(String),
#[error("Authentication error: {0}")]
AuthError(String),
#[error("Runtime error: {0}")]
RuntimeError(String),
#[error("Problem formulation error: {0}")]
ProblemError(String),
#[error("Embedding error: {0}")]
EmbeddingError(String),
#[error("Hybrid solver error: {0}")]
HybridSolverError(String),
#[error("Problem status error: {0}")]
StatusError(String),
#[error("Batch operation error: {0}")]
BatchError(String),
#[error("Solver configuration error: {0}")]
SolverConfigError(String),
#[error("Operation timed out: {0}")]
TimeoutError(String),
}
pub type DWaveResult<T> = Result<T, DWaveError>;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SolverInfo {
pub id: String,
pub name: String,
pub description: String,
pub num_qubits: usize,
pub connectivity: SolverConnectivity,
pub properties: SolverProperties,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SolverConnectivity {
#[serde(rename = "type")]
pub type_: String,
#[serde(flatten)]
pub params: serde_json::Value,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SolverProperties {
pub parameters: serde_json::Value,
#[serde(flatten)]
pub other: serde_json::Value,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProblemParams {
pub num_reads: usize,
pub annealing_time: usize,
#[serde(rename = "programming_thermalization")]
pub programming_therm: usize,
#[serde(rename = "readout_thermalization")]
pub readout_therm: usize,
#[serde(rename = "flux_biases", skip_serializing_if = "Option::is_none")]
pub flux_biases: Option<Vec<f64>>,
#[serde(rename = "flux_bias", skip_serializing_if = "Option::is_none")]
pub flux_bias_map: Option<serde_json::Map<String, serde_json::Value>>,
#[serde(flatten)]
pub other: serde_json::Value,
}
impl Default for ProblemParams {
fn default() -> Self {
Self {
num_reads: 1000,
annealing_time: 20,
programming_therm: 1000,
readout_therm: 0,
flux_biases: None,
flux_bias_map: None,
other: serde_json::Value::Object(serde_json::Map::new()),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Problem {
#[serde(rename = "linear")]
pub linear_terms: serde_json::Value,
#[serde(rename = "quadratic")]
pub quadratic_terms: serde_json::Value,
#[serde(rename = "type")]
pub type_: String,
pub solver: String,
pub params: ProblemParams,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Solution {
pub energies: Vec<f64>,
pub occurrences: Vec<usize>,
pub solutions: Vec<Vec<i8>>,
pub num_samples: usize,
pub problem_id: String,
pub solver: String,
pub timing: serde_json::Value,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum SolverType {
#[serde(rename = "qpu")]
QuantumProcessor,
#[serde(rename = "hybrid")]
Hybrid,
#[serde(rename = "dqm")]
DiscreteQuadraticModel,
#[serde(rename = "cqm")]
ConstrainedQuadraticModel,
#[serde(rename = "software")]
Software,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SolverCategory {
QPU,
Hybrid,
Software,
All,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum ProblemStatus {
#[serde(rename = "IN_PROGRESS")]
InProgress,
#[serde(rename = "COMPLETED")]
Completed,
#[serde(rename = "FAILED")]
Failed,
#[serde(rename = "CANCELLED")]
Cancelled,
#[serde(rename = "PENDING")]
Pending,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProblemInfo {
pub id: String,
pub status: ProblemStatus,
pub submitted_on: String,
pub solver: String,
#[serde(rename = "type")]
pub problem_type: String,
pub params: serde_json::Value,
#[serde(flatten)]
pub metadata: serde_json::Value,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LeapSolverInfo {
pub id: String,
pub name: String,
pub description: String,
#[serde(rename = "category")]
pub solver_type: SolverType,
pub status: String,
pub properties: serde_json::Value,
pub problem_types: Vec<String>,
pub avg_load: Option<f64>,
pub available: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AnnealingSchedule {
pub schedule: Vec<(f64, f64)>,
}
impl AnnealingSchedule {
pub fn linear(annealing_time: f64) -> Self {
Self {
schedule: vec![(0.0, 1.0), (annealing_time, 0.0)],
}
}
pub fn pause_and_ramp(annealing_time: f64, pause_start: f64, pause_duration: f64) -> Self {
Self {
schedule: vec![
(0.0, 1.0),
(pause_start, 1.0 - pause_start / annealing_time),
(
pause_start + pause_duration,
1.0 - pause_start / annealing_time,
),
(annealing_time, 0.0),
],
}
}
pub fn custom(points: Vec<(f64, f64)>) -> Self {
Self { schedule: points }
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AdvancedProblemParams {
pub num_reads: usize,
#[serde(skip_serializing_if = "Option::is_none")]
pub anneal_schedule: Option<AnnealingSchedule>,
#[serde(skip_serializing_if = "Option::is_none")]
pub programming_thermalization: Option<usize>,
#[serde(skip_serializing_if = "Option::is_none")]
pub readout_thermalization: Option<usize>,
#[serde(skip_serializing_if = "Option::is_none")]
pub auto_scale: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub chain_strength: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub flux_biases: Option<HashMap<String, f64>>,
#[serde(flatten)]
pub extra: HashMap<String, serde_json::Value>,
}
impl Default for AdvancedProblemParams {
fn default() -> Self {
Self {
num_reads: 1000,
anneal_schedule: None,
programming_thermalization: Some(1000),
readout_thermalization: Some(0),
auto_scale: Some(true),
chain_strength: None,
flux_biases: None,
extra: HashMap::new(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HybridSolverParams {
#[serde(skip_serializing_if = "Option::is_none")]
pub time_limit: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_variables: Option<usize>,
#[serde(flatten)]
pub extra: HashMap<String, serde_json::Value>,
}
impl Default for HybridSolverParams {
fn default() -> Self {
Self {
time_limit: Some(5.0),
max_variables: None,
extra: HashMap::new(),
}
}
}
#[derive(Debug, Clone)]
pub struct ProblemMetrics {
pub total_time: Duration,
pub queue_time: Duration,
pub access_time: Duration,
pub programming_time: Duration,
pub sampling_time: Duration,
pub readout_time: Duration,
pub best_energy: f64,
pub num_valid_solutions: usize,
pub chain_break_fraction: Option<f64>,
}
#[derive(Debug)]
pub struct BatchSubmissionResult {
pub problem_ids: Vec<String>,
pub statuses: Vec<Result<String, DWaveError>>,
pub submission_time: Duration,
}
#[derive(Debug, Clone)]
pub struct SolverSelector {
pub category: SolverCategory,
pub min_qubits: Option<usize>,
pub max_queue_time: Option<f64>,
pub online_only: bool,
pub name_pattern: Option<String>,
pub topology_preference: Option<String>,
}
impl Default for SolverSelector {
fn default() -> Self {
Self {
category: SolverCategory::All,
min_qubits: None,
max_queue_time: None,
online_only: true,
name_pattern: None,
topology_preference: None,
}
}
}
#[derive(Debug, Clone)]
pub struct EmbeddingConfig {
pub auto_embed: bool,
pub timeout: Duration,
pub chain_strength_method: ChainStrengthMethod,
pub custom_embedding: Option<Embedding>,
pub optimization_level: usize,
}
#[derive(Debug, Clone)]
pub enum ChainStrengthMethod {
Auto,
Fixed(f64),
Adaptive(f64),
}
impl Default for EmbeddingConfig {
fn default() -> Self {
Self {
auto_embed: true,
timeout: Duration::from_secs(30),
chain_strength_method: ChainStrengthMethod::Auto,
custom_embedding: None,
optimization_level: 1,
}
}
}
#[derive(Debug)]
pub struct DWaveClient {
client: Client,
endpoint: String,
token: String,
runtime: Runtime,
default_solver_selector: SolverSelector,
default_embedding_config: EmbeddingConfig,
max_retries: usize,
request_timeout: Duration,
problem_timeout: Duration,
}
impl DWaveClient {
pub fn new(token: impl Into<String>, endpoint: Option<String>) -> DWaveResult<Self> {
Self::with_config(
token,
endpoint,
SolverSelector::default(),
EmbeddingConfig::default(),
)
}
pub fn with_config(
token: impl Into<String>,
endpoint: Option<String>,
solver_selector: SolverSelector,
embedding_config: EmbeddingConfig,
) -> DWaveResult<Self> {
let client = Client::builder()
.timeout(Duration::from_secs(300))
.build()
.map_err(DWaveError::NetworkError)?;
let runtime = Runtime::new().map_err(|e| DWaveError::RuntimeError(e.to_string()))?;
let endpoint =
endpoint.unwrap_or_else(|| "https://cloud.dwavesys.com/sapi/v2".to_string());
Ok(Self {
client,
endpoint,
token: token.into(),
runtime,
default_solver_selector: solver_selector,
default_embedding_config: embedding_config,
max_retries: 3,
request_timeout: Duration::from_secs(300),
problem_timeout: Duration::from_secs(1800),
})
}
pub fn get_solvers(&self) -> DWaveResult<Vec<SolverInfo>> {
let url = format!("{}/solvers/remote", self.endpoint);
self.runtime.block_on(async {
let response = self
.client
.get(&url)
.header("Authorization", format!("token {}", self.token))
.send()
.await?;
if !response.status().is_success() {
let status = response.status();
let error_text = response.text().await?;
return Err(DWaveError::ApiError(format!(
"Error getting solvers: {status} - {error_text}"
)));
}
let solvers: Vec<SolverInfo> = response.json().await?;
Ok(solvers)
})
}
pub fn submit_ising(
&self,
model: &IsingModel,
solver_id: &str,
params: ProblemParams,
) -> DWaveResult<Solution> {
let mut linear_terms = serde_json::Map::new();
for (qubit, bias) in model.biases() {
let value = serde_json::to_value(bias).map_err(|e| {
DWaveError::ProblemError(format!("Failed to serialize bias: {e}"))
})?;
linear_terms.insert(qubit.to_string(), value);
}
let mut quadratic_terms = serde_json::Map::new();
for coupling in model.couplings() {
let key = format!("{},{}", coupling.i, coupling.j);
let value = serde_json::to_value(coupling.strength).map_err(|e| {
DWaveError::ProblemError(format!("Failed to serialize coupling: {e}"))
})?;
quadratic_terms.insert(key, value);
}
let problem = Problem {
linear_terms: serde_json::Value::Object(linear_terms),
quadratic_terms: serde_json::Value::Object(quadratic_terms),
type_: "ising".to_string(),
solver: solver_id.to_string(),
params,
};
self.submit_problem(&problem)
}
pub fn submit_qubo(
&self,
model: &QuboModel,
solver_id: &str,
params: ProblemParams,
) -> DWaveResult<Solution> {
let mut linear_terms = serde_json::Map::new();
for (var, value) in model.linear_terms() {
let json_value = serde_json::to_value(value).map_err(|e| {
DWaveError::ProblemError(format!("Failed to serialize linear term: {e}"))
})?;
linear_terms.insert(var.to_string(), json_value);
}
let mut quadratic_terms = serde_json::Map::new();
for (var1, var2, value) in model.quadratic_terms() {
let key = format!("{var1},{var2}");
let json_value = serde_json::to_value(value).map_err(|e| {
DWaveError::ProblemError(format!("Failed to serialize quadratic term: {e}"))
})?;
quadratic_terms.insert(key, json_value);
}
let problem = Problem {
linear_terms: serde_json::Value::Object(linear_terms),
quadratic_terms: serde_json::Value::Object(quadratic_terms),
type_: "qubo".to_string(),
solver: solver_id.to_string(),
params,
};
self.submit_problem(&problem)
}
pub fn submit_ising_with_flux_bias(
&self,
model: &IsingModel,
solver_id: &str,
params: ProblemParams,
flux_biases: &std::collections::HashMap<usize, f64>,
) -> DWaveResult<Solution> {
let mut params_with_flux = params;
let mut flux_map = serde_json::Map::new();
for (qubit, &flux_bias) in flux_biases {
let value = serde_json::to_value(flux_bias).map_err(|e| {
DWaveError::ProblemError(format!("Failed to serialize flux bias: {e}"))
})?;
flux_map.insert(qubit.to_string(), value);
}
params_with_flux.flux_bias_map = Some(flux_map);
self.submit_ising(model, solver_id, params_with_flux)
}
fn submit_problem(&self, problem: &Problem) -> DWaveResult<Solution> {
let url = format!("{}/problems", self.endpoint);
self.runtime.block_on(async {
let response = self
.client
.post(&url)
.header("Authorization", format!("token {}", self.token))
.header("Content-Type", "application/json")
.json(problem)
.send()
.await?;
if !response.status().is_success() {
let status = response.status();
let error_text = response.text().await?;
return Err(DWaveError::ApiError(format!(
"Error submitting problem: {status} - {error_text}"
)));
}
let submit_response: serde_json::Value = response.json().await?;
let problem_id = submit_response["id"].as_str().ok_or_else(|| {
let error_msg = String::from("Failed to extract problem ID from response");
DWaveError::ApiError(error_msg)
})?;
let result_url = format!("{}/problems/{}", self.endpoint, problem_id);
let mut attempts = 0;
const MAX_ATTEMPTS: usize = 60;
while attempts < MAX_ATTEMPTS {
let status_response = self
.client
.get(&result_url)
.header("Authorization", format!("token {}", self.token))
.send()
.await?;
if !status_response.status().is_success() {
let status = status_response.status();
let error_text = status_response.text().await?;
return Err(DWaveError::ApiError(format!(
"Error getting problem status: {status} - {error_text}"
)));
}
let status: serde_json::Value = status_response.json().await?;
if let Some(state) = status["state"].as_str() {
if state == "COMPLETED" {
return Ok(Solution {
energies: serde_json::from_value(status["energies"].clone())?,
occurrences: serde_json::from_value(status["occurrences"].clone())?,
solutions: serde_json::from_value(status["solutions"].clone())?,
num_samples: status["num_samples"].as_u64().unwrap_or(0) as usize,
problem_id: problem_id.to_string(),
solver: problem.solver.clone(),
timing: status["timing"].clone(),
});
} else if state == "FAILED" {
let error = status["error"].as_str().unwrap_or("Unknown error");
return Err(DWaveError::ApiError(format!("Problem failed: {error}")));
}
}
tokio::time::sleep(Duration::from_secs(5)).await;
attempts += 1;
}
Err(DWaveError::ApiError(
"Timeout waiting for problem solution".into(),
))
})
}
pub fn get_leap_solvers(&self) -> DWaveResult<Vec<LeapSolverInfo>> {
let url = format!("{}/solvers/remote", self.endpoint);
self.runtime.block_on(async {
let response = self
.client
.get(&url)
.header("Authorization", format!("token {}", self.token))
.send()
.await?;
if !response.status().is_success() {
let status = response.status();
let error_text = response.text().await?;
return Err(DWaveError::ApiError(format!(
"Error getting Leap solvers: {status} - {error_text}"
)));
}
let solvers: Vec<LeapSolverInfo> = response.json().await?;
Ok(solvers)
})
}
pub fn select_solver(
&self,
selector: Option<&SolverSelector>,
) -> DWaveResult<LeapSolverInfo> {
let selector = selector.unwrap_or(&self.default_solver_selector);
let solvers = self.get_leap_solvers()?;
let filtered_solvers: Vec<_> = solvers
.into_iter()
.filter(|solver| {
let category_match = match selector.category {
SolverCategory::QPU => {
matches!(solver.solver_type, SolverType::QuantumProcessor)
}
SolverCategory::Hybrid => {
matches!(solver.solver_type, SolverType::Hybrid)
}
SolverCategory::Software => {
matches!(solver.solver_type, SolverType::Software)
}
SolverCategory::All => true,
};
let availability_match = !selector.online_only || solver.available;
let name_match = selector
.name_pattern
.as_ref()
.map(|pattern| solver.name.contains(pattern))
.unwrap_or(true);
let queue_match = selector
.max_queue_time
.map(|max_time| solver.avg_load.unwrap_or(0.0) <= max_time)
.unwrap_or(true);
category_match && availability_match && name_match && queue_match
})
.collect();
if filtered_solvers.is_empty() {
return Err(DWaveError::SolverConfigError(
"No solvers match the selection criteria".to_string(),
));
}
let mut best_solver = filtered_solvers[0].clone();
for solver in &filtered_solvers[1..] {
let current_load = best_solver.avg_load.unwrap_or(f64::INFINITY);
let candidate_load = solver.avg_load.unwrap_or(f64::INFINITY);
if candidate_load < current_load {
best_solver = solver.clone();
}
}
Ok(best_solver)
}
pub fn submit_ising_with_embedding(
&self,
model: &IsingModel,
solver_id: Option<&str>,
params: Option<AdvancedProblemParams>,
embedding_config: Option<&EmbeddingConfig>,
) -> DWaveResult<Solution> {
let embedding_config = embedding_config.unwrap_or(&self.default_embedding_config);
let solver = if let Some(id) = solver_id {
self.get_leap_solvers()?
.into_iter()
.find(|s| s.id == id)
.ok_or_else(|| {
DWaveError::SolverConfigError(format!("Solver {id} not found"))
})?
} else {
self.select_solver(None)?
};
if matches!(solver.solver_type, SolverType::QuantumProcessor) {
self.submit_with_auto_embedding(model, &solver, params, embedding_config)
} else {
let legacy_params = if let Some(p) = params {
let flux_bias_map = if let Some(fb) = p.flux_biases {
let mut map = serde_json::Map::new();
for (k, v) in fb {
let value = serde_json::to_value(v).map_err(|e| {
DWaveError::ProblemError(format!(
"Failed to serialize flux bias: {e}"
))
})?;
map.insert(k, value);
}
Some(map)
} else {
None
};
ProblemParams {
num_reads: p.num_reads,
annealing_time: 20,
programming_therm: p.programming_thermalization.unwrap_or(1000),
readout_therm: p.readout_thermalization.unwrap_or(0),
flux_biases: None,
flux_bias_map,
other: serde_json::Value::Object(serde_json::Map::new()),
}
} else {
ProblemParams::default()
};
self.submit_ising(model, &solver.id, legacy_params)
}
}
fn submit_with_auto_embedding(
&self,
model: &IsingModel,
solver: &LeapSolverInfo,
params: Option<AdvancedProblemParams>,
embedding_config: &EmbeddingConfig,
) -> DWaveResult<Solution> {
let params = params.unwrap_or_default();
let mut logical_edges = Vec::new();
for coupling in model.couplings() {
logical_edges.push((coupling.i, coupling.j));
}
let hardware_graph = self.get_solver_topology(&solver.id)?;
let embedding = if let Some(custom_emb) = &embedding_config.custom_embedding {
custom_emb.clone()
} else {
let embedder = MinorMiner {
max_tries: 10 * embedding_config.optimization_level,
..Default::default()
};
embedder
.find_embedding(&logical_edges, model.num_qubits, &hardware_graph)
.map_err(|e| DWaveError::EmbeddingError(e.to_string()))?
};
let chain_strength =
Self::calculate_chain_strength(model, &embedding_config.chain_strength_method);
let embedded_problem = Self::embed_problem(model, &embedding, chain_strength)?;
self.submit_embedded_problem(&embedded_problem, solver, params)
}
fn get_solver_topology(&self, solver_id: &str) -> DWaveResult<HardwareGraph> {
let url = format!("{}/solvers/remote/{}", self.endpoint, solver_id);
let topology_info = self.runtime.block_on(async {
let response = self
.client
.get(&url)
.header("Authorization", format!("token {}", self.token))
.send()
.await?;
if !response.status().is_success() {
let status = response.status();
let error_text = response.text().await?;
return Err(DWaveError::ApiError(format!(
"Error getting solver topology: {status} - {error_text}"
)));
}
let solver_data: serde_json::Value = response.json().await?;
Ok(solver_data)
})?;
let properties = &topology_info["properties"];
if let Some(edges) = properties["couplers"].as_array() {
let mut hardware_edges = Vec::new();
for edge in edges {
if let (Some(i), Some(j)) = (edge[0].as_u64(), edge[1].as_u64()) {
hardware_edges.push((i as usize, j as usize));
}
}
let num_qubits = properties["qubits"]
.as_array()
.map(|arr| arr.len())
.unwrap_or(0);
Ok(HardwareGraph::new_custom(num_qubits, hardware_edges))
} else {
Err(DWaveError::SolverConfigError(
"Could not parse solver topology".to_string(),
))
}
}
fn calculate_chain_strength(model: &IsingModel, method: &ChainStrengthMethod) -> f64 {
match method {
ChainStrengthMethod::Auto => {
let max_coupling = model
.couplings()
.iter()
.map(|c| c.strength.abs())
.fold(0.0, f64::max);
let max_bias = (0..model.num_qubits)
.filter_map(|i| model.get_bias(i).ok())
.fold(0.0_f64, |acc, bias| acc.max(bias.abs()));
2.0 * (max_coupling.max(max_bias))
}
ChainStrengthMethod::Fixed(value) => *value,
ChainStrengthMethod::Adaptive(multiplier) => {
let avg_coupling = model
.couplings()
.iter()
.map(|c| c.strength.abs())
.sum::<f64>()
/ model.couplings().len().max(1) as f64;
multiplier * avg_coupling
}
}
}
fn embed_problem(
model: &IsingModel,
embedding: &Embedding,
chain_strength: f64,
) -> DWaveResult<IsingModel> {
let mut embedded_model = IsingModel::new(0);
let max_qubit = embedding
.chains
.values()
.flat_map(|chain| chain.iter())
.max()
.copied()
.unwrap_or(0);
embedded_model = IsingModel::new(max_qubit + 1);
for (var, chain) in &embedding.chains {
if let Ok(bias) = model.get_bias(*var) {
if bias != 0.0 {
let bias_per_qubit = bias / chain.len() as f64;
for &qubit in chain {
embedded_model
.set_bias(qubit, bias_per_qubit)
.map_err(|e| DWaveError::EmbeddingError(e.to_string()))?;
}
}
}
}
for coupling in model.couplings() {
if let (Some(chain1), Some(chain2)) = (
embedding.chains.get(&coupling.i),
embedding.chains.get(&coupling.j),
) {
for &q1 in chain1 {
for &q2 in chain2 {
embedded_model
.set_coupling(q1, q2, coupling.strength)
.map_err(|e| DWaveError::EmbeddingError(e.to_string()))?;
}
}
}
}
for chain in embedding.chains.values() {
for window in chain.windows(2) {
if let [q1, q2] = window {
embedded_model
.set_coupling(*q1, *q2, -chain_strength)
.map_err(|e| DWaveError::EmbeddingError(e.to_string()))?;
}
}
}
Ok(embedded_model)
}
fn submit_embedded_problem(
&self,
embedded_model: &IsingModel,
solver: &LeapSolverInfo,
params: AdvancedProblemParams,
) -> DWaveResult<Solution> {
let flux_bias_map = if let Some(fb) = params.flux_biases {
let mut map = serde_json::Map::new();
for (k, v) in fb {
let value = serde_json::to_value(v).map_err(|e| {
DWaveError::ProblemError(format!("Failed to serialize flux bias: {e}"))
})?;
map.insert(k, value);
}
Some(map)
} else {
None
};
let legacy_params = ProblemParams {
num_reads: params.num_reads,
annealing_time: params
.anneal_schedule
.as_ref()
.and_then(|schedule| schedule.schedule.last())
.map(|(time, _)| *time as usize)
.unwrap_or(20),
programming_therm: params.programming_thermalization.unwrap_or(1000),
readout_therm: params.readout_thermalization.unwrap_or(0),
flux_biases: None,
flux_bias_map,
other: serde_json::Value::Object(serde_json::Map::new()),
};
self.submit_ising(embedded_model, &solver.id, legacy_params)
}
pub fn submit_hybrid(
&self,
model: &IsingModel,
solver_id: Option<&str>,
params: Option<HybridSolverParams>,
) -> DWaveResult<Solution> {
let params = params.unwrap_or_default();
let solver = if let Some(id) = solver_id {
self.get_leap_solvers()?
.into_iter()
.find(|s| s.id == id)
.ok_or_else(|| {
DWaveError::SolverConfigError(format!("Solver {id} not found"))
})?
} else {
let hybrid_selector = SolverSelector {
category: SolverCategory::Hybrid,
..Default::default()
};
self.select_solver(Some(&hybrid_selector))?
};
let mut linear_terms = serde_json::Map::new();
for (qubit, bias) in model.biases() {
let value = serde_json::to_value(bias).map_err(|e| {
DWaveError::ProblemError(format!("Failed to serialize bias: {e}"))
})?;
linear_terms.insert(qubit.to_string(), value);
}
let mut quadratic_terms = serde_json::Map::new();
for coupling in model.couplings() {
let key = format!("{},{}", coupling.i, coupling.j);
let value = serde_json::to_value(coupling.strength).map_err(|e| {
DWaveError::ProblemError(format!("Failed to serialize coupling: {e}"))
})?;
quadratic_terms.insert(key, value);
}
let mut hybrid_params = params.extra.clone();
if let Some(time_limit) = params.time_limit {
let value = serde_json::to_value(time_limit).map_err(|e| {
DWaveError::ProblemError(format!("Failed to serialize time_limit: {e}"))
})?;
hybrid_params.insert("time_limit".to_string(), value);
}
let problem = Problem {
linear_terms: serde_json::Value::Object(linear_terms),
quadratic_terms: serde_json::Value::Object(quadratic_terms),
type_: "ising".to_string(),
solver: solver.id,
params: ProblemParams {
num_reads: 1,
annealing_time: 1,
programming_therm: 0,
readout_therm: 0,
flux_biases: None,
flux_bias_map: None,
other: serde_json::Value::Object(
hybrid_params.into_iter().map(|(k, v)| (k, v)).collect(),
),
},
};
self.submit_problem(&problem)
}
pub fn get_problem_status(&self, problem_id: &str) -> DWaveResult<ProblemInfo> {
let url = format!("{}/problems/{}", self.endpoint, problem_id);
self.runtime.block_on(async {
let response = self
.client
.get(&url)
.header("Authorization", format!("token {}", self.token))
.send()
.await?;
if !response.status().is_success() {
let status = response.status();
let error_text = response.text().await?;
return Err(DWaveError::ApiError(format!(
"Error getting problem status: {} - {}",
status, error_text
)));
}
let problem_info: ProblemInfo = response.json().await?;
Ok(problem_info)
})
}
pub fn cancel_problem(&self, problem_id: &str) -> DWaveResult<()> {
let url = format!("{}/problems/{}/cancel", self.endpoint, problem_id);
self.runtime.block_on(async {
let response = self
.client
.delete(&url)
.header("Authorization", format!("token {}", self.token))
.send()
.await?;
if !response.status().is_success() {
let status = response.status();
let error_text = response.text().await?;
return Err(DWaveError::ApiError(format!(
"Error cancelling problem: {} - {}",
status, error_text
)));
}
Ok(())
})
}
pub fn submit_batch(
&self,
problems: Vec<(&IsingModel, Option<&str>, Option<AdvancedProblemParams>)>,
) -> DWaveResult<BatchSubmissionResult> {
let start_time = Instant::now();
let mut problem_ids = Vec::new();
let mut statuses = Vec::new();
for (model, solver_id, params) in problems {
match self.submit_ising_with_embedding(model, solver_id, params, None) {
Ok(solution) => {
problem_ids.push(solution.problem_id.clone());
statuses.push(Ok(solution.problem_id));
}
Err(e) => {
problem_ids.push(String::new());
statuses.push(Err(e));
}
}
}
Ok(BatchSubmissionResult {
problem_ids,
statuses,
submission_time: start_time.elapsed(),
})
}
pub fn get_problem_metrics(&self, problem_id: &str) -> DWaveResult<ProblemMetrics> {
let solution = self.get_problem_result(problem_id)?;
let timing = &solution.timing;
let queue_time =
Duration::from_micros(timing["qpu_access_overhead_time"].as_u64().unwrap_or(0));
let programming_time =
Duration::from_micros(timing["qpu_programming_time"].as_u64().unwrap_or(0));
let sampling_time =
Duration::from_micros(timing["qpu_sampling_time"].as_u64().unwrap_or(0));
let readout_time =
Duration::from_micros(timing["qpu_readout_time"].as_u64().unwrap_or(0));
let total_time = queue_time + programming_time + sampling_time + readout_time;
let access_time = programming_time + sampling_time + readout_time;
let best_energy = solution
.energies
.iter()
.min_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
.copied()
.unwrap_or(f64::INFINITY);
Ok(ProblemMetrics {
total_time,
queue_time,
access_time,
programming_time,
sampling_time,
readout_time,
best_energy,
num_valid_solutions: solution.solutions.len(),
chain_break_fraction: timing["chain_break_fraction"].as_f64(),
})
}
pub fn get_problem_result(&self, problem_id: &str) -> DWaveResult<Solution> {
let start_time = Instant::now();
loop {
let status = self.get_problem_status(problem_id)?;
match status.status {
ProblemStatus::Completed => {
return self.get_solution_data(problem_id);
}
ProblemStatus::Failed => {
return Err(DWaveError::StatusError(format!(
"Problem {} failed",
problem_id
)));
}
ProblemStatus::Cancelled => {
return Err(DWaveError::StatusError(format!(
"Problem {} was cancelled",
problem_id
)));
}
ProblemStatus::InProgress | ProblemStatus::Pending => {
if start_time.elapsed() > self.problem_timeout {
return Err(DWaveError::TimeoutError(format!(
"Timeout waiting for problem {} completion",
problem_id
)));
}
std::thread::sleep(Duration::from_secs(2));
}
}
}
}
fn get_solution_data(&self, problem_id: &str) -> DWaveResult<Solution> {
let url = format!("{}/problems/{}", self.endpoint, problem_id);
self.runtime.block_on(async {
let response = self
.client
.get(&url)
.header("Authorization", format!("token {}", self.token))
.send()
.await?;
if !response.status().is_success() {
let status = response.status();
let error_text = response.text().await?;
return Err(DWaveError::ApiError(format!(
"Error getting solution data: {} - {}",
status, error_text
)));
}
let data: serde_json::Value = response.json().await?;
Ok(Solution {
energies: serde_json::from_value(data["energies"].clone())?,
occurrences: serde_json::from_value(data["occurrences"].clone())?,
solutions: serde_json::from_value(data["solutions"].clone())?,
num_samples: data["num_samples"].as_u64().unwrap_or(0) as usize,
problem_id: problem_id.to_string(),
solver: data["solver"].as_str().unwrap_or("unknown").to_string(),
timing: data["timing"].clone(),
})
})
}
pub fn list_problems(&self, limit: Option<usize>) -> DWaveResult<Vec<ProblemInfo>> {
let mut url = format!("{}/problems", self.endpoint);
if let Some(limit) = limit {
let _ = write!(url, "?limit={}", limit);
}
self.runtime.block_on(async {
let response = self
.client
.get(&url)
.header("Authorization", format!("token {}", self.token))
.send()
.await?;
if !response.status().is_success() {
let status = response.status();
let error_text = response.text().await?;
return Err(DWaveError::ApiError(format!(
"Error listing problems: {} - {}",
status, error_text
)));
}
let problems: Vec<ProblemInfo> = response.json().await?;
Ok(problems)
})
}
pub fn get_usage_info(&self) -> DWaveResult<serde_json::Value> {
let url = format!("{}/usage", self.endpoint);
self.runtime.block_on(async {
let response = self
.client
.get(&url)
.header("Authorization", format!("token {}", self.token))
.send()
.await?;
if !response.status().is_success() {
let status = response.status();
let error_text = response.text().await?;
return Err(DWaveError::ApiError(format!(
"Error getting usage info: {} - {}",
status, error_text
)));
}
let usage: serde_json::Value = response.json().await?;
Ok(usage)
})
}
}
}
#[cfg(not(feature = "dwave"))]
mod placeholder {
use thiserror::Error;
#[derive(Error, Debug)]
pub enum DWaveError {
#[error("D-Wave feature not enabled. Recompile with '--features dwave'")]
NotEnabled,
}
pub type DWaveResult<T> = Result<T, DWaveError>;
#[derive(Debug, Clone)]
pub struct DWaveClient {
_private: (),
}
impl DWaveClient {
pub fn new(_token: impl Into<String>, _endpoint: Option<String>) -> DWaveResult<Self> {
Err(DWaveError::NotEnabled)
}
}
#[derive(Debug, Clone)]
pub struct ProblemParams {
pub num_reads: usize,
pub annealing_time: usize,
pub programming_therm: usize,
pub readout_therm: usize,
}
#[derive(Debug, Clone)]
pub enum SolverType {
QuantumProcessor,
Hybrid,
Software,
}
#[derive(Debug, Clone)]
pub enum SolverCategory {
QPU,
Hybrid,
Software,
All,
}
#[derive(Debug, Clone)]
pub enum ProblemStatus {
InProgress,
Completed,
Failed,
Cancelled,
Pending,
}
#[derive(Debug, Clone)]
pub struct SolverSelector;
#[derive(Debug, Clone)]
pub struct EmbeddingConfig;
#[derive(Debug, Clone)]
pub struct AdvancedProblemParams;
#[derive(Debug, Clone)]
pub struct HybridSolverParams;
#[derive(Debug, Clone)]
pub struct LeapSolverInfo;
#[derive(Debug, Clone)]
pub struct ProblemInfo;
#[derive(Debug, Clone)]
pub struct AnnealingSchedule;
#[derive(Debug, Clone)]
pub struct ProblemMetrics;
#[derive(Debug, Clone)]
pub struct BatchSubmissionResult;
#[derive(Debug, Clone)]
pub enum ChainStrengthMethod {
Auto,
Fixed(f64),
Adaptive(f64),
}
impl Default for ProblemParams {
fn default() -> Self {
Self {
num_reads: 1000,
annealing_time: 20,
programming_therm: 1000,
readout_therm: 0,
}
}
}
}
#[must_use]
pub const fn is_available() -> bool {
cfg!(feature = "dwave")
}