Skip to content

Commit eb84786

Browse files
authored
Allow to catch and propagate errors in all resource estimation models (#2457)
This changes return types for methods in the `Overhead` and `FactoryBuilder` trait from `T` to `Result<T, resource_estimator::estimates::Error>`. No implementation of this `Overhead` and `FactoryBuilder` trait returns an error; therefore, the behavior of RE for the default system architecture does not change. However, when implementing the traits to extend the resource estimator, the new signature allows more flexibility and allows users of the implementation to see errors.
1 parent a870c14 commit eb84786

File tree

15 files changed

+262
-184
lines changed

15 files changed

+262
-184
lines changed

resource_estimator/src/estimates/error.rs

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,27 @@ pub enum Error {
1313
#[error("Algorithm requires at least one magic state or measurement to estimate resources")]
1414
#[diagnostic(code("Qsc.Estimates.AlgorithmHasNoResources"))]
1515
AlgorithmHasNoResources,
16+
/// The number of algorithmic logical qubits cannot be computed.
17+
///
18+
/// ✅ This does not contain user data and can be logged
19+
/// ✅ This error cannot be triggered by the system.
20+
#[error("Cannot compute the number of algorithmic logical qubits: {0}")]
21+
#[diagnostic(code("Qsc.Estimates.AlgorithmicLogicalQubitsComputationFailed"))]
22+
AlgorithmicLogicalQubitsComputationFailed(String),
23+
/// The algorithmic logical depth cannot be computed.
24+
///
25+
/// ✅ This does not contain user data and can be logged
26+
/// ✅ This error cannot be triggered by the system.
27+
#[error("Cannot compute the algorithmic logical depth: {0}")]
28+
#[diagnostic(code("Qsc.Estimates.AlgorithmicLogicalDepthComputationFailed"))]
29+
AlgorithmicLogicalDepthComputationFailed(String),
30+
/// The number of required magic states cannot be computed.
31+
///
32+
/// ✅ This does not contain user data and can be logged
33+
/// ✅ This error cannot be triggered by the system.
34+
#[error("Cannot compute the required number of magic states: {0}")]
35+
#[diagnostic(code("Qsc.Estimates.NumberOfMagicStatesComputationFailed"))]
36+
NumberOfMagicStatesComputationFailed(String),
1637
/// Both constraints for maximal time and
1738
/// maximal number of qubits are provided
1839
///
@@ -37,12 +58,12 @@ pub enum Error {
3758
#[error("No solution found for the provided maximum number of physical qubits.")]
3859
#[diagnostic(code("Qsc.Estimates.MaxPhysicalQubitsTooSmall"))]
3960
MaxPhysicalQubitsTooSmall,
40-
/// Resource estimation configuration can never produce T states
61+
/// Resource estimation failed to find factories
4162
///
4263
/// ✅ This error cannot be triggered by the system.
43-
#[error("Resource estimation configuration can never produce T states, required magic state output error rate was {0:.3e}")]
44-
#[diagnostic(code("Qsc.Estimates.CannotComputeMagicStates"))]
45-
CannotComputeMagicStates(f64),
64+
#[error("Resource estimation failed to find factories: {0}")]
65+
#[diagnostic(code("Qsc.Estimates.FactorySearchFailed"))]
66+
FactorySearchFailed(String),
4667
/// Constraint-based search only supports one magic state type.
4768
///
4869
/// ✅ This error cannot be triggered by the system, since only one magic

resource_estimator/src/estimates/error_correction.rs

Lines changed: 24 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -82,13 +82,11 @@ pub trait ErrorCorrection {
8282
required_logical_error_rate: f64,
8383
) -> Result<Self::Parameter, String> {
8484
for parameter in self.code_parameter_range(None) {
85-
if let (Ok(probability), Ok(logical_qubits)) = (
86-
self.logical_error_rate(qubit, &parameter),
87-
self.logical_qubits(&parameter),
88-
) {
89-
if probability / (logical_qubits as f64) <= required_logical_error_rate {
90-
return Ok(parameter);
91-
}
85+
let probability = self.logical_error_rate(qubit, &parameter)?;
86+
let logical_qubits = self.logical_qubits(&parameter)?;
87+
88+
if probability / (logical_qubits as f64) <= required_logical_error_rate {
89+
return Ok(parameter);
9290
}
9391
}
9492

@@ -110,20 +108,17 @@ pub trait ErrorCorrection {
110108
let mut best: Option<(Self::Parameter, f64)> = None;
111109

112110
for parameter in self.code_parameter_range(None) {
113-
if let (Ok(probability), Ok(logical_qubits), Ok(physical_qubits)) = (
114-
self.logical_error_rate(qubit, &parameter),
115-
self.logical_qubits(&parameter),
116-
self.physical_qubits(&parameter),
117-
) {
118-
let physical_qubits_per_logical_qubits =
119-
physical_qubits as f64 / logical_qubits as f64;
120-
if (probability / (logical_qubits as f64) <= required_logical_error_rate)
121-
&& best
122-
.as_ref()
123-
.is_none_or(|&(_, pq)| physical_qubits_per_logical_qubits < pq)
124-
{
125-
best = Some((parameter, physical_qubits_per_logical_qubits));
126-
}
111+
let probability = self.logical_error_rate(qubit, &parameter)?;
112+
let logical_qubits = self.logical_qubits(&parameter)?;
113+
let physical_qubits = self.physical_qubits(&parameter)?;
114+
115+
let physical_qubits_per_logical_qubits = physical_qubits as f64 / logical_qubits as f64;
116+
if (probability / (logical_qubits as f64) <= required_logical_error_rate)
117+
&& best
118+
.as_ref()
119+
.is_none_or(|&(_, pq)| physical_qubits_per_logical_qubits < pq)
120+
{
121+
best = Some((parameter, physical_qubits_per_logical_qubits));
127122
}
128123
}
129124

@@ -146,16 +141,14 @@ pub trait ErrorCorrection {
146141
let mut best: Option<(Self::Parameter, u64)> = None;
147142

148143
for parameter in self.code_parameter_range(None) {
149-
if let (Ok(probability), Ok(logical_qubits), Ok(logical_cycle_time)) = (
150-
self.logical_error_rate(qubit, &parameter),
151-
self.logical_qubits(&parameter),
152-
self.logical_cycle_time(qubit, &parameter),
153-
) {
154-
if (probability / (logical_qubits as f64) <= required_logical_error_rate)
155-
&& best.as_ref().is_none_or(|&(_, t)| logical_cycle_time < t)
156-
{
157-
best = Some((parameter, logical_cycle_time));
158-
}
144+
let probability = self.logical_error_rate(qubit, &parameter)?;
145+
let logical_qubits = self.logical_qubits(&parameter)?;
146+
let logical_cycle_time = self.logical_cycle_time(qubit, &parameter)?;
147+
148+
if (probability / (logical_qubits as f64) <= required_logical_error_rate)
149+
&& best.as_ref().is_none_or(|&(_, t)| logical_cycle_time < t)
150+
{
151+
best = Some((parameter, logical_cycle_time));
159152
}
160153
}
161154

resource_estimator/src/estimates/factory.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ where
3333
magic_state_type: usize,
3434
output_error_rate: f64,
3535
max_code_parameter: &E::Parameter,
36-
) -> Option<Vec<Cow<Self::Factory>>>;
36+
) -> Result<Vec<Cow<Self::Factory>>, String>;
3737

3838
fn num_magic_state_types(&self) -> usize {
3939
1

resource_estimator/src/estimates/factory/dispatch.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ impl<E: ErrorCorrection, Builder1: FactoryBuilder<E>, Builder2: FactoryBuilder<E
7676
magic_state_type: usize,
7777
output_error_rate: f64,
7878
max_code_parameter: &E::Parameter,
79-
) -> Option<Vec<Cow<Self::Factory>>> {
79+
) -> Result<Vec<Cow<Self::Factory>>, String> {
8080
match magic_state_type {
8181
0 => self
8282
.builder1

resource_estimator/src/estimates/factory/empty.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ impl<E: ErrorCorrection<Parameter = impl Clone>> FactoryBuilder<E> for NoFactori
3232
_magic_state_type: usize,
3333
_output_error_rate: f64,
3434
_max_code_parameter: &<E as ErrorCorrection>::Parameter,
35-
) -> Option<Vec<std::borrow::Cow<Self::Factory>>> {
35+
) -> Result<Vec<std::borrow::Cow<Self::Factory>>, String> {
3636
unreachable!()
3737
}
3838

resource_estimator/src/estimates/factory/round_based.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ impl<P: Clone> DistillationRound<P> {
8383
let mut upper = self.num_units;
8484
let mut lower = self.num_units / 2;
8585
while lower < upper {
86-
self.num_units = (lower + upper) / 2;
86+
self.num_units = u64::midpoint(lower, upper);
8787
let num_output_ts = self.compute_num_output_states(failure_probability);
8888
if num_output_ts >= output_states_needed_next {
8989
upper = self.num_units;

resource_estimator/src/estimates/layout.rs

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,26 +3,27 @@
33

44
use serde::Serialize;
55

6+
use super::Error;
67
use super::{ErrorBudget, ErrorBudgetStrategy};
78

89
/// Trait to model post-layout logical overhead
910
pub trait Overhead {
1011
/// The number of logical qubits to execute the algorithm after mapping
1112
///
1213
/// This number does not include qubit used to produce magic states.
13-
fn logical_qubits(&self) -> u64;
14+
fn logical_qubits(&self) -> Result<u64, String>;
1415

1516
/// The number of logical unit cycles to execute the algorithm
1617
///
1718
/// This number is a lower bound for the execution time of the algorithm,
1819
/// and might be extended by assuming no-ops.
19-
fn logical_depth(&self, budget: &ErrorBudget) -> u64;
20+
fn logical_depth(&self, budget: &ErrorBudget) -> Result<u64, String>;
2021

2122
/// The number of magic states
2223
///
2324
/// The index is used to indicate the type of magic states and must be
2425
/// supported by available factory builders in the physical estimation.
25-
fn num_magic_states(&self, budget: &ErrorBudget, index: usize) -> u64;
26+
fn num_magic_states(&self, budget: &ErrorBudget, index: usize) -> Result<u64, String>;
2627

2728
/// When implemented, prunes the error budget with respect to the provided
2829
/// strategy
@@ -49,18 +50,23 @@ impl RealizedOverhead {
4950
overhead: &impl Overhead,
5051
budget: &ErrorBudget,
5152
num_magic_state_types: usize,
52-
) -> Self {
53-
let logical_qubits = overhead.logical_qubits();
54-
let logical_depth = overhead.logical_depth(budget);
53+
) -> Result<Self, Error> {
54+
let logical_qubits = overhead
55+
.logical_qubits()
56+
.map_err(Error::AlgorithmicLogicalQubitsComputationFailed)?;
57+
let logical_depth = overhead
58+
.logical_depth(budget)
59+
.map_err(Error::AlgorithmicLogicalDepthComputationFailed)?;
5560
let num_magic_states = (0..num_magic_state_types)
5661
.map(|index| overhead.num_magic_states(budget, index))
57-
.collect();
62+
.collect::<Result<_, _>>()
63+
.map_err(Error::NumberOfMagicStatesComputationFailed)?;
5864

59-
Self {
65+
Ok(Self {
6066
logical_qubits,
6167
logical_depth,
6268
num_magic_states,
63-
}
69+
})
6470
}
6571

6672
#[must_use]
@@ -80,15 +86,15 @@ impl RealizedOverhead {
8086
}
8187

8288
impl Overhead for RealizedOverhead {
83-
fn logical_qubits(&self) -> u64 {
84-
self.logical_qubits
89+
fn logical_qubits(&self) -> Result<u64, String> {
90+
Ok(self.logical_qubits)
8591
}
8692

87-
fn logical_depth(&self, _budget: &ErrorBudget) -> u64 {
88-
self.logical_depth
93+
fn logical_depth(&self, _budget: &ErrorBudget) -> Result<u64, String> {
94+
Ok(self.logical_depth)
8995
}
9096

91-
fn num_magic_states(&self, _budget: &ErrorBudget, index: usize) -> u64 {
92-
self.num_magic_states[index]
97+
fn num_magic_states(&self, _budget: &ErrorBudget, index: usize) -> Result<u64, String> {
98+
Ok(self.num_magic_states[index])
9399
}
94100
}

0 commit comments

Comments
 (0)