1use crate::circuit::{Instruction, StandardGate};
26use crate::compile::CompilerError;
27use crate::device::{Device, PhysicalQubit, Topology};
28use std::collections::{BTreeMap, BTreeSet, VecDeque};
29
30type DirectedQubitPair = (PhysicalQubit, PhysicalQubit);
31type TwoQubitGateMap = BTreeMap<DirectedQubitPair, Vec<TwoQubitGateStatus>>;
32
33#[derive(Debug, Clone, Copy, Default, PartialEq)]
35pub(super) enum TwoQubitGateStatus {
36 #[default]
37 Unsupported,
38 Uncalibrated,
39 Calibrated(f64),
40}
41
42impl TwoQubitGateStatus {
43 pub(super) fn is_supported(self) -> bool {
44 !matches!(self, Self::Unsupported)
45 }
46
47 pub(super) fn error(self) -> Option<f64> {
48 match self {
49 Self::Calibrated(error) => Some(error),
50 Self::Unsupported | Self::Uncalibrated => None,
51 }
52 }
53}
54
55#[derive(Debug, Clone, PartialEq)]
63pub struct PhysicalLayoutGraph {
64 physical_qubits: Vec<PhysicalQubit>,
65 physical_index: BTreeMap<PhysicalQubit, usize>,
66 adjacency: Vec<Vec<usize>>,
67 distances: DistanceTable,
68 directed_couplings: BTreeSet<DirectedQubitPair>,
69 readout_errors: BTreeMap<PhysicalQubit, f64>,
70 two_qubit_gates: TwoQubitGateMap,
71 native_two_qubit_gates: Vec<bool>,
72 has_readout_error_data: bool,
73 has_native_two_qubit_capabilities: bool,
74 has_two_qubit_error_data: bool,
75}
76
77impl PhysicalLayoutGraph {
78 pub fn from_device(device: &Device) -> Result<Self, CompilerError> {
84 let physical_qubits: Vec<_> = device.usable_qubits().collect();
85 if physical_qubits.is_empty() {
86 return Err(CompilerError::InvalidInput(
87 "layout requires at least one usable physical qubit".to_string(),
88 ));
89 }
90
91 let usable: BTreeSet<_> = physical_qubits.iter().copied().collect();
92 let physical_index = build_physical_index(&physical_qubits);
93 let adjacency = build_undirected_adjacency(device.topology(), &physical_qubits, &usable);
94 let distances =
95 DistanceTable::from_adjacency(&physical_qubits, &physical_index, &adjacency);
96 let directed_couplings = collect_directed_couplings(device.topology(), &usable);
97 let (readout_errors, has_readout_error_data) =
98 collect_readout_errors(device, &physical_qubits)?;
99 let two_qubit_data = collect_two_qubit_data(device, &usable)?;
100
101 Ok(Self {
102 physical_qubits,
103 physical_index,
104 adjacency,
105 distances,
106 directed_couplings,
107 readout_errors,
108 two_qubit_gates: two_qubit_data.gates,
109 native_two_qubit_gates: two_qubit_data.native_gates,
110 has_readout_error_data,
111 has_native_two_qubit_capabilities: two_qubit_data.has_native_capabilities,
112 has_two_qubit_error_data: two_qubit_data.has_error_data,
113 })
114 }
115
116 pub fn physical_qubits(&self) -> &[PhysicalQubit] {
118 &self.physical_qubits
119 }
120
121 pub fn distances(&self) -> &DistanceTable {
123 &self.distances
124 }
125
126 pub(super) fn physical_index(&self, qubit: PhysicalQubit) -> Option<usize> {
127 self.physical_index.get(&qubit).copied()
128 }
129
130 pub(super) fn physical_at(&self, index: usize) -> Option<PhysicalQubit> {
131 self.physical_qubits.get(index).copied()
132 }
133
134 pub(super) fn distance_by_index(&self, a: usize, b: usize) -> Option<u32> {
135 self.distances.distance_by_index(a, b)
136 }
137
138 pub fn distance(&self, a: PhysicalQubit, b: PhysicalQubit) -> Option<u32> {
143 let a_index = self.physical_index(a)?;
144 let b_index = self.physical_index(b)?;
145 self.distance_by_index(a_index, b_index)
146 }
147
148 pub fn is_adjacent_undirected(&self, a: PhysicalQubit, b: PhysicalQubit) -> bool {
150 let Some(a_index) = self.physical_index(a) else {
151 return false;
152 };
153 let Some(b_index) = self.physical_index(b) else {
154 return false;
155 };
156 self.adjacency[a_index].contains(&b_index)
157 }
158
159 pub(super) fn undirected_edges_by_index(&self) -> impl Iterator<Item = (usize, usize)> + '_ {
162 self.adjacency
163 .iter()
164 .enumerate()
165 .flat_map(|(left, neighbors)| {
166 neighbors
167 .iter()
168 .copied()
169 .filter(move |right| left < *right)
170 .map(move |right| (left, right))
171 })
172 }
173
174 pub fn readout_error(&self, qubit: PhysicalQubit) -> Option<f64> {
176 self.readout_errors.get(&qubit).copied()
177 }
178
179 pub(super) fn readout_error_by_index(&self, index: usize) -> Option<f64> {
180 self.physical_at(index)
181 .and_then(|qubit| self.readout_error(qubit))
182 }
183
184 pub fn supports_two_qubit_gate_directed(
186 &self,
187 control: PhysicalQubit,
188 target: PhysicalQubit,
189 gate: StandardGate,
190 ) -> bool {
191 self.two_qubit_gate_status(control, target, gate)
192 .is_supported()
193 }
194
195 pub fn two_qubit_gate_error_directed(
201 &self,
202 control: PhysicalQubit,
203 target: PhysicalQubit,
204 gate: StandardGate,
205 ) -> Option<f64> {
206 self.two_qubit_gate_status(control, target, gate).error()
207 }
208
209 pub(super) fn two_qubit_gate_status(
210 &self,
211 control: PhysicalQubit,
212 target: PhysicalQubit,
213 gate: StandardGate,
214 ) -> TwoQubitGateStatus {
215 self.two_qubit_gates
216 .get(&(control, target))
217 .and_then(|gates| gates.get(gate as usize))
218 .copied()
219 .unwrap_or_default()
220 }
221
222 pub(super) fn two_qubit_gate_status_by_index(
223 &self,
224 control: usize,
225 target: usize,
226 gate: StandardGate,
227 ) -> TwoQubitGateStatus {
228 match (self.physical_at(control), self.physical_at(target)) {
229 (Some(control), Some(target)) => self.two_qubit_gate_status(control, target, gate),
230 _ => TwoQubitGateStatus::Unsupported,
231 }
232 }
233
234 pub(super) fn is_two_qubit_gate_native_anywhere(&self, gate: StandardGate) -> bool {
235 self.native_two_qubit_gates
236 .get(gate as usize)
237 .copied()
238 .unwrap_or(false)
239 }
240
241 pub fn supports_directed_coupling(
243 &self,
244 control: PhysicalQubit,
245 target: PhysicalQubit,
246 ) -> bool {
247 self.directed_couplings.contains(&(control, target))
248 }
249
250 pub(super) fn supports_directed_coupling_by_index(
251 &self,
252 control: usize,
253 target: usize,
254 ) -> bool {
255 match (self.physical_at(control), self.physical_at(target)) {
256 (Some(control), Some(target)) => self.supports_directed_coupling(control, target),
257 _ => false,
258 }
259 }
260
261 pub fn has_fidelity_data(&self) -> bool {
263 self.has_readout_error_data || self.has_two_qubit_error_data
264 }
265
266 pub fn has_readout_error_data(&self) -> bool {
268 self.has_readout_error_data
269 }
270
271 pub(crate) fn has_native_two_qubit_capabilities(&self) -> bool {
273 self.has_native_two_qubit_capabilities
274 }
275
276 pub fn has_two_qubit_error_data(&self) -> bool {
278 self.has_two_qubit_error_data
279 }
280}
281
282#[derive(Debug, Clone, PartialEq)]
287pub struct DistanceTable {
288 qubits: Vec<PhysicalQubit>,
289 index: BTreeMap<PhysicalQubit, usize>,
290 distances: Vec<Vec<Option<u32>>>,
291}
292
293impl DistanceTable {
294 fn from_adjacency(
296 qubits: &[PhysicalQubit],
297 index: &BTreeMap<PhysicalQubit, usize>,
298 adjacency: &[Vec<usize>],
299 ) -> Self {
300 let mut distances = vec![vec![None; qubits.len()]; qubits.len()];
301
302 for (start_index, item) in distances.iter_mut().enumerate().take(qubits.len()) {
303 let mut queue = VecDeque::new();
304 item[start_index] = Some(0);
305 queue.push_back(start_index);
306
307 while let Some(current_index) = queue.pop_front() {
308 let current_distance =
309 item[current_index].expect("queued nodes have assigned distances");
310
311 for neighbor_index in adjacency[current_index].iter().copied() {
312 if item[neighbor_index].is_none() {
313 item[neighbor_index] = Some(current_distance + 1);
314 queue.push_back(neighbor_index);
315 }
316 }
317 }
318 }
319
320 Self {
321 qubits: qubits.to_vec(),
322 index: index.clone(),
323 distances,
324 }
325 }
326
327 pub fn qubits(&self) -> &[PhysicalQubit] {
329 &self.qubits
330 }
331
332 pub fn distance(&self, a: PhysicalQubit, b: PhysicalQubit) -> Option<u32> {
337 let a_index = self.index.get(&a)?;
338 let b_index = self.index.get(&b)?;
339 self.distance_by_index(*a_index, *b_index)
340 }
341
342 pub(super) fn distance_by_index(&self, a: usize, b: usize) -> Option<u32> {
343 self.distances.get(a)?.get(b).copied().flatten()
344 }
345}
346
347fn build_physical_index(physical_qubits: &[PhysicalQubit]) -> BTreeMap<PhysicalQubit, usize> {
348 physical_qubits
349 .iter()
350 .copied()
351 .enumerate()
352 .map(|(index, qubit)| (qubit, index))
353 .collect()
354}
355
356fn build_undirected_adjacency(
357 topology: &Topology,
358 physical_qubits: &[PhysicalQubit],
359 usable: &BTreeSet<PhysicalQubit>,
360) -> Vec<Vec<usize>> {
361 let index = build_physical_index(physical_qubits);
362 let mut adjacency = vec![Vec::new(); physical_qubits.len()];
363
364 for qubit in physical_qubits {
365 let qubit_index = index[qubit];
366 for neighbor in topology.successors(*qubit) {
367 if usable.contains(&neighbor) {
368 adjacency[qubit_index].push(index[&neighbor]);
369 }
370 }
371 for neighbor in topology.predecessors(*qubit) {
372 if usable.contains(&neighbor) {
373 adjacency[qubit_index].push(index[&neighbor]);
374 }
375 }
376 }
377
378 for neighbors in &mut adjacency {
379 neighbors.sort_unstable_by_key(|index| physical_qubits[*index]);
380 neighbors.dedup();
381 }
382
383 adjacency
384}
385
386fn collect_readout_errors(
387 device: &Device,
388 physical_qubits: &[PhysicalQubit],
389) -> Result<(BTreeMap<PhysicalQubit, f64>, bool), CompilerError> {
390 let mut errors = BTreeMap::new();
391 for qubit in physical_qubits {
392 if let Some(error) = device.get_readout_error(*qubit) {
393 validate_probability(error, "readout error")?;
394 errors.insert(*qubit, error);
395 }
396 }
397 let has_data = !errors.is_empty();
398 Ok((errors, has_data))
399}
400
401fn collect_directed_couplings(
402 topology: &Topology,
403 usable: &BTreeSet<PhysicalQubit>,
404) -> BTreeSet<DirectedQubitPair> {
405 let mut couplings = BTreeSet::new();
406 for control in usable {
407 for target in topology.successors(*control) {
408 if usable.contains(&target) {
409 couplings.insert((*control, target));
410 }
411 }
412 }
413 couplings
414}
415
416struct TwoQubitData {
417 gates: TwoQubitGateMap,
418 native_gates: Vec<bool>,
419 has_native_capabilities: bool,
420 has_error_data: bool,
421}
422
423fn collect_two_qubit_data(
424 device: &Device,
425 usable: &BTreeSet<PhysicalQubit>,
426) -> Result<TwoQubitData, CompilerError> {
427 if let Some(error) = device.default_two_qubit_error() {
428 validate_probability(error, "default two-qubit error")?;
429 }
430
431 let mut gates = BTreeMap::new();
432 let mut native_gates = vec![false; StandardGate::all().len()];
433 let mut has_native_capabilities = false;
434 let mut has_error_data = false;
435 for control in usable {
436 for target in device.topology().successors(*control) {
437 if !usable.contains(&target) {
438 continue;
439 }
440
441 let edge = device.edge_properties(*control, target);
442 if let Some(edge) = edge {
443 for property in edge.native_instructions() {
444 validate_probability(property.error_rate(), "edge two-qubit error")?;
445 }
446 }
447
448 let mut edge_gates = None;
449 for gate in StandardGate::all()
450 .iter()
451 .copied()
452 .filter(|gate| gate.num_qubits() == 2)
453 {
454 let instruction = Instruction::Standard(gate);
455 if !device.supports_native_instruction(&instruction, &[*control, target]) {
456 continue;
457 }
458
459 has_native_capabilities = true;
460 native_gates[gate as usize] = true;
461 let status = match device.two_qubit_error(*control, target, &instruction) {
462 Some(error) => {
463 validate_probability(error, "gate-specific two-qubit error")?;
464 has_error_data = true;
465 TwoQubitGateStatus::Calibrated(error)
466 }
467 None => TwoQubitGateStatus::Uncalibrated,
468 };
469 edge_gates.get_or_insert_with(|| {
470 vec![TwoQubitGateStatus::Unsupported; StandardGate::all().len()]
471 })[gate as usize] = status;
472 }
473 if let Some(edge_gates) = edge_gates {
474 gates.insert((*control, target), edge_gates);
475 }
476 }
477 }
478
479 Ok(TwoQubitData {
480 gates,
481 native_gates,
482 has_native_capabilities,
483 has_error_data,
484 })
485}
486
487fn validate_probability(value: f64, name: &str) -> Result<(), CompilerError> {
488 if value.is_finite() && (0.0..=1.0).contains(&value) {
489 Ok(())
490 } else {
491 Err(CompilerError::InvalidInput(format!(
492 "device {} must be a finite probability in [0, 1], got {}",
493 name, value
494 )))
495 }
496}