Skip to content

Commit a3aaf4f

Browse files
authored
Distance coefficient power parameter in QEC scheme (microsoft#2212)
This PR extends the formula template for QEC logical error rate formulas from: $$ a \left(\frac{p}{p_{\mathrm{th}}}\right)^{\frac{d+1}{2}} $$ to $$ a d^k \left(\frac{p}{p_{\mathrm{th}}}\right)^{\frac{d+1}{2}} $$ where $k$ is the new distance coefficient power discussed in [arXiv:2411.10406, Section III.B, page 25](https://arxiv.org/pdf/2411.10406#page=25). For $k = 0$, the default, the formula remains unchanged compared to the current one. However, changing $k$ to other values allows to model a different error correction behavior, as motivated by the paper. The parameter is called `distance_coefficient_power` or `distanceCoefficientPower` in the JSON parameter. It is also added to the Python RE parameter utility structure (together with `max_code_distance` which was absent so far).
1 parent 5c7522c commit a3aaf4f

File tree

9 files changed

+105
-23
lines changed

9 files changed

+105
-23
lines changed

pip/qsharp/estimator/_estimator.py

+2
Original file line numberDiff line numberDiff line change
@@ -227,8 +227,10 @@ class EstimatorQecScheme(AutoValidatingParams):
227227
name: Optional[str] = None
228228
error_correction_threshold: Optional[float] = validating_field(_check_error_rate)
229229
crossing_prefactor: Optional[float] = None
230+
distance_coefficient_power: Optional[int] = None
230231
logical_cycle_time: Optional[str] = None
231232
physical_qubits_per_logical_qubit: Optional[str] = None
233+
max_code_distance: Optional[int] = None
232234

233235

234236
@dataclass

resource_estimator/src/estimates/error_correction.rs

+17-2
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,21 @@ pub trait ErrorCorrection {
6565
&self,
6666
qubit: &Self::Qubit,
6767
required_logical_error_rate: f64,
68+
) -> Result<Self::Parameter, String> {
69+
self.compute_smallest_code_parameter(qubit, required_logical_error_rate)
70+
}
71+
72+
/// Default implementation for `Self::compute_code_parameter` in which the
73+
/// smallest code parameter that satisfies the required logical error rate
74+
/// is returned.
75+
///
76+
/// This method assumes that the code parameters that are returned from
77+
/// `Self::code_parameter_range` are ordered by the logical error rate per
78+
/// qubit, starting from the largest one.
79+
fn compute_smallest_code_parameter(
80+
&self,
81+
qubit: &Self::Qubit,
82+
required_logical_error_rate: f64,
6883
) -> Result<Self::Parameter, String> {
6984
for parameter in self.code_parameter_range(None) {
7085
if let (Ok(probability), Ok(logical_qubits)) = (
@@ -105,7 +120,7 @@ pub trait ErrorCorrection {
105120
if (probability / (logical_qubits as f64) <= required_logical_error_rate)
106121
&& best
107122
.as_ref()
108-
.map_or(true, |&(_, pq)| physical_qubits_per_logical_qubits < pq)
123+
.is_none_or(|&(_, pq)| physical_qubits_per_logical_qubits < pq)
109124
{
110125
best = Some((parameter, physical_qubits_per_logical_qubits));
111126
}
@@ -137,7 +152,7 @@ pub trait ErrorCorrection {
137152
self.logical_cycle_time(qubit, &parameter),
138153
) {
139154
if (probability / (logical_qubits as f64) <= required_logical_error_rate)
140-
&& best.as_ref().map_or(true, |&(_, t)| logical_cycle_time < t)
155+
&& best.as_ref().is_none_or(|&(_, t)| logical_cycle_time < t)
141156
{
142157
best = Some((parameter, logical_cycle_time));
143158
}

resource_estimator/src/estimates/error_correction/code_with_threshold_and_distance.rs

+45-14
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,22 @@ pub struct CodeWithThresholdAndDistance<Evaluator> {
1212
evaluator: Evaluator,
1313
crossing_prefactor: f64,
1414
error_correction_threshold: f64,
15+
distance_coefficient_power: u32,
1516
max_code_distance: Option<u64>,
1617
}
1718

18-
impl<Evaluator> CodeWithThresholdAndDistance<Evaluator> {
19+
impl<Evaluator: CodeWithThresholdAndDistanceEvaluator> CodeWithThresholdAndDistance<Evaluator> {
1920
pub fn new(
2021
evaluator: Evaluator,
2122
crossing_prefactor: f64,
2223
error_correction_threshold: f64,
24+
distance_coefficient_power: u32,
2325
) -> Self {
2426
Self {
2527
evaluator,
2628
crossing_prefactor,
2729
error_correction_threshold,
30+
distance_coefficient_power,
2831
max_code_distance: None,
2932
}
3033
}
@@ -33,12 +36,14 @@ impl<Evaluator> CodeWithThresholdAndDistance<Evaluator> {
3336
evaluator: Evaluator,
3437
crossing_prefactor: f64,
3538
error_correction_threshold: f64,
39+
distance_coefficient_power: u32,
3640
max_code_distance: u64,
3741
) -> Self {
3842
Self {
3943
evaluator,
4044
crossing_prefactor,
4145
error_correction_threshold,
46+
distance_coefficient_power,
4247
max_code_distance: Some(max_code_distance),
4348
}
4449
}
@@ -59,6 +64,14 @@ impl<Evaluator> CodeWithThresholdAndDistance<Evaluator> {
5964
self.error_correction_threshold = error_correction_threshold;
6065
}
6166

67+
pub fn distance_coefficient_power(&self) -> u32 {
68+
self.distance_coefficient_power
69+
}
70+
71+
pub fn set_distance_coefficient_power(&mut self, distance_coefficient_power: u32) {
72+
self.distance_coefficient_power = distance_coefficient_power;
73+
}
74+
6275
pub fn max_code_distance(&self) -> Option<&u64> {
6376
self.max_code_distance.as_ref()
6477
}
@@ -74,6 +87,29 @@ impl<Evaluator> CodeWithThresholdAndDistance<Evaluator> {
7487
pub fn evaluator_mut(&mut self) -> &mut Evaluator {
7588
&mut self.evaluator
7689
}
90+
91+
/// When the distance coefficient power is 0, the function can be easily
92+
/// inverted and we can avoid looping over all code parameters
93+
fn compute_smallest_code_parameter_with_inverse_formula(
94+
&self,
95+
qubit: &Evaluator::Qubit,
96+
required_logical_error_rate: f64,
97+
) -> Result<u64, String> {
98+
// Compute code distance d (Equation (E2) in paper)
99+
let physical_error_rate = self.evaluator.physical_error_rate(qubit);
100+
let numerator = 2.0 * (self.crossing_prefactor / required_logical_error_rate).ln();
101+
let denominator = (self.error_correction_threshold / physical_error_rate).ln();
102+
103+
let code_distance = (((numerator / denominator) - 1.0).ceil() as u64) | 0x1;
104+
105+
if let Some(max_distance) = self.max_code_distance {
106+
if max_distance < code_distance {
107+
return Err(format!("The computed code distance {code_distance} is too high; maximum allowed code distance is {max_distance}; try increasing the total logical error budget"));
108+
}
109+
}
110+
111+
Ok(code_distance)
112+
}
77113
}
78114

79115
impl<Evaluator: CodeWithThresholdAndDistanceEvaluator> ErrorCorrection
@@ -104,30 +140,25 @@ impl<Evaluator: CodeWithThresholdAndDistanceEvaluator> ErrorCorrection
104140
))
105141
} else {
106142
Ok(self.crossing_prefactor
143+
* (code_distance.pow(self.distance_coefficient_power) as f64)
107144
* ((physical_error_rate / self.error_correction_threshold)
108145
.powi((*code_distance as i32 + 1) / 2)))
109146
}
110147
}
111148

112-
// Compute code distance d (Equation (E2) in paper)
113149
fn compute_code_parameter(
114150
&self,
115151
qubit: &Self::Qubit,
116152
required_logical_qubit_error_rate: f64,
117153
) -> Result<u64, String> {
118-
let physical_error_rate = self.evaluator.physical_error_rate(qubit);
119-
let numerator = 2.0 * (self.crossing_prefactor / required_logical_qubit_error_rate).ln();
120-
let denominator = (self.error_correction_threshold / physical_error_rate).ln();
121-
122-
let code_distance = (((numerator / denominator) - 1.0).ceil() as u64) | 0x1;
123-
124-
if let Some(max_distance) = self.max_code_distance {
125-
if max_distance < code_distance {
126-
return Err(format!("The computed code distance {code_distance} is too high; maximum allowed code distance is {max_distance}; try increasing the total logical error budget"));
127-
}
154+
if self.distance_coefficient_power == 0 {
155+
self.compute_smallest_code_parameter_with_inverse_formula(
156+
qubit,
157+
required_logical_qubit_error_rate,
158+
)
159+
} else {
160+
self.compute_smallest_code_parameter(qubit, required_logical_qubit_error_rate)
128161
}
129-
130-
Ok(code_distance)
131162
}
132163

133164
fn code_parameter_range(

resource_estimator/src/estimates/physical_estimation.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@ impl<
222222
// ensures that the first code parameter is always tried. After
223223
// that, the last code parameter governs the reuse of the magic
224224
// state factory.
225-
if last_code_parameter.as_ref().map_or(true, |d| {
225+
if last_code_parameter.as_ref().is_none_or(|d| {
226226
self.ftp
227227
.code_parameter_cmp(self.qubit.as_ref(), d, &code_parameter)
228228
.is_gt()
@@ -293,7 +293,7 @@ impl<
293293

294294
if best_estimation_result
295295
.as_ref()
296-
.map_or(true, |r| result.physical_qubits() < r.physical_qubits())
296+
.is_none_or(|r| result.physical_qubits() < r.physical_qubits())
297297
{
298298
best_estimation_result = Some(result);
299299
}
@@ -372,7 +372,7 @@ impl<
372372
// ensures that the first code parameter is always tried. After
373373
// that, the last code parameter governs the reuse of the magic
374374
// state factory.
375-
if last_code_parameter.as_ref().map_or(true, |d| {
375+
if last_code_parameter.as_ref().is_none_or(|d| {
376376
self.ftp
377377
.code_parameter_cmp(self.qubit.as_ref(), d, &code_parameter)
378378
.is_gt()
@@ -443,7 +443,7 @@ impl<
443443

444444
if best_estimation_result
445445
.as_ref()
446-
.map_or(true, |r| result.runtime() < r.runtime())
446+
.is_none_or(|r| result.runtime() < r.runtime())
447447
{
448448
best_estimation_result = Some(result);
449449
}

resource_estimator/src/estimates/physical_estimation/estimate_frontier.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ impl<
8989
// ensures that the first code parameter is always tried. After
9090
// that, the last code parameter governs the reuse of the magic
9191
// state factory.
92-
if last_code_parameter.as_ref().map_or(true, |d| {
92+
if last_code_parameter.as_ref().is_none_or(|d| {
9393
self.ftp
9494
.code_parameter_cmp(self.qubit.as_ref(), d, &code_parameter)
9595
.is_gt()

resource_estimator/src/estimates/physical_estimation/estimate_without_restrictions.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,7 @@ impl<
274274
error_budget: &ErrorBudget,
275275
num_cycles: u64,
276276
) -> bool {
277-
self.max_factories.map_or(true, |max_factories| {
277+
self.max_factories.is_none_or(|max_factories| {
278278
max_factories >= self.num_factories(logical_patch, 0, factory, error_budget, num_cycles)
279279
})
280280
}

resource_estimator/src/system/modeling/fault_tolerance.rs

+16-1
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ pub struct ProtocolSpecification {
4444
#[serde(default)]
4545
pub(crate) crossing_prefactor: Option<f64>,
4646
#[serde(default)]
47+
pub(crate) distance_coefficient_power: Option<u32>,
48+
#[serde(default)]
4749
pub(crate) logical_cycle_time: Option<String>,
4850
#[serde(default)]
4951
pub(crate) physical_qubits_per_logical_qubit: Option<String>,
@@ -57,6 +59,7 @@ impl Default for ProtocolSpecification {
5759
name: "surface_code".into(),
5860
crossing_prefactor: None,
5961
error_correction_threshold: None,
62+
distance_coefficient_power: None,
6063
logical_cycle_time: None,
6164
physical_qubits_per_logical_qubit: None,
6265
max_code_distance: default_max_code_distance(),
@@ -236,7 +239,7 @@ pub fn load_protocol_from_specification(
236239
}
237240

238241
fn base_protocol(
239-
model: &mut ProtocolSpecification,
242+
model: &ProtocolSpecification,
240243
qubit: &PhysicalQubit,
241244
) -> crate::system::Result<(CodeWithThresholdAndDistance<ProtocolEvaluator>, bool)> {
242245
if model.name == "surface_code" || model.name == "surfaceCode" || model.name == "surface-code" {
@@ -260,6 +263,8 @@ fn base_protocol(
260263
.crossing_prefactor
261264
.ok_or_else(|| CannotParseJSON(serde::de::Error::missing_field("crossingPrefactor")))?;
262265

266+
let distance_coefficient_power = model.distance_coefficient_power.unwrap_or(0);
267+
263268
let logical_cycle_time_expr = model
264269
.logical_cycle_time
265270
.as_ref()
@@ -296,6 +301,7 @@ fn base_protocol(
296301
evaluator,
297302
crossing_prefactor,
298303
error_correction_threshold,
304+
distance_coefficient_power,
299305
max_code_distance,
300306
),
301307
false,
@@ -323,6 +329,12 @@ fn update_default_from_specification(
323329
model.crossing_prefactor = Some(code.crossing_prefactor());
324330
}
325331

332+
if let Some(distance_coefficient_power) = model.distance_coefficient_power {
333+
code.set_distance_coefficient_power(distance_coefficient_power);
334+
} else {
335+
model.distance_coefficient_power = Some(code.distance_coefficient_power());
336+
}
337+
326338
if let Some(logical_cycle_time) = model.logical_cycle_time.as_ref() {
327339
code.evaluator_mut().logical_cycle_time =
328340
CompiledExpression::from_string(logical_cycle_time, "logicalCycleTime")?;
@@ -395,6 +407,7 @@ pub fn surface_code_gate_based() -> CodeWithThresholdAndDistance<ProtocolEvaluat
395407
},
396408
crossing_prefactor,
397409
error_correction_threshold,
410+
0,
398411
MAX_CODE_DISTANCE,
399412
)
400413
}
@@ -435,6 +448,7 @@ pub fn surface_code_measurement_based() -> CodeWithThresholdAndDistance<Protocol
435448
},
436449
crossing_prefactor,
437450
error_correction_threshold,
451+
0,
438452
MAX_CODE_DISTANCE,
439453
)
440454
}
@@ -475,6 +489,7 @@ pub fn floquet_code() -> CodeWithThresholdAndDistance<ProtocolEvaluator> {
475489
},
476490
crossing_prefactor,
477491
error_correction_threshold,
492+
0,
478493
MAX_CODE_DISTANCE,
479494
)
480495
}

resource_estimator/src/system/modeling/fault_tolerance/tests.rs

+18
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,21 @@ fn compute_code_distance() -> Result<(), String> {
1717

1818
Ok(())
1919
}
20+
21+
#[test]
22+
fn compute_distance_is_inverse() -> Result<(), String> {
23+
let qubit = PhysicalQubit::default();
24+
let mut ftp = surface_code_gate_based();
25+
26+
for distance_coefficient_power in [0, 1, 2] {
27+
ftp.set_distance_coefficient_power(distance_coefficient_power);
28+
29+
for code_distance in (1..=49).step_by(2) {
30+
let error_rate = ftp.logical_error_rate(&qubit, &code_distance)?;
31+
let computed_distance = ftp.compute_code_parameter(&qubit, error_rate)?;
32+
assert_eq!(computed_distance, code_distance);
33+
}
34+
}
35+
36+
Ok(())
37+
}

resource_estimator/src/system/test_report.json

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
"estimateType": "singlePoint",
1111
"qecScheme": {
1212
"crossingPrefactor": 0.03,
13+
"distanceCoefficientPower": 0,
1314
"errorCorrectionThreshold": 0.01,
1415
"logicalCycleTime": "(4 * twoQubitGateTime + 2 * oneQubitMeasurementTime) * codeDistance",
1516
"maxCodeDistance": 50,

0 commit comments

Comments
 (0)