Skip to main content

quantrs2_device/cloud/provider_optimizations/
ibmoptimizer_traits.rs

1//! # IBMOptimizer - Trait Implementations
2//!
3//! Implements `ProviderOptimizer` for IBM Quantum.  The optimizer:
4//!
5//! - Prefers the CNOT + Rz + √X (Sx) native gate set exposed by all IBM Eagle
6//!   and Heron processors.
7//! - Recommends Sabre qubit-routing to minimise SWAP overhead on heavy-hex
8//!   topologies.
9//! - Estimates cost using IBM Quantum's runtime-second pricing (free-tier ≤ 600 s/month;
10//!   premium plan at $1.60 / runtime-second).
11//! - Selects the least-busy backend with sufficient qubit count.
12//!
13//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
14
15use super::traits::ProviderOptimizer;
16use super::types::*;
17use crate::prelude::CloudProvider;
18use crate::DeviceResult;
19use std::collections::HashMap;
20use std::time::Duration;
21use uuid::Uuid;
22
23// ---------------------------------------------------------------------------
24// IBM Quantum pricing constants (USD, 2024 schedule)
25// ---------------------------------------------------------------------------
26
27/// Free-tier monthly runtime budget (seconds).  Jobs that fit within this
28/// window cost nothing.
29const IBM_FREE_TIER_SECONDS_PER_MONTH: f64 = 600.0;
30
31/// Premium pricing per runtime-second (USD).
32const IBM_PREMIUM_PER_RUNTIME_SECOND_USD: f64 = 1.60;
33
34// ---------------------------------------------------------------------------
35// IBM backend catalogue (representative subset, 2024 Q3)
36// ---------------------------------------------------------------------------
37
38#[derive(Debug, Clone, Copy)]
39struct IbmBackendSpec {
40    name: &'static str,
41    qubit_count: usize,
42    /// Current (simulated) queue depth as a fraction of daily capacity [0,1].
43    /// A lower value means the backend is less busy and a job will start sooner.
44    relative_queue_depth: f64,
45    /// Representative CNOT fidelity.
46    cx_fidelity: f64,
47    /// Typical runtime per 1 000 shots (seconds).
48    runtime_per_1k_shots_s: f64,
49}
50
51const IBM_BACKENDS: &[IbmBackendSpec] = &[
52    IbmBackendSpec {
53        name: "ibm_brisbane",
54        qubit_count: 127,
55        relative_queue_depth: 0.55,
56        cx_fidelity: 0.985,
57        runtime_per_1k_shots_s: 2.5,
58    },
59    IbmBackendSpec {
60        name: "ibm_kyoto",
61        qubit_count: 127,
62        relative_queue_depth: 0.40,
63        cx_fidelity: 0.982,
64        runtime_per_1k_shots_s: 2.6,
65    },
66    IbmBackendSpec {
67        name: "ibm_osaka",
68        qubit_count: 127,
69        relative_queue_depth: 0.70,
70        cx_fidelity: 0.987,
71        runtime_per_1k_shots_s: 2.4,
72    },
73    IbmBackendSpec {
74        name: "ibm_sherbrooke",
75        qubit_count: 127,
76        relative_queue_depth: 0.30,
77        cx_fidelity: 0.983,
78        runtime_per_1k_shots_s: 2.5,
79    },
80    IbmBackendSpec {
81        name: "ibm_torino",
82        qubit_count: 133,
83        relative_queue_depth: 0.25,
84        cx_fidelity: 0.991,
85        runtime_per_1k_shots_s: 2.2,
86    },
87];
88
89/// Select the least-busy IBM backend that can host `qubit_count` qubits
90/// and satisfies `min_fidelity`.
91fn select_ibm_backend(qubit_count: usize, min_fidelity: f64) -> Option<&'static IbmBackendSpec> {
92    IBM_BACKENDS
93        .iter()
94        .filter(|b| b.qubit_count >= qubit_count && b.cx_fidelity >= min_fidelity)
95        .min_by(|a, b| {
96            a.relative_queue_depth
97                .partial_cmp(&b.relative_queue_depth)
98                .unwrap_or(std::cmp::Ordering::Equal)
99        })
100}
101
102// ---------------------------------------------------------------------------
103// ProviderOptimizer implementation
104// ---------------------------------------------------------------------------
105
106impl ProviderOptimizer for IBMOptimizer {
107    /// Produce an `OptimizationRecommendation` for executing the workload on
108    /// IBM Quantum.
109    ///
110    /// Strategy:
111    /// 1. Select the least-busy backend that meets the qubit and fidelity
112    ///    requirements.
113    /// 2. Enable CNOT+Rz+Sx gate set via aggressive transpilation.
114    /// 3. Apply readout error mitigation (TREX / M3) and ZNE if fidelity is
115    ///    critical (depth > 50).
116    fn optimize_workload(
117        &self,
118        workload: &WorkloadSpec,
119    ) -> DeviceResult<OptimizationRecommendation> {
120        let qubit_count = workload.circuit_characteristics.qubit_count;
121        let shots = workload.execution_requirements.shots;
122        let min_fidelity = workload
123            .circuit_characteristics
124            .coherence_requirements
125            .min_gate_fidelity;
126        let depth = workload.circuit_characteristics.circuit_depth;
127
128        let primary = select_ibm_backend(qubit_count, min_fidelity).ok_or_else(|| {
129            crate::DeviceError::InvalidInput(format!(
130                "No IBM Quantum backend can accommodate {qubit_count} qubits \
131                 with CNOT fidelity ≥ {min_fidelity:.3}"
132            ))
133        })?;
134
135        // Enable ZNE for deep circuits (depth > 50) where decoherence matters.
136        let use_zne = depth > 50;
137
138        let recommended_config = ExecutionConfig {
139            provider: CloudProvider::IBM,
140            backend: primary.name.to_string(),
141            optimization_settings: OptimizationSettings {
142                circuit_optimization: CircuitOptimizationSettings {
143                    gate_fusion: true,
144                    gate_cancellation: true,
145                    circuit_compression: true,
146                    transpilation_level: TranspilationLevel::Advanced,
147                    error_mitigation: ErrorMitigationSettings {
148                        zero_noise_extrapolation: use_zne,
149                        readout_error_mitigation: true,
150                        gate_error_mitigation: false,
151                        decoherence_mitigation: use_zne,
152                        crosstalk_mitigation: false,
153                    },
154                },
155                hardware_optimization: HardwareOptimizationSettings {
156                    qubit_mapping: QubitMappingStrategy::NoiseAdaptive,
157                    routing_optimization: RoutingOptimizationStrategy::MinimumSwaps,
158                    calibration_optimization: CalibrationOptimizationStrategy::Dynamic,
159                    noise_adaptation: NoiseAdaptationStrategy::ModelBased,
160                },
161                ..OptimizationSettings::default()
162            },
163            ..ExecutionConfig::default()
164        };
165
166        let cost_estimate = compute_ibm_cost(shots, primary);
167        let perf_prediction = compute_ibm_performance(workload, primary);
168
169        let alternatives: Vec<AlternativeRecommendation> = IBM_BACKENDS
170            .iter()
171            .filter(|b| {
172                b.name != primary.name
173                    && b.qubit_count >= qubit_count
174                    && b.cx_fidelity >= min_fidelity
175            })
176            .map(|b| {
177                let alt_cost = compute_ibm_cost(shots, b);
178                let alt_perf = compute_ibm_performance(workload, b);
179                AlternativeRecommendation {
180                    alternative_id: Uuid::new_v4().to_string(),
181                    config: ExecutionConfig {
182                        provider: CloudProvider::IBM,
183                        backend: b.name.to_string(),
184                        optimization_settings: OptimizationSettings::default(),
185                        ..ExecutionConfig::default()
186                    },
187                    trade_offs: TradeOffAnalysis {
188                        performance_impact: alt_perf.expected_fidelity
189                            - perf_prediction.expected_fidelity,
190                        cost_impact: alt_cost.total_cost - cost_estimate.total_cost,
191                        reliability_impact: 0.0,
192                        complexity_impact: 0.0,
193                        trade_off_summary: format!(
194                            "{} — queue depth {:.0}%, Δcost ${:+.2}, Δfidelity {:+.3}",
195                            b.name,
196                            b.relative_queue_depth * 100.0,
197                            alt_cost.total_cost - cost_estimate.total_cost,
198                            alt_perf.expected_fidelity - perf_prediction.expected_fidelity
199                        ),
200                    },
201                    use_case_suitability: alt_perf.success_probability,
202                }
203            })
204            .collect();
205
206        let rationale = format!(
207            "Selected {} as the least-busy IBM Quantum backend with ≥ {qubit_count} qubits \
208             and CNOT fidelity ≥ {min_fidelity:.3}. \
209             Current relative queue depth: {:.0}%. \
210             ZNE mitigation: {}. Estimated cost: ${:.2}.",
211            primary.name,
212            primary.relative_queue_depth * 100.0,
213            if use_zne { "enabled" } else { "disabled" },
214            cost_estimate.total_cost,
215        );
216
217        Ok(OptimizationRecommendation {
218            recommendation_id: Uuid::new_v4().to_string(),
219            workload_id: workload.workload_id.clone(),
220            provider: CloudProvider::IBM,
221            recommended_config,
222            optimization_strategies: self.get_optimization_strategies(),
223            expected_performance: perf_prediction,
224            cost_estimate,
225            confidence_score: 0.88,
226            rationale,
227            alternative_recommendations: alternatives,
228        })
229    }
230
231    fn get_provider(&self) -> CloudProvider {
232        CloudProvider::IBM
233    }
234
235    fn get_optimization_strategies(&self) -> Vec<OptimizationStrategy> {
236        vec![
237            OptimizationStrategy::CircuitOptimization,
238            OptimizationStrategy::HardwareSelection,
239            OptimizationStrategy::ErrorMitigation,
240        ]
241    }
242
243    fn predict_performance(
244        &self,
245        workload: &WorkloadSpec,
246        config: &ExecutionConfig,
247    ) -> DeviceResult<PerformancePrediction> {
248        let backend = IBM_BACKENDS
249            .iter()
250            .find(|b| b.name == config.backend)
251            .unwrap_or(&IBM_BACKENDS[0]);
252        Ok(compute_ibm_performance(workload, backend))
253    }
254
255    fn estimate_cost(
256        &self,
257        workload: &WorkloadSpec,
258        config: &ExecutionConfig,
259    ) -> DeviceResult<CostEstimate> {
260        let backend = IBM_BACKENDS
261            .iter()
262            .find(|b| b.name == config.backend)
263            .unwrap_or(&IBM_BACKENDS[0]);
264        Ok(compute_ibm_cost(
265            workload.execution_requirements.shots,
266            backend,
267        ))
268    }
269}
270
271// ---------------------------------------------------------------------------
272// Internal helpers
273// ---------------------------------------------------------------------------
274
275fn compute_ibm_cost(shots: usize, backend: &IbmBackendSpec) -> CostEstimate {
276    // Runtime (seconds) = shots / 1000 × time-per-1k-shots
277    let runtime_s = (shots as f64 / 1000.0) * backend.runtime_per_1k_shots_s;
278
279    // Apply free tier: first 600 s/month are free.
280    let billable_s = (runtime_s - IBM_FREE_TIER_SECONDS_PER_MONTH).max(0.0);
281    let execution_cost = billable_s * IBM_PREMIUM_PER_RUNTIME_SECOND_USD;
282    let total = execution_cost;
283    let uncertainty = (total * 0.20).max(0.01);
284
285    CostEstimate {
286        total_cost: total,
287        cost_breakdown: CostBreakdown {
288            execution_cost,
289            queue_cost: 0.0,
290            storage_cost: 0.0,
291            network_cost: 0.0,
292            overhead_cost: 0.0,
293            discount_applied: (runtime_s.min(IBM_FREE_TIER_SECONDS_PER_MONTH))
294                * IBM_PREMIUM_PER_RUNTIME_SECOND_USD,
295        },
296        cost_model: CostModel::PayPerUse,
297        uncertainty_range: (total - uncertainty, total + uncertainty),
298        cost_optimization_opportunities: vec![CostOptimizationOpportunity {
299            opportunity_type: CostOptimizationType::SchedulingOptimization,
300            potential_savings: total * 0.15,
301            implementation_effort: 0.3,
302            description: "Schedule during off-peak hours to reduce queue wait time \
303                           and stay within the monthly free-tier budget."
304                .to_string(),
305        }],
306    }
307}
308
309fn compute_ibm_performance(
310    workload: &WorkloadSpec,
311    backend: &IbmBackendSpec,
312) -> PerformancePrediction {
313    let cc = &workload.circuit_characteristics;
314    let shots = workload.execution_requirements.shots;
315
316    // CNOT error accumulates per two-qubit layer.
317    let cx_layers = (cc.circuit_depth / 3).max(1) as f64;
318    let expected_fidelity = backend.cx_fidelity.powf(cx_layers).clamp(0.01, 1.0);
319
320    // Queue wait: proportional to relative queue depth (0–300 s range).
321    let queue_s = backend.relative_queue_depth * 300.0;
322
323    // Execution time modelled as runtime per 1k shots.
324    let exec_s = (shots as f64 / 1000.0) * backend.runtime_per_1k_shots_s;
325
326    let success_probability = (expected_fidelity * 0.98).clamp(0.0, 1.0);
327
328    PerformancePrediction {
329        execution_time: Duration::from_secs_f64(exec_s),
330        queue_time: Duration::from_secs_f64(queue_s),
331        total_time: Duration::from_secs_f64(exec_s + queue_s),
332        success_probability,
333        expected_fidelity,
334        resource_utilization: ResourceUtilizationPrediction {
335            cpu_utilization: 0.03,
336            memory_utilization: 0.08,
337            quantum_resource_utilization: cc.qubit_count as f64 / backend.qubit_count as f64,
338            network_utilization: 0.01,
339            storage_utilization: 0.005,
340        },
341        bottlenecks: Vec::new(),
342        confidence_interval: (success_probability * 0.93, success_probability * 1.04),
343    }
344}