Skip to main content

quantrs2_device/cloud/provider_optimizations/
azureoptimizer_traits.rs

1//! # AzureOptimizer - Trait Implementations
2//!
3//! Implements `ProviderOptimizer` for Azure Quantum.  The optimizer:
4//!
5//! - Supports IonQ and Quantinuum (formerly Cambridge Quantum/Honeywell) targets.
6//! - Uses IonQ native gates (MS + R_zz + GPi) or Quantinuum TKET-compiled ZZ + Rz.
7//! - Estimates cost using Azure Quantum pricing:
8//!   - IonQ: $0.00097 per gate-qubit (circuit resource units).
9//!   - Quantinuum: HQC credit packs (100 HQC credits ≈ $200; 1 HQC ≈ depth × qubits).
10//! - Selects the provider by performance/cost tradeoff based on circuit
11//!   characteristics.
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// Pricing constants
25// ---------------------------------------------------------------------------
26
27/// IonQ: price per circuit resource unit (gate × qubit) in USD.
28const AZURE_IONQ_PER_CRU_USD: f64 = 0.00097;
29
30/// Quantinuum HQC credit pack: 100 credits for $200 USD.
31const AZURE_QUANTINUUM_USD_PER_HQC: f64 = 2.00;
32
33/// HQC formula coefficient: H-credit = circuit_depth × qubit_count / constant.
34const AZURE_QUANTINUUM_HQC_DEPTH_CONSTANT: f64 = 5000.0;
35
36// ---------------------------------------------------------------------------
37// Backend catalogue
38// ---------------------------------------------------------------------------
39
40#[derive(Debug, Clone, Copy, PartialEq, Eq)]
41enum AzureTarget {
42    IonQAria,
43    IonQHarmony,
44    QuantinuumH1,
45    QuantinuumH2,
46}
47
48#[derive(Debug, Clone, Copy)]
49struct AzureBackendSpec {
50    target: AzureTarget,
51    name: &'static str,
52    max_qubits: usize,
53    two_qubit_fidelity: f64,
54    /// Typical queue latency in seconds.
55    queue_latency_s: f64,
56}
57
58const AZURE_BACKENDS: &[AzureBackendSpec] = &[
59    AzureBackendSpec {
60        target: AzureTarget::IonQAria,
61        name: "ionq.simulator",
62        max_qubits: 29,
63        two_qubit_fidelity: 0.972,
64        queue_latency_s: 20.0,
65    },
66    AzureBackendSpec {
67        target: AzureTarget::IonQHarmony,
68        name: "ionq.qpu",
69        max_qubits: 11,
70        two_qubit_fidelity: 0.965,
71        queue_latency_s: 40.0,
72    },
73    AzureBackendSpec {
74        target: AzureTarget::QuantinuumH1,
75        name: "quantinuum.hqs-lt-s1",
76        max_qubits: 20,
77        two_qubit_fidelity: 0.997,
78        queue_latency_s: 60.0,
79    },
80    AzureBackendSpec {
81        target: AzureTarget::QuantinuumH2,
82        name: "quantinuum.hqs-lt-s2",
83        max_qubits: 32,
84        two_qubit_fidelity: 0.998,
85        queue_latency_s: 90.0,
86    },
87];
88
89/// Estimate the cost for a single backend.
90fn estimate_azure_cost_for_backend(
91    shots: usize,
92    circuit_depth: usize,
93    qubit_count: usize,
94    backend: &AzureBackendSpec,
95) -> f64 {
96    match backend.target {
97        AzureTarget::IonQAria | AzureTarget::IonQHarmony => {
98            // IonQ charges per circuit resource unit (CRU = gate_count × qubit_targets).
99            // Approximation: gate_count ≈ circuit_depth × qubit_count / 2.
100            let approx_gates = (circuit_depth * qubit_count) as f64 / 2.0;
101            let cru = approx_gates * qubit_count as f64;
102            cru * AZURE_IONQ_PER_CRU_USD * shots as f64
103        }
104        AzureTarget::QuantinuumH1 | AzureTarget::QuantinuumH2 => {
105            // Quantinuum charges HQC credits proportional to circuit size.
106            let hqc = (circuit_depth as f64 * qubit_count as f64)
107                / AZURE_QUANTINUUM_HQC_DEPTH_CONSTANT
108                * shots as f64;
109            hqc * AZURE_QUANTINUUM_USD_PER_HQC
110        }
111    }
112}
113
114/// Select backend by best performance/cost ratio.
115fn select_azure_backend(
116    qubit_count: usize,
117    min_fidelity: f64,
118    circuit_depth: usize,
119    shots: usize,
120) -> Option<&'static AzureBackendSpec> {
121    // Gather feasible candidates.
122    let candidates: Vec<&AzureBackendSpec> = AZURE_BACKENDS
123        .iter()
124        .filter(|b| b.max_qubits >= qubit_count && b.two_qubit_fidelity >= min_fidelity)
125        .collect();
126
127    // Score each candidate by fidelity/cost ratio.
128    candidates.into_iter().max_by(|a, b| {
129        let cost_a =
130            estimate_azure_cost_for_backend(shots, circuit_depth, qubit_count, a).max(1e-9);
131        let cost_b =
132            estimate_azure_cost_for_backend(shots, circuit_depth, qubit_count, b).max(1e-9);
133        let score_a = a.two_qubit_fidelity / cost_a;
134        let score_b = b.two_qubit_fidelity / cost_b;
135        score_a
136            .partial_cmp(&score_b)
137            .unwrap_or(std::cmp::Ordering::Equal)
138    })
139}
140
141// ---------------------------------------------------------------------------
142// ProviderOptimizer implementation
143// ---------------------------------------------------------------------------
144
145impl ProviderOptimizer for AzureOptimizer {
146    /// Produce an `OptimizationRecommendation` for executing the workload on
147    /// Azure Quantum.
148    ///
149    /// Selects the target with the best fidelity/cost tradeoff, recommends
150    /// native-gate transpilation for the selected provider, and exposes
151    /// alternative targets.
152    fn optimize_workload(
153        &self,
154        workload: &WorkloadSpec,
155    ) -> DeviceResult<OptimizationRecommendation> {
156        let qubit_count = workload.circuit_characteristics.qubit_count;
157        let shots = workload.execution_requirements.shots;
158        let min_fidelity = workload
159            .circuit_characteristics
160            .coherence_requirements
161            .min_gate_fidelity;
162        let circuit_depth = workload.circuit_characteristics.circuit_depth;
163
164        let primary = select_azure_backend(qubit_count, min_fidelity, circuit_depth, shots)
165            .ok_or_else(|| {
166                crate::DeviceError::InvalidInput(format!(
167                    "No Azure Quantum backend can accommodate {qubit_count} qubits \
168                     with two-qubit fidelity ≥ {min_fidelity:.3}"
169                ))
170            })?;
171
172        // Transpilation level depends on the target technology.
173        let (gate_fusion, zne) = match primary.target {
174            AzureTarget::QuantinuumH1 | AzureTarget::QuantinuumH2 => (true, circuit_depth > 40),
175            _ => (true, false),
176        };
177
178        let recommended_config = ExecutionConfig {
179            provider: CloudProvider::Azure,
180            backend: primary.name.to_string(),
181            optimization_settings: OptimizationSettings {
182                circuit_optimization: CircuitOptimizationSettings {
183                    gate_fusion,
184                    gate_cancellation: true,
185                    circuit_compression: true,
186                    transpilation_level: TranspilationLevel::Aggressive,
187                    error_mitigation: ErrorMitigationSettings {
188                        zero_noise_extrapolation: zne,
189                        readout_error_mitigation: true,
190                        gate_error_mitigation: false,
191                        decoherence_mitigation: false,
192                        crosstalk_mitigation: false,
193                    },
194                },
195                hardware_optimization: HardwareOptimizationSettings {
196                    qubit_mapping: QubitMappingStrategy::FidelityOptimized,
197                    routing_optimization: RoutingOptimizationStrategy::FidelityAware,
198                    calibration_optimization: CalibrationOptimizationStrategy::Dynamic,
199                    noise_adaptation: NoiseAdaptationStrategy::Statistical,
200                },
201                ..OptimizationSettings::default()
202            },
203            ..ExecutionConfig::default()
204        };
205
206        let cost_estimate = compute_azure_cost(shots, circuit_depth, qubit_count, primary);
207        let perf_prediction = compute_azure_performance(workload, primary);
208
209        let alternatives: Vec<AlternativeRecommendation> = AZURE_BACKENDS
210            .iter()
211            .filter(|b| {
212                b.name != primary.name
213                    && b.max_qubits >= qubit_count
214                    && b.two_qubit_fidelity >= min_fidelity
215            })
216            .map(|b| {
217                let alt_cost = compute_azure_cost(shots, circuit_depth, qubit_count, b);
218                let alt_perf = compute_azure_performance(workload, b);
219                AlternativeRecommendation {
220                    alternative_id: Uuid::new_v4().to_string(),
221                    config: ExecutionConfig {
222                        provider: CloudProvider::Azure,
223                        backend: b.name.to_string(),
224                        optimization_settings: OptimizationSettings::default(),
225                        ..ExecutionConfig::default()
226                    },
227                    trade_offs: TradeOffAnalysis {
228                        performance_impact: alt_perf.expected_fidelity
229                            - perf_prediction.expected_fidelity,
230                        cost_impact: alt_cost.total_cost - cost_estimate.total_cost,
231                        reliability_impact: 0.0,
232                        complexity_impact: 0.0,
233                        trade_off_summary: format!(
234                            "{} — Δcost ${:+.4}, Δfidelity {:+.4}",
235                            b.name,
236                            alt_cost.total_cost - cost_estimate.total_cost,
237                            alt_perf.expected_fidelity - perf_prediction.expected_fidelity
238                        ),
239                    },
240                    use_case_suitability: alt_perf.success_probability,
241                }
242            })
243            .collect();
244
245        let rationale = format!(
246            "Selected {} on Azure Quantum as the best fidelity/cost tradeoff \
247             for {qubit_count} qubits at ≥{min_fidelity:.3} two-qubit fidelity. \
248             Estimated cost: ${:.4}. ZNE: {}.",
249            primary.name,
250            cost_estimate.total_cost,
251            if zne { "enabled" } else { "disabled" }
252        );
253
254        Ok(OptimizationRecommendation {
255            recommendation_id: Uuid::new_v4().to_string(),
256            workload_id: workload.workload_id.clone(),
257            provider: CloudProvider::Azure,
258            recommended_config,
259            optimization_strategies: self.get_optimization_strategies(),
260            expected_performance: perf_prediction,
261            cost_estimate,
262            confidence_score: 0.85,
263            rationale,
264            alternative_recommendations: alternatives,
265        })
266    }
267
268    fn get_provider(&self) -> CloudProvider {
269        CloudProvider::Azure
270    }
271
272    fn get_optimization_strategies(&self) -> Vec<OptimizationStrategy> {
273        vec![
274            OptimizationStrategy::SchedulingOptimization,
275            OptimizationStrategy::HardwareSelection,
276            OptimizationStrategy::CacheOptimization,
277        ]
278    }
279
280    fn predict_performance(
281        &self,
282        workload: &WorkloadSpec,
283        config: &ExecutionConfig,
284    ) -> DeviceResult<PerformancePrediction> {
285        let backend = AZURE_BACKENDS
286            .iter()
287            .find(|b| b.name == config.backend)
288            .unwrap_or(&AZURE_BACKENDS[0]);
289        Ok(compute_azure_performance(workload, backend))
290    }
291
292    fn estimate_cost(
293        &self,
294        workload: &WorkloadSpec,
295        config: &ExecutionConfig,
296    ) -> DeviceResult<CostEstimate> {
297        let backend = AZURE_BACKENDS
298            .iter()
299            .find(|b| b.name == config.backend)
300            .unwrap_or(&AZURE_BACKENDS[0]);
301        Ok(compute_azure_cost(
302            workload.execution_requirements.shots,
303            workload.circuit_characteristics.circuit_depth,
304            workload.circuit_characteristics.qubit_count,
305            backend,
306        ))
307    }
308}
309
310// ---------------------------------------------------------------------------
311// Internal helpers
312// ---------------------------------------------------------------------------
313
314fn compute_azure_cost(
315    shots: usize,
316    circuit_depth: usize,
317    qubit_count: usize,
318    backend: &AzureBackendSpec,
319) -> CostEstimate {
320    let total = estimate_azure_cost_for_backend(shots, circuit_depth, qubit_count, backend);
321    let uncertainty = total * 0.18;
322
323    CostEstimate {
324        total_cost: total,
325        cost_breakdown: CostBreakdown {
326            execution_cost: total,
327            queue_cost: 0.0,
328            storage_cost: 0.0,
329            network_cost: 0.0,
330            overhead_cost: 0.0,
331            discount_applied: 0.0,
332        },
333        cost_model: CostModel::PayPerUse,
334        uncertainty_range: (total - uncertainty, total + uncertainty),
335        cost_optimization_opportunities: vec![CostOptimizationOpportunity {
336            opportunity_type: CostOptimizationType::ResourceRightSizing,
337            potential_savings: total * 0.12,
338            implementation_effort: 0.25,
339            description: "Reduce circuit depth via aggressive TKET optimisation to lower \
340                           HQC credit consumption on Quantinuum targets."
341                .to_string(),
342        }],
343    }
344}
345
346fn compute_azure_performance(
347    workload: &WorkloadSpec,
348    backend: &AzureBackendSpec,
349) -> PerformancePrediction {
350    let cc = &workload.circuit_characteristics;
351    let shots = workload.execution_requirements.shots;
352
353    // Fidelity model: fidelity^(depth/4) approximates accumulated two-qubit errors.
354    let two_qubit_layers = (cc.circuit_depth / 4).max(1) as f64;
355    let expected_fidelity = backend
356        .two_qubit_fidelity
357        .powf(two_qubit_layers)
358        .clamp(0.01, 1.0);
359    let success_probability = (expected_fidelity * 0.99).clamp(0.0, 1.0);
360
361    // Execution time: trapped-ion gates are slow (~250 µs per CX); assume 2 ms/shot.
362    let exec_s = shots as f64 * 0.002;
363
364    PerformancePrediction {
365        execution_time: Duration::from_secs_f64(exec_s),
366        queue_time: Duration::from_secs_f64(backend.queue_latency_s),
367        total_time: Duration::from_secs_f64(exec_s + backend.queue_latency_s),
368        success_probability,
369        expected_fidelity,
370        resource_utilization: ResourceUtilizationPrediction {
371            cpu_utilization: 0.03,
372            memory_utilization: 0.06,
373            quantum_resource_utilization: cc.qubit_count as f64 / backend.max_qubits as f64,
374            network_utilization: 0.01,
375            storage_utilization: 0.005,
376        },
377        bottlenecks: Vec::new(),
378        confidence_interval: (success_probability * 0.94, success_probability * 1.03),
379    }
380}