Skip to main content

quantrs2_device/cloud/provider_optimizations/
awsoptimizer_traits.rs

1//! # AWSOptimizer - Trait Implementations
2//!
3//! Implements `ProviderOptimizer` for AWS Braket. The optimizer:
4//!
5//! - Selects the cheapest backend that meets fidelity requirements (IonQ, Rigetti OQC).
6//! - Estimates cost using AWS Braket per-task and per-shot pricing (as of 2024).
7//! - Predicts performance by modelling gate-count, circuit depth, and shot count
8//!   against empirical backend characteristics.
9//!
10//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
11
12use super::traits::ProviderOptimizer;
13use super::types::*;
14use crate::prelude::CloudProvider;
15use crate::DeviceResult;
16use std::collections::HashMap;
17use std::time::Duration;
18use uuid::Uuid;
19
20// ---------------------------------------------------------------------------
21// AWS Braket pricing constants (USD, 2024 schedule)
22// ---------------------------------------------------------------------------
23
24/// Per-task base fee (USD) charged regardless of shot count.
25const AWS_PER_TASK_FEE_USD: f64 = 0.30;
26
27/// Per-shot price on IonQ hardware (USD).
28const AWS_IONQ_PER_SHOT_USD: f64 = 0.00035;
29
30/// Per-shot price on Rigetti hardware (USD).
31const AWS_RIGETTI_PER_SHOT_USD: f64 = 0.00075;
32
33/// Per-shot price on OQC hardware (USD).
34const AWS_OQC_PER_SHOT_USD: f64 = 0.00035;
35
36// ---------------------------------------------------------------------------
37// Backend fidelity reference values (empirical, dimensionless ∈ [0,1])
38// ---------------------------------------------------------------------------
39
40/// Expected two-qubit gate fidelity on IonQ Harmony.
41const IONQ_TWO_QUBIT_FIDELITY: f64 = 0.965;
42
43/// Expected two-qubit gate fidelity on Rigetti Aspen-M series.
44const RIGETTI_TWO_QUBIT_FIDELITY: f64 = 0.935;
45
46/// Expected two-qubit gate fidelity on OQC Lucy.
47const OQC_TWO_QUBIT_FIDELITY: f64 = 0.958;
48
49// ---------------------------------------------------------------------------
50// Helper: choose the cheapest backend that meets minimum fidelity
51// ---------------------------------------------------------------------------
52
53#[derive(Debug, Clone, Copy)]
54struct BackendSpec {
55    name: &'static str,
56    per_shot_usd: f64,
57    two_qubit_fidelity: f64,
58    max_qubits: usize,
59    /// Typical per-shot queue latency (seconds).
60    queue_latency_s: f64,
61}
62
63const AWS_BACKENDS: &[BackendSpec] = &[
64    BackendSpec {
65        name: "ionq.harmony",
66        per_shot_usd: AWS_IONQ_PER_SHOT_USD,
67        two_qubit_fidelity: IONQ_TWO_QUBIT_FIDELITY,
68        max_qubits: 11,
69        queue_latency_s: 30.0,
70    },
71    BackendSpec {
72        name: "ionq.aria-1",
73        per_shot_usd: AWS_IONQ_PER_SHOT_USD,
74        two_qubit_fidelity: 0.975,
75        max_qubits: 25,
76        queue_latency_s: 45.0,
77    },
78    BackendSpec {
79        name: "rigetti.aspen-m-3",
80        per_shot_usd: AWS_RIGETTI_PER_SHOT_USD,
81        two_qubit_fidelity: RIGETTI_TWO_QUBIT_FIDELITY,
82        max_qubits: 79,
83        queue_latency_s: 20.0,
84    },
85    BackendSpec {
86        name: "oqc.lucy",
87        per_shot_usd: AWS_OQC_PER_SHOT_USD,
88        two_qubit_fidelity: OQC_TWO_QUBIT_FIDELITY,
89        max_qubits: 8,
90        queue_latency_s: 25.0,
91    },
92];
93
94/// Select the cheapest backend that satisfies qubit count and fidelity
95/// constraints.  Returns `None` when no backend can accommodate the workload.
96fn select_aws_backend(qubit_count: usize, min_fidelity: f64) -> Option<&'static BackendSpec> {
97    AWS_BACKENDS
98        .iter()
99        .filter(|b| b.max_qubits >= qubit_count && b.two_qubit_fidelity >= min_fidelity)
100        .min_by(|a, b| {
101            a.per_shot_usd
102                .partial_cmp(&b.per_shot_usd)
103                .unwrap_or(std::cmp::Ordering::Equal)
104        })
105}
106
107// ---------------------------------------------------------------------------
108// ProviderOptimizer implementation
109// ---------------------------------------------------------------------------
110
111impl ProviderOptimizer for AWSOptimizer {
112    /// Produce an `OptimizationRecommendation` for executing the workload on
113    /// AWS Braket.
114    ///
115    /// The recommendation:
116    /// 1. Selects the cheapest AWS Braket backend that fits the qubit count
117    ///    and coherence requirements.
118    /// 2. Recommends native-gate transpilation (IonQ: MS + Rz; Rigetti: CZ + Rz;
119    ///    OQC: ECR + Rz) to minimise two-qubit gate overhead.
120    /// 3. Scores cost and performance and populates `alternative_recommendations`
121    ///    with other feasible backends for comparison.
122    fn optimize_workload(
123        &self,
124        workload: &WorkloadSpec,
125    ) -> DeviceResult<OptimizationRecommendation> {
126        let qubit_count = workload.circuit_characteristics.qubit_count;
127        let shots = workload.execution_requirements.shots;
128        let min_fidelity = workload
129            .circuit_characteristics
130            .coherence_requirements
131            .min_gate_fidelity;
132
133        // Select the primary (cheapest-qualifying) backend.
134        let primary = select_aws_backend(qubit_count, min_fidelity).ok_or_else(|| {
135            crate::DeviceError::InvalidInput(format!(
136                "No AWS Braket backend can accommodate {qubit_count} qubits \
137                 with fidelity ≥ {min_fidelity:.3}"
138            ))
139        })?;
140
141        // Build the recommended ExecutionConfig.
142        let recommended_config = ExecutionConfig {
143            provider: CloudProvider::AWS,
144            backend: primary.name.to_string(),
145            optimization_settings: OptimizationSettings {
146                circuit_optimization: CircuitOptimizationSettings {
147                    gate_fusion: true,
148                    gate_cancellation: true,
149                    circuit_compression: true,
150                    transpilation_level: TranspilationLevel::Advanced,
151                    error_mitigation: ErrorMitigationSettings {
152                        zero_noise_extrapolation: false,
153                        readout_error_mitigation: true,
154                        gate_error_mitigation: false,
155                        decoherence_mitigation: false,
156                        crosstalk_mitigation: false,
157                    },
158                },
159                ..OptimizationSettings::default()
160            },
161            ..ExecutionConfig::default()
162        };
163
164        // Build cost and performance predictions.
165        let cost_estimate = compute_aws_cost(shots, primary);
166        let perf_prediction = compute_aws_performance(workload, primary);
167
168        // Build alternative recommendations from other feasible backends.
169        let alternatives: Vec<AlternativeRecommendation> = AWS_BACKENDS
170            .iter()
171            .filter(|b| {
172                b.name != primary.name
173                    && b.max_qubits >= qubit_count
174                    && b.two_qubit_fidelity >= min_fidelity
175            })
176            .map(|b| {
177                let alt_cost = compute_aws_cost(shots, b);
178                let alt_perf = compute_aws_performance(workload, b);
179                AlternativeRecommendation {
180                    alternative_id: Uuid::new_v4().to_string(),
181                    config: ExecutionConfig {
182                        provider: CloudProvider::AWS,
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                            "{} — Δcost ${:+.4}, Δfidelity {:+.3}",
195                            b.name,
196                            alt_cost.total_cost - cost_estimate.total_cost,
197                            alt_perf.expected_fidelity - perf_prediction.expected_fidelity
198                        ),
199                    },
200                    use_case_suitability: alt_perf.success_probability,
201                }
202            })
203            .collect();
204
205        let rationale = format!(
206            "Selected {} as the cheapest AWS Braket backend that supports {qubit_count} qubits \
207             with two-qubit gate fidelity ≥ {min_fidelity:.3}. \
208             Estimated cost: ${:.4} for {shots} shots.",
209            primary.name, cost_estimate.total_cost,
210        );
211
212        Ok(OptimizationRecommendation {
213            recommendation_id: Uuid::new_v4().to_string(),
214            workload_id: workload.workload_id.clone(),
215            provider: CloudProvider::AWS,
216            recommended_config,
217            optimization_strategies: self.get_optimization_strategies(),
218            expected_performance: perf_prediction,
219            cost_estimate,
220            confidence_score: 0.82,
221            rationale,
222            alternative_recommendations: alternatives,
223        })
224    }
225
226    fn get_provider(&self) -> CloudProvider {
227        CloudProvider::AWS
228    }
229
230    fn get_optimization_strategies(&self) -> Vec<OptimizationStrategy> {
231        vec![
232            OptimizationStrategy::CostOptimization,
233            OptimizationStrategy::LoadBalancing,
234            OptimizationStrategy::ResourceProvisioning,
235        ]
236    }
237
238    /// Predict execution performance for a given workload/config pair on
239    /// AWS Braket.
240    ///
241    /// The model accounts for:
242    /// - Gate count relative to coherence budget (decoherence penalty).
243    /// - Qubit count vs backend capacity (routing overhead).
244    /// - Shot count vs typical queue depth.
245    fn predict_performance(
246        &self,
247        workload: &WorkloadSpec,
248        config: &ExecutionConfig,
249    ) -> DeviceResult<PerformancePrediction> {
250        // Look up the backend spec; fall back to Rigetti if not found.
251        let backend = AWS_BACKENDS
252            .iter()
253            .find(|b| b.name == config.backend)
254            .unwrap_or(&AWS_BACKENDS[2]);
255
256        Ok(compute_aws_performance(workload, backend))
257    }
258
259    /// Estimate cost for a given workload/config pair on AWS Braket.
260    ///
261    /// Pricing model:
262    /// - `total_cost = per_task_fee + shots × per_shot_price`
263    fn estimate_cost(
264        &self,
265        workload: &WorkloadSpec,
266        config: &ExecutionConfig,
267    ) -> DeviceResult<CostEstimate> {
268        let backend = AWS_BACKENDS
269            .iter()
270            .find(|b| b.name == config.backend)
271            .unwrap_or(&AWS_BACKENDS[2]);
272        Ok(compute_aws_cost(
273            workload.execution_requirements.shots,
274            backend,
275        ))
276    }
277}
278
279// ---------------------------------------------------------------------------
280// Internal helpers
281// ---------------------------------------------------------------------------
282
283fn compute_aws_cost(shots: usize, backend: &BackendSpec) -> CostEstimate {
284    let execution_cost = shots as f64 * backend.per_shot_usd;
285    let total = execution_cost + AWS_PER_TASK_FEE_USD;
286    // 15 % uncertainty band on quantum hardware.
287    let uncertainty = total * 0.15;
288
289    CostEstimate {
290        total_cost: total,
291        cost_breakdown: CostBreakdown {
292            execution_cost,
293            queue_cost: 0.0,
294            storage_cost: 0.0,
295            network_cost: 0.0,
296            overhead_cost: AWS_PER_TASK_FEE_USD,
297            discount_applied: 0.0,
298        },
299        cost_model: CostModel::PayPerUse,
300        uncertainty_range: (total - uncertainty, total + uncertainty),
301        cost_optimization_opportunities: vec![CostOptimizationOpportunity {
302            opportunity_type: CostOptimizationType::VolumeDiscount,
303            potential_savings: total * 0.10,
304            implementation_effort: 0.2,
305            description: "Consolidate multiple small jobs into a single batch task to \
306                           amortise the per-task fee."
307                .to_string(),
308        }],
309    }
310}
311
312fn compute_aws_performance(
313    workload: &WorkloadSpec,
314    backend: &BackendSpec,
315) -> PerformancePrediction {
316    let cc = &workload.circuit_characteristics;
317    let shots = workload.execution_requirements.shots;
318
319    // Decoherence penalty: fidelity degrades roughly as (fidelity)^(2q_gates).
320    // We count two-qubit gates as circuit_depth / 2 (rough approximation).
321    let two_qubit_gates = (cc.circuit_depth / 2).max(1) as f64;
322    let expected_fidelity = (backend.two_qubit_fidelity.powf(two_qubit_gates)).clamp(0.01, 1.0);
323
324    // Routing overhead: if qubit count > half capacity, add a depth penalty.
325    let routing_penalty = if cc.qubit_count as f64 > backend.max_qubits as f64 * 0.6 {
326        0.90
327    } else {
328        1.0
329    };
330    let adjusted_fidelity = (expected_fidelity * routing_penalty).clamp(0.0, 1.0);
331
332    // Success probability: product of readout fidelity (0.97) and gate fidelity.
333    let success_probability = (adjusted_fidelity * 0.97).clamp(0.0, 1.0);
334
335    // Execution time: each shot takes ~1 ms on trapped-ion, plus queue wait.
336    let exec_time_ms = shots as f64 * 1.5;
337    let queue_time_s = backend.queue_latency_s;
338
339    PerformancePrediction {
340        execution_time: Duration::from_millis(exec_time_ms as u64),
341        queue_time: Duration::from_secs_f64(queue_time_s),
342        total_time: Duration::from_millis(exec_time_ms as u64)
343            + Duration::from_secs_f64(queue_time_s),
344        success_probability,
345        expected_fidelity: adjusted_fidelity,
346        resource_utilization: ResourceUtilizationPrediction {
347            cpu_utilization: 0.05,
348            memory_utilization: 0.10,
349            quantum_resource_utilization: cc.qubit_count as f64 / backend.max_qubits as f64,
350            network_utilization: 0.02,
351            storage_utilization: 0.01,
352        },
353        bottlenecks: Vec::new(),
354        confidence_interval: (success_probability * 0.92, success_probability * 1.05),
355    }
356}