use crate::circuit::{
Circuit, ClassicalControlOp, Directive, Instruction, Operation, Qubit, StandardGate,
ValueClassicalControlOp, ValueInstruction, ValueOperation,
};
use crate::device::topology::Topology;
use crate::device::{DeviceError, DeviceValidationError, PhysicalQubit};
use std::collections::{BTreeSet, HashMap, HashSet};
use std::ops::RangeInclusive;
use time::OffsetDateTime;
fn validate_native_gate_arity(
instruction: &Instruction,
expected: RangeInclusive<usize>,
) -> Result<(), DeviceError> {
let Instruction::Standard(gate) = instruction else {
return Err(DeviceError::NonStandardNativeInstruction {
instruction: instruction.to_string(),
});
};
let actual = gate.num_qubits();
if expected.contains(&actual) {
Ok(())
} else {
Err(DeviceError::InvalidNativeInstructionArity {
instruction: instruction.to_string(),
expected,
actual,
})
}
}
#[derive(Debug, Clone)]
pub struct InstructionProp {
instruction: Instruction,
error_rate: f64,
length: Option<f64>,
}
impl InstructionProp {
pub fn new(instruction: Instruction, error_rate: f64) -> Self {
Self {
instruction,
error_rate,
length: None,
}
}
pub fn with_length(mut self, length: f64) -> Self {
self.length = Some(length);
self
}
pub fn set_length(&mut self, length: f64) {
self.length = Some(length);
}
pub fn with_instruction(mut self, instruction: Instruction) -> Self {
self.instruction = instruction;
self
}
pub fn set_instruction(&mut self, instruction: Instruction) {
self.instruction = instruction;
}
pub fn with_error_rate(mut self, error_rate: f64) -> Self {
self.error_rate = error_rate;
self
}
pub fn set_error_rate(&mut self, error_rate: f64) {
self.error_rate = error_rate;
}
pub fn instruction(&self) -> &Instruction {
&self.instruction
}
pub fn error_rate(&self) -> f64 {
self.error_rate
}
pub fn length(&self) -> Option<f64> {
self.length
}
fn validate(&self) -> Result<(), DeviceError> {
if !(self.error_rate.is_finite() && (0.0..=1.0).contains(&self.error_rate)) {
return Err(DeviceError::InvalidNativeInstructionErrorRate {
instruction: self.instruction.to_string(),
value: format!("{:?}", self.error_rate),
});
}
if let Some(length) = self.length
&& !(length.is_finite() && length >= 0.0)
{
return Err(DeviceError::InvalidNativeInstructionDuration {
instruction: self.instruction.to_string(),
value: format!("{length:?}"),
});
}
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct QubitProp {
readout_error: f64,
prob_meas0_prep1: Option<f64>,
prob_meas1_prep0: Option<f64>,
t1: Option<f64>,
t2: Option<f64>,
frequency: Option<f64>,
native_instructions: Vec<InstructionProp>,
}
impl QubitProp {
pub fn new(readout_error: f64) -> Self {
Self {
readout_error,
prob_meas0_prep1: None,
prob_meas1_prep0: None,
t1: None,
t2: None,
frequency: None,
native_instructions: Vec::new(),
}
}
pub fn with_prob_meas0_prep1(mut self, prob: f64) -> Self {
self.prob_meas0_prep1 = Some(prob);
self
}
pub fn set_prob_meas0_prep1(&mut self, prob: f64) {
self.prob_meas0_prep1 = Some(prob);
}
pub fn with_prob_meas1_prep0(mut self, prob: f64) -> Self {
self.prob_meas1_prep0 = Some(prob);
self
}
pub fn set_prob_meas1_prep0(&mut self, prob: f64) {
self.prob_meas1_prep0 = Some(prob);
}
pub fn with_t1(mut self, t1: f64) -> Self {
self.t1 = Some(t1);
self
}
pub fn set_t1(&mut self, t1: f64) {
self.t1 = Some(t1);
}
pub fn with_t2(mut self, t2: f64) -> Self {
self.t2 = Some(t2);
self
}
pub fn set_t2(&mut self, t2: f64) {
self.t2 = Some(t2);
}
pub fn with_frequency(mut self, frequency: f64) -> Self {
self.frequency = Some(frequency);
self
}
pub fn set_frequency(&mut self, frequency: f64) {
self.frequency = Some(frequency);
}
pub fn with_native_instruction(mut self, prop: InstructionProp) -> Result<Self, DeviceError> {
self.set_native_instruction(prop)?;
Ok(self)
}
pub fn set_native_instruction(&mut self, prop: InstructionProp) -> Result<(), DeviceError> {
validate_native_gate_arity(prop.instruction(), 1..=1)?;
prop.validate()?;
self.native_instructions.push(prop);
Ok(())
}
pub fn readout_error(&self) -> f64 {
self.readout_error
}
pub fn prob_meas0_prep1(&self) -> Option<f64> {
self.prob_meas0_prep1
}
pub fn prob_meas1_prep0(&self) -> Option<f64> {
self.prob_meas1_prep0
}
pub fn t1(&self) -> Option<f64> {
self.t1
}
pub fn t2(&self) -> Option<f64> {
self.t2
}
pub fn frequency(&self) -> Option<f64> {
self.frequency
}
pub fn native_instructions(&self) -> &[InstructionProp] {
&self.native_instructions
}
}
#[derive(Debug, Clone)]
pub struct EdgeProp {
native_instructions: Vec<InstructionProp>,
}
enum NativeInstructionSupport<'a> {
Explicit(&'a InstructionProp),
Inherited,
Unsupported,
}
#[derive(Debug, Clone, Copy, Default, PartialEq)]
pub(crate) struct NativeInstructionCalibration {
pub(crate) error_rate: Option<f64>,
pub(crate) duration: Option<f64>,
}
impl EdgeProp {
pub fn new() -> Self {
Self {
native_instructions: Vec::new(),
}
}
pub fn with_native_instruction(mut self, prop: InstructionProp) -> Result<Self, DeviceError> {
self.set_native_instruction(prop)?;
Ok(self)
}
pub fn set_native_instruction(&mut self, prop: InstructionProp) -> Result<(), DeviceError> {
validate_native_gate_arity(prop.instruction(), 2..=2)?;
prop.validate()?;
self.native_instructions.push(prop);
Ok(())
}
pub fn native_instructions(&self) -> &[InstructionProp] {
&self.native_instructions
}
}
impl Default for EdgeProp {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct Device {
name: String,
qubits: BTreeSet<PhysicalQubit>,
invalid_qubits: BTreeSet<PhysicalQubit>,
topology: Topology,
native_gates: Vec<Instruction>,
calibration_time: Option<OffsetDateTime>,
default_t1: Option<f64>,
default_t2: Option<f64>,
default_readout_error: Option<f64>,
default_single_qubit_error: Option<f64>,
default_two_qubit_error: Option<f64>,
qubit_properties: HashMap<PhysicalQubit, QubitProp>,
edge_properties: HashMap<(PhysicalQubit, PhysicalQubit), EdgeProp>,
}
impl Device {
pub fn new(
name: impl Into<String>,
qubits: HashSet<PhysicalQubit>,
topology: Topology,
) -> Result<Self, DeviceError> {
for q in topology.qubits() {
if !qubits.contains(&q) {
return Err(DeviceError::InvalidOnlineQubit(q));
}
}
Ok(Self {
name: name.into(),
qubits: qubits.into_iter().collect(),
invalid_qubits: BTreeSet::new(),
topology,
native_gates: Vec::new(),
calibration_time: None,
default_t1: None,
default_t2: None,
default_readout_error: None,
default_single_qubit_error: None,
default_two_qubit_error: None,
qubit_properties: HashMap::new(),
edge_properties: HashMap::new(),
})
}
pub fn line(name: impl Into<String>, num_qubits: u32) -> Result<Self, DeviceError> {
let physical_qubits = (0..num_qubits).map(PhysicalQubit::new).collect::<Vec<_>>();
Self::line_from_qubits(name, physical_qubits)
}
pub fn line_from_qubits(
name: impl Into<String>,
physical_qubits: Vec<PhysicalQubit>,
) -> Result<Self, DeviceError> {
if physical_qubits.is_empty() {
return Err(DeviceError::EmptyDevice);
}
let qubits = physical_qubits.iter().copied().collect::<HashSet<_>>();
let topology = Topology::line(physical_qubits).map_err(DeviceError::InvalidTopology)?;
Self::new(name, qubits, topology)
}
pub fn bidirectional_line(
name: impl Into<String>,
num_qubits: u32,
) -> Result<Self, DeviceError> {
let edges = (0..num_qubits.saturating_sub(1))
.flat_map(|index| [(index, index + 1), (index + 1, index)])
.collect::<Vec<_>>();
Self::from_u32_edges(name, num_qubits, &edges)
}
pub fn ring(name: impl Into<String>, num_qubits: u32) -> Result<Self, DeviceError> {
let mut edges = BTreeSet::new();
if num_qubits >= 2 {
for index in 0..num_qubits {
let next = (index + 1) % num_qubits;
edges.insert((index, next));
edges.insert((next, index));
}
}
let edges = edges.into_iter().collect::<Vec<_>>();
Self::from_u32_edges(name, num_qubits, &edges)
}
pub fn star(
name: impl Into<String>,
num_qubits: u32,
center: u32,
) -> Result<Self, DeviceError> {
let edges = (0..num_qubits)
.filter(|&index| index != center)
.flat_map(|index| [(center, index), (index, center)])
.collect::<Vec<_>>();
Self::from_u32_edges(name, num_qubits, &edges)
}
pub fn grid(name: impl Into<String>, rows: u32, cols: u32) -> Result<Self, DeviceError> {
let num_qubits = rows.saturating_mul(cols);
let mut edges = Vec::new();
for row in 0..rows {
for col in 0..cols {
let current = row * cols + col;
if col + 1 < cols {
edges.push((current, current + 1));
edges.push((current + 1, current));
}
if row + 1 < rows {
edges.push((current, current + cols));
edges.push((current + cols, current));
}
}
}
Self::from_u32_edges(name, num_qubits, &edges)
}
pub fn from_edges(
name: impl Into<String>,
num_qubits: u32,
edges: &[(u32, u32)],
) -> Result<Self, DeviceError> {
Self::from_u32_edges(name, num_qubits, edges)
}
fn from_u32_edges(
name: impl Into<String>,
num_qubits: u32,
edges: &[(u32, u32)],
) -> Result<Self, DeviceError> {
if num_qubits == 0 {
return Err(DeviceError::EmptyDevice);
}
let physical_qubits = (0..num_qubits).map(PhysicalQubit::new).collect::<Vec<_>>();
let qubits = physical_qubits.iter().copied().collect::<HashSet<_>>();
let coupling_map = edges
.iter()
.enumerate()
.map(|(index, &(control, target))| {
(
PhysicalQubit::new(control),
PhysicalQubit::new(target),
format!("e{index}"),
)
})
.collect::<Vec<_>>();
let topology =
Topology::new(physical_qubits, coupling_map).map_err(DeviceError::InvalidTopology)?;
Self::new(name, qubits, topology)
}
pub fn with_invalid_qubits(
mut self,
invalid_qubits: HashSet<PhysicalQubit>,
) -> Result<Self, DeviceError> {
self.set_invalid_qubits(invalid_qubits)?;
Ok(self)
}
pub fn set_invalid_qubits(
&mut self,
invalid_qubits: HashSet<PhysicalQubit>,
) -> Result<(), DeviceError> {
for &qubit in &invalid_qubits {
if !self.qubits.contains(&qubit) {
return Err(DeviceError::QubitNotInDevice(qubit));
}
}
self.invalid_qubits = invalid_qubits.into_iter().collect();
Ok(())
}
pub fn with_native_gates(mut self, gates: Vec<Instruction>) -> Result<Self, DeviceError> {
self.set_native_gates(gates)?;
Ok(self)
}
pub fn set_native_gates(&mut self, gates: Vec<Instruction>) -> Result<(), DeviceError> {
for gate in &gates {
validate_native_gate_arity(gate, 0..=2)?;
}
self.native_gates = gates;
Ok(())
}
pub fn with_calibration_time(mut self, time: OffsetDateTime) -> Self {
self.calibration_time = Some(time);
self
}
pub fn set_calibration_time(&mut self, time: OffsetDateTime) {
self.calibration_time = Some(time);
}
pub fn with_default_t1(mut self, t1: f64) -> Self {
self.default_t1 = Some(t1);
self
}
pub fn set_default_t1(&mut self, t1: f64) {
self.default_t1 = Some(t1);
}
pub fn with_default_t2(mut self, t2: f64) -> Self {
self.default_t2 = Some(t2);
self
}
pub fn set_default_t2(&mut self, t2: f64) {
self.default_t2 = Some(t2);
}
pub fn with_default_readout_error(mut self, error: f64) -> Self {
self.default_readout_error = Some(error);
self
}
pub fn set_default_readout_error(&mut self, error: f64) {
self.default_readout_error = Some(error);
}
pub fn with_default_single_qubit_error(mut self, error: f64) -> Self {
self.default_single_qubit_error = Some(error);
self
}
pub fn set_default_single_qubit_error(&mut self, error: f64) {
self.default_single_qubit_error = Some(error);
}
pub fn with_default_two_qubit_error(mut self, error: f64) -> Self {
self.default_two_qubit_error = Some(error);
self
}
pub fn set_default_two_qubit_error(&mut self, error: f64) {
self.default_two_qubit_error = Some(error);
}
pub fn add_qubit_properties(
&mut self,
qubit: PhysicalQubit,
props: QubitProp,
) -> Result<(), DeviceError> {
if !self.qubits.contains(&qubit) {
return Err(DeviceError::QubitNotInDevice(qubit));
}
if !self.topology.contains_qubit(&qubit) {
return Err(DeviceError::QubitNotInTopology(qubit));
}
self.qubit_properties.insert(qubit, props);
Ok(())
}
pub fn add_edge_properties(
&mut self,
control: PhysicalQubit,
target: PhysicalQubit,
props: EdgeProp,
) -> Result<(), DeviceError> {
if !self.topology.supports_directed_coupling(control, target) {
return Err(DeviceError::EdgeNotInTopology(control, target));
}
self.edge_properties.insert((control, target), props);
Ok(())
}
fn instruction_matches(stored: &Instruction, requested: &Instruction) -> bool {
match (stored, requested) {
(Instruction::Standard(stored), Instruction::Standard(requested)) => {
stored == requested
}
_ => false,
}
}
fn resolve_native_instruction<'a>(
&'a self,
local: Option<&'a [InstructionProp]>,
instruction: &Instruction,
) -> NativeInstructionSupport<'a> {
if let Some(local) = local.filter(|instructions| !instructions.is_empty()) {
return local
.iter()
.find(|prop| Self::instruction_matches(prop.instruction(), instruction))
.map_or(
NativeInstructionSupport::Unsupported,
NativeInstructionSupport::Explicit,
);
}
if self
.native_gates
.iter()
.any(|stored| Self::instruction_matches(stored, instruction))
{
NativeInstructionSupport::Inherited
} else {
NativeInstructionSupport::Unsupported
}
}
pub fn name(&self) -> &str {
&self.name
}
pub fn qubits(&self) -> impl Iterator<Item = PhysicalQubit> + '_ {
self.qubits.iter().copied()
}
pub fn invalid_qubits(&self) -> impl Iterator<Item = PhysicalQubit> + '_ {
self.invalid_qubits.iter().copied()
}
pub fn is_usable_qubit(&self, qubit: PhysicalQubit) -> bool {
self.qubits.contains(&qubit) && !self.invalid_qubits.contains(&qubit)
}
pub fn usable_qubits(&self) -> impl Iterator<Item = PhysicalQubit> + '_ {
self.qubits.difference(&self.invalid_qubits).copied()
}
pub fn num_usable_qubits(&self) -> usize {
self.qubits.len() - self.invalid_qubits.len()
}
pub fn topology(&self) -> &Topology {
&self.topology
}
pub fn native_gates(&self) -> &[Instruction] {
&self.native_gates
}
pub fn qubit_properties(&self, qubit: PhysicalQubit) -> Option<&QubitProp> {
self.qubit_properties.get(&qubit)
}
pub fn edge_properties(
&self,
control: PhysicalQubit,
target: PhysicalQubit,
) -> Option<&EdgeProp> {
self.edge_properties.get(&(control, target))
}
pub fn supports_native_instruction(
&self,
instruction: &Instruction,
qargs: &[PhysicalQubit],
) -> bool {
if qargs.iter().any(|qubit| !self.is_usable_qubit(*qubit)) {
return false;
}
match instruction {
Instruction::Standard(StandardGate::GPhase) if qargs.is_empty() => !matches!(
self.resolve_native_instruction(None, instruction),
NativeInstructionSupport::Unsupported
),
Instruction::Standard(gate) if gate.num_qubits() == qargs.len() => match qargs {
[qubit] => !matches!(
self.resolve_native_instruction(
self.qubit_properties
.get(qubit)
.map(QubitProp::native_instructions),
instruction,
),
NativeInstructionSupport::Unsupported
),
[control, target]
if self.topology.supports_directed_coupling(*control, *target) =>
{
!matches!(
self.resolve_native_instruction(
self.edge_properties(*control, *target)
.map(EdgeProp::native_instructions),
instruction,
),
NativeInstructionSupport::Unsupported
)
}
_ => false,
},
Instruction::Standard(_)
| Instruction::McGate(_)
| Instruction::UnitaryGate(_)
| Instruction::CircuitGate(_)
| Instruction::Directive(_)
| Instruction::ClassicalData(_)
| Instruction::ClassicalControl(_)
| Instruction::Delay => false,
}
}
pub fn validate_operation(&self, operation: &Operation) -> Result<(), DeviceValidationError> {
let qargs = self.validate_qargs(&operation.qubits)?;
match &operation.instruction {
Instruction::ClassicalControl(control) => match control {
ClassicalControlOp::If(op) => {
self.validate_control_body(op.then_body().operations())?;
if let Some(body) = op.else_body() {
self.validate_control_body(body.operations())?;
}
}
ClassicalControlOp::While(op) => {
self.validate_control_body(op.body().operations())?
}
ClassicalControlOp::For(op) => {
self.validate_control_body(op.body().operations())?
}
ClassicalControlOp::Switch(op) => {
for case in op.cases() {
self.validate_control_body(case.body().operations())?;
}
if let Some(body) = op.default() {
self.validate_control_body(body.operations())?;
}
}
ClassicalControlOp::Break | ClassicalControlOp::Continue => {}
},
instruction => self.validate_atomic_instruction(instruction, qargs)?,
}
Ok(())
}
pub fn validate_value_operation(
&self,
operation: &ValueOperation,
) -> Result<(), DeviceValidationError> {
let qargs = self.validate_qargs(&operation.qubits)?;
match &operation.instruction {
ValueInstruction::Instruction(instruction) => {
self.validate_atomic_instruction(instruction, qargs)?
}
ValueInstruction::ClassicalControl(control) => match control {
ValueClassicalControlOp::If {
then_body,
else_body,
..
} => {
self.validate_value_control_body(then_body.operations())?;
if let Some(body) = else_body {
self.validate_value_control_body(body.operations())?;
}
}
ValueClassicalControlOp::While { body, .. }
| ValueClassicalControlOp::For { body, .. } => {
self.validate_value_control_body(body.operations())?
}
ValueClassicalControlOp::Switch { cases, default, .. } => {
for case in cases {
self.validate_value_control_body(case.body.operations())?;
}
if let Some(body) = default {
self.validate_value_control_body(body.operations())?;
}
}
ValueClassicalControlOp::Break | ValueClassicalControlOp::Continue => {}
},
}
Ok(())
}
fn validate_qargs(
&self,
qubits: &[Qubit],
) -> Result<Vec<PhysicalQubit>, DeviceValidationError> {
let qargs = qubits
.iter()
.copied()
.map(PhysicalQubit::from_qubit)
.collect::<Vec<_>>();
if let Some(qubit) = qargs
.iter()
.copied()
.find(|qubit| !self.is_usable_qubit(*qubit))
{
return Err(DeviceValidationError::UnusablePhysicalQubit {
device: self.name.clone(),
qubit,
});
}
Ok(qargs)
}
fn validate_atomic_instruction(
&self,
instruction: &Instruction,
qargs: Vec<PhysicalQubit>,
) -> Result<(), DeviceValidationError> {
match instruction {
Instruction::Directive(Directive::Measure | Directive::Reset) if qargs.len() == 1 => {}
Instruction::Directive(Directive::Barrier) => {}
Instruction::ClassicalData(op) => match op {
crate::circuit::ClassicalDataOp::Store { .. } if qargs.is_empty() => {}
crate::circuit::ClassicalDataOp::MeasureBit { .. } if qargs.len() == 1 => {}
crate::circuit::ClassicalDataOp::MeasureBits { .. } if !qargs.is_empty() => {}
_ => return Err(self.unsupported_instruction(instruction, qargs)),
},
Instruction::Delay if qargs.len() == 1 => {}
Instruction::McGate(_) | Instruction::UnitaryGate(_) | Instruction::CircuitGate(_) => {
return Err(DeviceValidationError::UndecomposedInstruction {
device: self.name.clone(),
instruction: instruction.to_string(),
qargs,
});
}
Instruction::Standard(gate) if gate.num_qubits() > 2 => {
return Err(DeviceValidationError::UndecomposedInstruction {
device: self.name.clone(),
instruction: instruction.to_string(),
qargs,
});
}
Instruction::Standard(gate) if gate.num_qubits() == 2 => {
let [control, target] = qargs.as_slice() else {
return Err(self.unsupported_instruction(instruction, qargs));
};
if !self.topology.supports_directed_coupling(*control, *target) {
return Err(DeviceValidationError::MissingDirectedCoupling {
device: self.name.clone(),
instruction: instruction.to_string(),
control: *control,
target: *target,
});
}
if !self.supports_native_instruction(instruction, &qargs) {
return Err(self.unsupported_instruction(instruction, qargs));
}
}
Instruction::Standard(_) if self.supports_native_instruction(instruction, &qargs) => {}
_ => return Err(self.unsupported_instruction(instruction, qargs)),
}
Ok(())
}
pub fn validate_circuit(&self, circuit: &Circuit) -> Result<(), DeviceValidationError> {
self.validate_operations(circuit.operations())
}
fn validate_operations(&self, operations: &[Operation]) -> Result<(), DeviceValidationError> {
for operation in operations {
self.validate_operation(operation)?;
}
Ok(())
}
fn validate_control_body(&self, operations: &[Operation]) -> Result<(), DeviceValidationError> {
let operations = if operations.first().is_some_and(|operation| {
matches!(
operation.instruction,
Instruction::Standard(StandardGate::GPhase)
) && operation.qubits.is_empty()
}) {
&operations[1..]
} else {
operations
};
self.validate_operations(operations)
}
fn validate_value_control_body(
&self,
operations: &[ValueOperation],
) -> Result<(), DeviceValidationError> {
let operations = if operations.first().is_some_and(|operation| {
matches!(
operation.instruction,
ValueInstruction::Instruction(Instruction::Standard(StandardGate::GPhase))
) && operation.qubits.is_empty()
}) {
&operations[1..]
} else {
operations
};
for operation in operations {
self.validate_value_operation(operation)?;
}
Ok(())
}
fn unsupported_instruction(
&self,
instruction: &Instruction,
qargs: Vec<PhysicalQubit>,
) -> DeviceValidationError {
DeviceValidationError::UnsupportedInstruction {
device: self.name.clone(),
instruction: instruction.to_string(),
qargs,
}
}
pub fn single_qubit_error(
&self,
qubit: PhysicalQubit,
instruction: &Instruction,
) -> Option<f64> {
if !self.is_usable_qubit(qubit) {
return None;
}
if !matches!(instruction, Instruction::Standard(gate) if gate.num_qubits() == 1) {
return None;
}
match self.resolve_native_instruction(
self.qubit_properties
.get(&qubit)
.map(QubitProp::native_instructions),
instruction,
) {
NativeInstructionSupport::Explicit(prop) => Some(prop.error_rate()),
NativeInstructionSupport::Inherited => self.default_single_qubit_error,
NativeInstructionSupport::Unsupported => None,
}
}
pub fn two_qubit_error(
&self,
control: PhysicalQubit,
target: PhysicalQubit,
instruction: &Instruction,
) -> Option<f64> {
if !self.is_usable_qubit(control)
|| !self.is_usable_qubit(target)
|| !self.topology.supports_directed_coupling(control, target)
{
return None;
}
if !matches!(instruction, Instruction::Standard(gate) if gate.num_qubits() == 2) {
return None;
}
match self.resolve_native_instruction(
self.edge_properties(control, target)
.map(EdgeProp::native_instructions),
instruction,
) {
NativeInstructionSupport::Explicit(prop) => Some(prop.error_rate()),
NativeInstructionSupport::Inherited => self.default_two_qubit_error,
NativeInstructionSupport::Unsupported => None,
}
}
pub(crate) fn native_instruction_calibration(
&self,
instruction: &Instruction,
qargs: &[PhysicalQubit],
) -> Option<NativeInstructionCalibration> {
if !self.supports_native_instruction(instruction, qargs) {
return None;
}
let support = match qargs {
[qubit] => self.resolve_native_instruction(
self.qubit_properties
.get(qubit)
.map(QubitProp::native_instructions),
instruction,
),
[control, target] => self.resolve_native_instruction(
self.edge_properties(*control, *target)
.map(EdgeProp::native_instructions),
instruction,
),
_ => NativeInstructionSupport::Inherited,
};
match support {
NativeInstructionSupport::Explicit(prop) => Some(NativeInstructionCalibration {
error_rate: Some(prop.error_rate()),
duration: prop.length(),
}),
NativeInstructionSupport::Inherited => Some(NativeInstructionCalibration {
error_rate: match qargs.len() {
1 => self.default_single_qubit_error,
2 => self.default_two_qubit_error,
_ => None,
},
duration: None,
}),
NativeInstructionSupport::Unsupported => None,
}
}
pub fn edge_error(&self, control: PhysicalQubit, target: PhysicalQubit) -> Option<f64> {
if !self.is_usable_qubit(control)
|| !self.is_usable_qubit(target)
|| !self.topology.supports_directed_coupling(control, target)
{
return None;
}
self.edge_properties(control, target)
.and_then(|props| {
props
.native_instructions()
.iter()
.map(InstructionProp::error_rate)
.min_by(|a, b| a.total_cmp(b))
})
.or(self.default_two_qubit_error)
}
pub fn get_t1(&self, qubit: PhysicalQubit) -> Option<f64> {
self.qubit_properties
.get(&qubit)
.and_then(|p| p.t1)
.or(self.default_t1)
}
pub fn get_t2(&self, qubit: PhysicalQubit) -> Option<f64> {
self.qubit_properties
.get(&qubit)
.and_then(|p| p.t2)
.or(self.default_t2)
}
pub fn get_readout_error(&self, qubit: PhysicalQubit) -> Option<f64> {
self.qubit_properties
.get(&qubit)
.map(|p| p.readout_error)
.or(self.default_readout_error)
}
pub fn default_single_qubit_error(&self) -> Option<f64> {
self.default_single_qubit_error
}
pub fn default_two_qubit_error(&self) -> Option<f64> {
self.default_two_qubit_error
}
pub fn default_t1(&self) -> Option<f64> {
self.default_t1
}
pub fn default_t2(&self) -> Option<f64> {
self.default_t2
}
pub fn default_readout_error(&self) -> Option<f64> {
self.default_readout_error
}
pub fn calibration_time(&self) -> Option<OffsetDateTime> {
self.calibration_time
}
}
#[cfg(test)]
#[path = "./device_test.rs"]
mod device_test;