1use crate::circuit::{Instruction, Operation, Qubit, ValueInstruction, ValueOperation};
16use crate::compile::device_planning::cost::{
17 DevicePhysicalCost, MetricAvailability, NativePlanLeaf, RobustDurationKey, RobustErrorKey,
18};
19use smallvec::SmallVec;
20use std::cmp::Ordering;
21use std::collections::HashMap;
22
23const ERROR_LOG_TOLERANCE: f64 = 1e-12;
28
29#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
31pub enum NativeQualityPolicy {
32 #[default]
34 EntanglerFirst,
35 BalancedDepth,
37}
38
39#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
40pub(crate) struct NativeCriticalPathQuality {
41 pub(crate) one_qubit_ops: u32,
43 pub(crate) longest_one_qubit_run: u32,
45}
46
47#[derive(Debug, Clone, Copy, PartialEq)]
48pub(crate) struct NativeQualityVector {
49 pub(crate) physical: DevicePhysicalCost,
50 pub(crate) critical_path: NativeCriticalPathQuality,
51}
52
53#[derive(Debug, Clone, Copy, PartialEq, Eq)]
54pub(crate) enum NativeQualityViolation {
55 ScopeShape,
56 TwoQubitOps,
57 TwoQubitDepth,
58 TotalDepth,
59 Error,
60 Makespan,
61 Rank,
62}
63
64impl NativeQualityVector {
65 pub(crate) fn for_operations(physical: DevicePhysicalCost, operations: &[Operation]) -> Self {
66 let mut path = CriticalPathBuilder::default();
67 for operation in operations {
68 if matches!(
69 operation.instruction,
70 Instruction::Standard(_) | Instruction::McGate(_)
71 ) {
72 path.add_gate(&operation.qubits);
73 }
74 }
75 Self {
76 physical,
77 critical_path: path.finish(),
78 }
79 }
80
81 pub(crate) fn for_value_operations(
82 physical: DevicePhysicalCost,
83 operations: &[ValueOperation],
84 ) -> Self {
85 let mut path = CriticalPathBuilder::default();
86 for operation in operations {
87 if matches!(operation.instruction, ValueInstruction::Instruction(_)) {
88 path.add_gate(&operation.qubits);
89 }
90 }
91 Self {
92 physical,
93 critical_path: path.finish(),
94 }
95 }
96
97 pub(crate) fn for_native_plan_leaves(
101 physical: DevicePhysicalCost,
102 leaves: &[NativePlanLeaf],
103 ) -> Self {
104 let mut path = CriticalPathBuilder::default();
105 for leaf in leaves {
106 let qubits = leaf
107 .ordered_qargs
108 .iter()
109 .copied()
110 .map(Qubit::from)
111 .collect::<SmallVec<[Qubit; 2]>>();
112 path.add_gate(&qubits);
113 }
114 Self {
115 physical,
116 critical_path: path.finish(),
117 }
118 }
119
120 pub(crate) fn admissible_against(self, entry: Self, policy: NativeQualityPolicy) -> bool {
122 self.admissibility_violation_against(entry, policy)
123 .is_none()
124 }
125
126 pub(crate) fn admissibility_violation_against(
128 self,
129 entry: Self,
130 policy: NativeQualityPolicy,
131 ) -> Option<NativeQualityViolation> {
132 match policy {
133 NativeQualityPolicy::EntanglerFirst => None,
134 NativeQualityPolicy::BalancedDepth => {
135 if self.physical.native_two_qubit_ops > entry.physical.native_two_qubit_ops {
136 return Some(NativeQualityViolation::TwoQubitOps);
137 }
138 if self.physical.native_two_qubit_depth > entry.physical.native_two_qubit_depth {
139 return Some(NativeQualityViolation::TwoQubitDepth);
140 }
141 if self.physical.total_native_depth > entry.physical.total_native_depth {
142 return Some(NativeQualityViolation::TotalDepth);
143 }
144 if !error_is_no_worse(self.physical.error, entry.physical.error) {
145 return Some(NativeQualityViolation::Error);
146 }
147 if !makespan_is_no_worse(self.physical.makespan, entry.physical.makespan) {
148 return Some(NativeQualityViolation::Makespan);
149 }
150 None
151 }
152 }
153 }
154
155 pub(crate) fn compare(self, other: Self, policy: NativeQualityPolicy) -> Ordering {
157 match policy {
158 NativeQualityPolicy::EntanglerFirst => self.physical.compare(other.physical),
159 NativeQualityPolicy::BalancedDepth => self
160 .physical
161 .total_native_depth
162 .cmp(&other.physical.total_native_depth)
163 .then_with(|| {
164 self.physical
165 .makespan
166 .compare_by(other.physical.makespan, |left, right| {
167 left.total_cmp(&right)
168 })
169 })
170 .then_with(|| {
171 self.critical_path
172 .one_qubit_ops
173 .cmp(&other.critical_path.one_qubit_ops)
174 })
175 .then_with(|| {
176 self.critical_path
177 .longest_one_qubit_run
178 .cmp(&other.critical_path.longest_one_qubit_run)
179 })
180 .then_with(|| {
181 self.physical
182 .native_two_qubit_depth
183 .cmp(&other.physical.native_two_qubit_depth)
184 })
185 .then_with(|| {
186 self.physical
187 .native_two_qubit_ops
188 .cmp(&other.physical.native_two_qubit_ops)
189 })
190 .then_with(|| {
191 self.physical
192 .native_total_ops
193 .cmp(&other.physical.native_total_ops)
194 })
195 .then_with(|| {
196 self.physical
197 .error
198 .compare_by(other.physical.error, |left, right| left.compare(right))
199 })
200 .then_with(|| {
201 self.physical
202 .duration
203 .compare_by(other.physical.duration, |left, right| left.compare(right))
204 }),
205 }
206 }
207
208 pub(crate) fn exact_pareto_dominance(self, other: Self) -> Option<bool> {
217 let mut strict = false;
218 dominance_component(
219 self.physical.native_two_qubit_ops,
220 other.physical.native_two_qubit_ops,
221 &mut strict,
222 )?;
223 dominance_component(
224 self.physical.native_two_qubit_depth,
225 other.physical.native_two_qubit_depth,
226 &mut strict,
227 )?;
228 dominance_component(
229 self.physical.total_native_depth,
230 other.physical.total_native_depth,
231 &mut strict,
232 )?;
233 dominance_component(
234 self.physical.native_total_ops,
235 other.physical.native_total_ops,
236 &mut strict,
237 )?;
238 strict |= error_dominance(self.physical.error, other.physical.error)?;
239 strict |= duration_dominance(self.physical.duration, other.physical.duration)?;
240 strict |= scalar_availability_dominance(self.physical.makespan, other.physical.makespan)?;
241 dominance_component(
242 self.critical_path.one_qubit_ops,
243 other.critical_path.one_qubit_ops,
244 &mut strict,
245 )?;
246 dominance_component(
247 self.critical_path.longest_one_qubit_run,
248 other.critical_path.longest_one_qubit_run,
249 &mut strict,
250 )?;
251 Some(strict)
252 }
253}
254
255fn dominance_component<T: Ord>(candidate: T, incumbent: T, strict: &mut bool) -> Option<()> {
256 match candidate.cmp(&incumbent) {
257 Ordering::Less => *strict = true,
258 Ordering::Equal => {}
259 Ordering::Greater => return None,
260 }
261 Some(())
262}
263
264fn error_dominance(
265 candidate: MetricAvailability<RobustErrorKey>,
266 incumbent: MetricAvailability<RobustErrorKey>,
267) -> Option<bool> {
268 match (candidate, incumbent) {
269 (MetricAvailability::Disabled, MetricAvailability::Disabled)
270 | (MetricAvailability::Inconsistent, MetricAvailability::Inconsistent) => Some(false),
271 (MetricAvailability::Available(candidate), MetricAvailability::Available(incumbent)) => {
272 if !error_is_no_worse(
273 MetricAvailability::Available(candidate),
274 MetricAvailability::Available(incumbent),
275 ) {
276 return None;
277 }
278 Some(
279 candidate.unavailable_count < incumbent.unavailable_count
280 || candidate.imputed_count < incumbent.imputed_count
281 || candidate.log_error + ERROR_LOG_TOLERANCE < incumbent.log_error,
282 )
283 }
284 _ => None,
285 }
286}
287
288fn duration_dominance(
289 candidate: MetricAvailability<RobustDurationKey>,
290 incumbent: MetricAvailability<RobustDurationKey>,
291) -> Option<bool> {
292 match (candidate, incumbent) {
293 (MetricAvailability::Disabled, MetricAvailability::Disabled)
294 | (MetricAvailability::Inconsistent, MetricAvailability::Inconsistent) => Some(false),
295 (MetricAvailability::Available(candidate), MetricAvailability::Available(incumbent)) => {
296 if candidate.unavailable_count > incumbent.unavailable_count
297 || candidate.imputed_count > incumbent.imputed_count
298 || candidate
299 .duration_work
300 .total_cmp(&incumbent.duration_work)
301 .is_gt()
302 {
303 return None;
304 }
305 Some(
306 candidate.unavailable_count < incumbent.unavailable_count
307 || candidate.imputed_count < incumbent.imputed_count
308 || candidate
309 .duration_work
310 .total_cmp(&incumbent.duration_work)
311 .is_lt(),
312 )
313 }
314 _ => None,
315 }
316}
317
318fn scalar_availability_dominance(
319 candidate: MetricAvailability<f64>,
320 incumbent: MetricAvailability<f64>,
321) -> Option<bool> {
322 match (candidate, incumbent) {
323 (MetricAvailability::Disabled, MetricAvailability::Disabled)
324 | (MetricAvailability::Inconsistent, MetricAvailability::Inconsistent) => Some(false),
325 (MetricAvailability::Available(candidate), MetricAvailability::Available(incumbent)) => {
326 match candidate.total_cmp(&incumbent) {
327 Ordering::Less => Some(true),
328 Ordering::Equal => Some(false),
329 Ordering::Greater => None,
330 }
331 }
332 _ => None,
333 }
334}
335
336fn error_is_no_worse(
337 candidate: MetricAvailability<RobustErrorKey>,
338 entry: MetricAvailability<RobustErrorKey>,
339) -> bool {
340 match (candidate, entry) {
341 (MetricAvailability::Disabled, MetricAvailability::Disabled)
342 | (MetricAvailability::Inconsistent, MetricAvailability::Inconsistent) => true,
343 (MetricAvailability::Available(candidate), MetricAvailability::Available(entry)) => {
344 candidate.unavailable_count <= entry.unavailable_count
345 && candidate.imputed_count <= entry.imputed_count
346 && (candidate.log_error <= entry.log_error
347 || candidate.log_error - entry.log_error <= ERROR_LOG_TOLERANCE)
348 }
349 _ => false,
350 }
351}
352
353fn makespan_is_no_worse(
354 candidate: MetricAvailability<f64>,
355 entry: MetricAvailability<f64>,
356) -> bool {
357 match (candidate, entry) {
358 (MetricAvailability::Disabled, MetricAvailability::Disabled)
359 | (MetricAvailability::Inconsistent, MetricAvailability::Inconsistent) => true,
360 (MetricAvailability::Available(candidate), MetricAvailability::Available(entry)) => {
361 candidate <= entry
362 }
363 _ => false,
364 }
365}
366
367#[derive(Debug)]
368struct PathNode {
369 one_qubit: bool,
370 predecessors: SmallVec<[usize; 3]>,
371 successors: SmallVec<[usize; 3]>,
372 forward_depth: u32,
373 backward_depth: u32,
374}
375
376#[derive(Default)]
377pub(super) struct CriticalPathBuilder {
378 nodes: Vec<PathNode>,
379 last_on_qubit: HashMap<Qubit, usize>,
380}
381
382impl CriticalPathBuilder {
383 pub(super) fn add_gate(&mut self, qubits: &[Qubit]) {
384 if qubits.is_empty() {
385 return;
386 }
387 let mut predecessors = SmallVec::<[usize; 3]>::new();
388 for qubit in qubits {
389 if let Some(predecessor) = self.last_on_qubit.get(qubit).copied()
390 && !predecessors.contains(&predecessor)
391 {
392 predecessors.push(predecessor);
393 }
394 }
395 let forward_depth = predecessors
396 .iter()
397 .map(|predecessor| self.nodes[*predecessor].forward_depth)
398 .max()
399 .unwrap_or(0)
400 .saturating_add(1);
401 let index = self.nodes.len();
402 for predecessor in &predecessors {
403 self.nodes[*predecessor].successors.push(index);
404 }
405 self.nodes.push(PathNode {
406 one_qubit: qubits.len() == 1,
407 predecessors,
408 successors: SmallVec::new(),
409 forward_depth,
410 backward_depth: 1,
411 });
412 for qubit in qubits {
413 self.last_on_qubit.insert(*qubit, index);
414 }
415 }
416
417 pub(super) fn finish(mut self) -> NativeCriticalPathQuality {
418 let total_depth = self
419 .nodes
420 .iter()
421 .map(|node| node.forward_depth)
422 .max()
423 .unwrap_or(0);
424 for index in (0..self.nodes.len()).rev() {
425 self.nodes[index].backward_depth = self.nodes[index]
426 .successors
427 .iter()
428 .map(|successor| self.nodes[*successor].backward_depth)
429 .max()
430 .unwrap_or(0)
431 .saturating_add(1);
432 }
433
434 let mut one_qubit_counts = vec![0_u32; self.nodes.len()];
435 let mut current_one_qubit_runs = vec![0_u32; self.nodes.len()];
436 let mut longest_one_qubit_run = 0_u32;
437 for index in 0..self.nodes.len() {
438 if !is_critical(&self.nodes[index], total_depth) {
439 continue;
440 }
441 let tight_predecessors =
442 self.nodes[index]
443 .predecessors
444 .iter()
445 .copied()
446 .filter(|predecessor| {
447 is_critical(&self.nodes[*predecessor], total_depth)
448 && self.nodes[*predecessor].forward_depth.saturating_add(1)
449 == self.nodes[index].forward_depth
450 });
451 let mut best_count = 0_u32;
452 let mut best_run = 0_u32;
453 for predecessor in tight_predecessors {
454 best_count = best_count.max(one_qubit_counts[predecessor]);
455 if self.nodes[predecessor].one_qubit {
456 best_run = best_run.max(current_one_qubit_runs[predecessor]);
457 }
458 }
459 if self.nodes[index].one_qubit {
460 one_qubit_counts[index] = best_count.saturating_add(1);
461 current_one_qubit_runs[index] = best_run.saturating_add(1);
462 longest_one_qubit_run = longest_one_qubit_run.max(current_one_qubit_runs[index]);
463 } else {
464 one_qubit_counts[index] = best_count;
465 }
466 }
467
468 let one_qubit_ops = self
469 .nodes
470 .iter()
471 .enumerate()
472 .filter(|(_, node)| node.forward_depth == total_depth)
473 .map(|(index, _)| one_qubit_counts[index])
474 .max()
475 .unwrap_or(0);
476 NativeCriticalPathQuality {
477 one_qubit_ops,
478 longest_one_qubit_run,
479 }
480 }
481}
482
483fn is_critical(node: &PathNode, total_depth: u32) -> bool {
484 node.forward_depth
485 .saturating_add(node.backward_depth)
486 .saturating_sub(1)
487 == total_depth
488}