microsoft/qdk

Public

mirrored from https://github.com/microsoft/qdkAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
billti/num2-sim

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

source/resource_estimator/src/system/modeling/tfactory.rs

511lines · modecode

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4#[cfg(test)]
5mod tests;
6
7use core::fmt;
8use std::{collections::BTreeMap, fmt::Display, vec};
9
10use serde::{Serialize, ser::SerializeMap};
11
12use crate::estimates::{
13 DistillationRound, DistillationUnit, Factory, LogicalPatch, RoundBasedFactory,
14};
15
16use super::{
17 super::{
18 compiled_expression::CompiledExpression,
19 error::IO::{self, CannotParseJSON},
20 },
21 PhysicalQubit, Protocol,
22};
23
24pub enum TFactoryQubit<'a> {
25 Logical(&'a LogicalPatch<Protocol>),
26 Physical(&'a PhysicalQubit),
27}
28
29impl TFactoryQubit<'_> {
30 pub fn physical_qubits(&self) -> u64 {
31 match self {
32 Self::Logical(qubit) => qubit.physical_qubits(),
33 Self::Physical(_) => 1,
34 }
35 }
36
37 pub fn cycle_time(&self) -> u64 {
38 match self {
39 Self::Logical(qubit) => qubit.logical_cycle_time(),
40 Self::Physical(qubit) => qubit.t_gate_time(),
41 }
42 }
43
44 pub fn clifford_error_rate(&self) -> f64 {
45 match self {
46 Self::Logical(qubit) => qubit.logical_error_rate(),
47 Self::Physical(qubit) => qubit.clifford_error_rate(),
48 }
49 }
50
51 pub fn readout_error_rate(&self) -> f64 {
52 match self {
53 // We did not push for a readout error rate for logical qubits,
54 // since destructive measurement on surface or Floquet codes has orders of magnitude better fidelity
55 // than the logical Clifford operations.
56 // Hence for modeling purposes, the logical readout error rate is always 0 and
57 // hence we do not even treat it as a parameter.
58 // This is not a great model, and the logical readout error rate is a function of the distance.
59 // But we would only see an effect on the final results in the cases of distance three (maybe distance five).
60 // This may be worthwhile for t-factory DUs where the lowest level can be at distance three or five.
61 // But we'd need to do some examples to see if it's worth the effort in changing
62 // how logical modeling works for QEC.
63 Self::Logical(_) => 0.0,
64 Self::Physical(qubit) => qubit.readout_error_rate(),
65 }
66 }
67
68 pub fn t_error_rate(&self) -> f64 {
69 match self {
70 Self::Logical(qubit) => qubit.physical_qubit().t_gate_error_rate(),
71 Self::Physical(qubit) => qubit.t_gate_error_rate(),
72 }
73 }
74
75 pub fn code_distance(&self) -> u64 {
76 match self {
77 Self::Logical(qubit) => *qubit.code_parameter(),
78 Self::Physical(_) => 1,
79 }
80 }
81}
82
83impl Display for TFactoryDistillationUnitType {
84 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
85 match self {
86 TFactoryDistillationUnitType::Logical => f.write_str("Logical"),
87 TFactoryDistillationUnitType::Physical => f.write_str("Physical"),
88 TFactoryDistillationUnitType::Combined => f.write_str("Combined"),
89 }
90 }
91}
92
93/// A formula to represent the evaluation of failure probabilities and error
94/// rates. The first argument is the `inputErrorRate`, the second argument the
95/// `cliffordErrorRate`, and the last argument the `readoutErrorRate`
96pub type TFactoryFormula = Box<dyn Fn(f64, f64, f64) -> f64>;
97
98impl From<CompiledExpression> for TFactoryFormula {
99 fn from(compiled_expression: CompiledExpression) -> Self {
100 Box::new(
101 move |input_error_rate: f64, clifford_error_rate: f64, readout_error_rate| -> f64 {
102 let mut context = BTreeMap::new();
103 context.insert("inputErrorRate".to_string(), input_error_rate);
104 context.insert("cliffordErrorRate".to_string(), clifford_error_rate);
105 context.insert("readoutErrorRate".to_string(), readout_error_rate);
106 context.insert("z".to_string(), input_error_rate);
107 context.insert("c".to_string(), clifford_error_rate);
108 context.insert("r".to_string(), readout_error_rate);
109
110 compiled_expression
111 .evaluate(&mut context)
112 .expect("expression evaluation should succeed")
113 },
114 )
115 }
116}
117
118#[derive(Copy, Clone, Eq, PartialEq)]
119pub enum TFactoryDistillationUnitType {
120 Logical,
121 Physical,
122 Combined,
123}
124
125pub struct TFactoryDistillationUnitResources {
126 /// The number of unit qubits utilized in distillation.
127 pub(crate) num_unit_qubits: u64,
128 /// The duration of distillation measured in qubit cycles.
129 pub(crate) duration_in_qubit_cycle_time: u64,
130}
131
132pub struct TFactoryDistillationUnitTemplate {
133 /// The distillation unit output name.
134 pub(crate) name: String,
135 /// The number of input t states accepted by the distillation unit.
136 pub(crate) num_input_ts: u64,
137 /// The number of output t states generated by the distillation unit.
138 pub(crate) num_output_ts: u64,
139 /// The failure probability formula expression.
140 pub(crate) failure_probability_function: TFactoryFormula,
141 /// The output error rate formula expression.
142 pub(crate) output_error_rate_function: TFactoryFormula,
143 /// The type of distillation unit defines the scope of qubit types: physical, logical, or both.
144 pub(crate) unit_type: TFactoryDistillationUnitType,
145 /// Specification for the physical qubit protocol.
146 pub(crate) physical_qubit_specification: Option<TFactoryDistillationUnitResources>,
147 /// Specification for the logical qubit protocol.
148 pub(crate) logical_qubit_specification: Option<TFactoryDistillationUnitResources>,
149 /// Specification for the logical qubit protocol if necessary to override for the first round of distillation.
150 pub(crate) logical_qubit_specification_first_round_override:
151 Option<TFactoryDistillationUnitResources>,
152}
153
154impl TFactoryDistillationUnitTemplate {
155 pub fn from_name(name: &str) -> core::result::Result<Self, IO> {
156 match name {
157 "15-1 RM" | "15-1 RM prep" | "15-to-1 RM" | "15-to-1 RM prep" => {
158 Ok(Self::create_distillation_unit_15_to_1_rm_prep_template())
159 }
160 "15-1 space-efficient"
161 | "15-1 space efficient"
162 | "15-to-1 space-efficient"
163 | "15-to-1 space efficient" => {
164 Ok(Self::create_distillation_unit_15_to_1_rm_space_efficient_template())
165 }
166 _ => Err(CannotParseJSON(serde::de::Error::custom(format!(
167 "Invalid distillation unit specification name: {name}."
168 )))),
169 }
170 }
171
172 pub fn create_distillation_unit_15_to_1_rm_prep_template() -> Self {
173 Self {
174 name: String::from("15-to-1 RM prep"),
175 num_input_ts: 15,
176 num_output_ts: 1,
177 failure_probability_function: Box::new(Self::failure_probability),
178 output_error_rate_function: Box::new(Self::output_error_rate),
179 unit_type: TFactoryDistillationUnitType::Combined,
180 physical_qubit_specification: Some(TFactoryDistillationUnitResources {
181 num_unit_qubits: 31,
182 duration_in_qubit_cycle_time: 24,
183 }),
184 logical_qubit_specification: Some(TFactoryDistillationUnitResources {
185 num_unit_qubits: 31,
186 duration_in_qubit_cycle_time: 11,
187 }),
188
189 logical_qubit_specification_first_round_override: None,
190 }
191 }
192
193 pub fn create_distillation_unit_15_to_1_rm_space_efficient_template() -> Self {
194 Self {
195 name: String::from("15-to-1 space efficient"),
196 num_input_ts: 15,
197 num_output_ts: 1,
198 failure_probability_function: Box::new(Self::failure_probability),
199 output_error_rate_function: Box::new(Self::output_error_rate),
200 unit_type: TFactoryDistillationUnitType::Combined,
201 physical_qubit_specification: Some(TFactoryDistillationUnitResources {
202 num_unit_qubits: 12,
203 duration_in_qubit_cycle_time: 45,
204 }),
205 logical_qubit_specification: Some(TFactoryDistillationUnitResources {
206 num_unit_qubits: 20,
207 duration_in_qubit_cycle_time: 13,
208 }),
209 logical_qubit_specification_first_round_override: None,
210 }
211 }
212
213 pub fn create_trivial_distillation_unit_1_to_1() -> Self {
214 Self {
215 name: String::from("trivial 1-to-1"),
216 num_input_ts: 1,
217 num_output_ts: 1,
218 failure_probability_function: Box::new(Self::trivial_failure_probability),
219 output_error_rate_function: Box::new(Self::trivial_error_rate),
220 unit_type: TFactoryDistillationUnitType::Logical,
221 physical_qubit_specification: None,
222 logical_qubit_specification: Some(TFactoryDistillationUnitResources {
223 num_unit_qubits: 1,
224 duration_in_qubit_cycle_time: 1,
225 }),
226 logical_qubit_specification_first_round_override: None,
227 }
228 }
229
230 fn failure_probability(
231 input_error_rate: f64,
232 clifford_error_rate: f64,
233 _readout_error_rate: f64,
234 ) -> f64 {
235 15.0 * input_error_rate + 356.0 * clifford_error_rate
236 }
237
238 fn output_error_rate(
239 input_error_rate: f64,
240 clifford_error_rate: f64,
241 _readout_error_rate: f64,
242 ) -> f64 {
243 35.0 * input_error_rate.powi(3) + 7.1 * clifford_error_rate
244 }
245
246 fn trivial_failure_probability(
247 _input_error_rate: f64,
248 _clifford_error_rate: f64,
249 _readout_error_rate: f64,
250 ) -> f64 {
251 0.0
252 }
253
254 fn trivial_error_rate(
255 input_error_rate: f64,
256 _clifford_error_rate: f64,
257 _readout_error_rate: f64,
258 ) -> f64 {
259 input_error_rate
260 }
261
262 pub fn default_distillation_unit_templates() -> Vec<Self> {
263 vec![
264 Self::create_distillation_unit_15_to_1_rm_prep_template(),
265 Self::create_distillation_unit_15_to_1_rm_space_efficient_template(),
266 ]
267 }
268}
269
270pub struct TFactoryDistillationUnit<'a> {
271 unit_type: TFactoryDistillationUnitType,
272 num_input_ts: u64,
273 num_output_ts: u64,
274 physical_qubits_at_first_round: u64,
275 physical_qubits_at_subsequent_rounds: u64,
276 duration_at_first_round: u64,
277 duration_at_subsequent_rounds: u64,
278 code_distance: u64,
279 failure_probability_formula: &'a dyn Fn(f64, f64, f64) -> f64,
280 output_error_rate_formula: &'a dyn Fn(f64, f64, f64) -> f64,
281 pub(crate) name: String,
282 clifford_error_rate: f64,
283 readout_error_rate: f64,
284 /// This is the qubit's T error rate that we need only to decide the input T
285 /// error rate for the first unit
286 qubit_t_error_rate: f64,
287}
288
289impl fmt::Debug for TFactoryDistillationUnit<'_> {
290 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
291 f.debug_struct("TFactoryDistillationUnit")
292 .field("unit_type", &self.unit_type.to_string())
293 .field("num_input_ts", &self.num_input_ts)
294 .field("num_output_ts", &self.num_output_ts)
295 .field(
296 "physical_qubits_at_first_round",
297 &self.physical_qubits_at_first_round,
298 )
299 .field(
300 "physical_qubits_at_subsequent_rounds",
301 &self.physical_qubits_at_subsequent_rounds,
302 )
303 .field("duration_at_first_round", &self.duration_at_first_round)
304 .field(
305 "duration_at_subsequent_rounds",
306 &self.duration_at_subsequent_rounds,
307 )
308 .field("code_distance", &self.code_distance)
309 .field("name", &self.name)
310 .field("clifford_error_rate", &self.clifford_error_rate)
311 .field("qubit_t_error_rate", &self.qubit_t_error_rate)
312 .finish()
313 }
314}
315
316impl<'a> TFactoryDistillationUnit<'a> {
317 pub fn by_template(
318 template: &'a TFactoryDistillationUnitTemplate,
319 qubit: &TFactoryQubit,
320 ) -> Self {
321 let code_distance = qubit.code_distance();
322
323 let specification_for_first_round = match (code_distance, template.unit_type) {
324 (
325 1,
326 TFactoryDistillationUnitType::Combined | TFactoryDistillationUnitType::Physical,
327 ) => &template.physical_qubit_specification,
328 (_, TFactoryDistillationUnitType::Physical) => &None,
329 (_, TFactoryDistillationUnitType::Combined | TFactoryDistillationUnitType::Logical) => {
330 if template
331 .logical_qubit_specification_first_round_override
332 .is_some()
333 {
334 &template.logical_qubit_specification_first_round_override
335 } else {
336 &template.logical_qubit_specification
337 }
338 }
339 };
340
341 let (physical_qubits_at_first_round, duration_at_first_round) =
342 specification_for_first_round
343 .as_ref()
344 .map(|spec| {
345 (
346 spec.num_unit_qubits * qubit.physical_qubits(),
347 spec.duration_in_qubit_cycle_time * qubit.cycle_time(),
348 )
349 })
350 .unwrap_or_default();
351
352 let specification_for_subsequent_rounds = match template.unit_type {
353 TFactoryDistillationUnitType::Physical => &None,
354 TFactoryDistillationUnitType::Combined | TFactoryDistillationUnitType::Logical => {
355 &template.logical_qubit_specification
356 }
357 };
358
359 let (physical_qubits_at_subsequent_rounds, duration_at_subsequent_rounds) =
360 specification_for_subsequent_rounds
361 .as_ref()
362 .map(|spec| {
363 (
364 spec.num_unit_qubits * qubit.physical_qubits(),
365 spec.duration_in_qubit_cycle_time * qubit.cycle_time(),
366 )
367 })
368 .unwrap_or_default();
369
370 let num_input_ts = template.num_input_ts;
371 let num_output_ts = template.num_output_ts;
372
373 let failure_probability_formula = &template.failure_probability_function;
374 let output_error_rate_formula = &template.output_error_rate_function;
375
376 let name = template.name.clone();
377
378 let clifford_error_rate = qubit.clifford_error_rate();
379 let qubit_t_error_rate = qubit.t_error_rate();
380 let readout_error_rate = qubit.readout_error_rate();
381
382 Self {
383 unit_type: template.unit_type,
384 num_input_ts,
385 num_output_ts,
386 physical_qubits_at_first_round,
387 physical_qubits_at_subsequent_rounds,
388 duration_at_first_round,
389 duration_at_subsequent_rounds,
390 code_distance,
391 name,
392 clifford_error_rate,
393 qubit_t_error_rate,
394 failure_probability_formula,
395 output_error_rate_formula,
396 readout_error_rate,
397 }
398 }
399
400 pub fn clifford_error_rate(&self) -> f64 {
401 self.clifford_error_rate
402 }
403
404 pub fn qubit_t_error_rate(&self) -> f64 {
405 self.qubit_t_error_rate
406 }
407
408 pub fn is_valid(&self) -> bool {
409 self.clifford_error_rate() <= 0.1 * self.qubit_t_error_rate
410 }
411}
412
413impl DistillationUnit<u64> for TFactoryDistillationUnit<'_> {
414 fn num_output_states(&self) -> u64 {
415 self.num_output_ts
416 }
417
418 fn num_input_states(&self) -> u64 {
419 self.num_input_ts
420 }
421
422 fn duration(&self, position: usize) -> u64 {
423 if position == 0 {
424 self.duration_at_first_round
425 } else {
426 self.duration_at_subsequent_rounds
427 }
428 }
429
430 fn physical_qubits(&self, position: usize) -> u64 {
431 if position == 0 {
432 self.physical_qubits_at_first_round
433 } else {
434 self.physical_qubits_at_subsequent_rounds
435 }
436 }
437
438 fn name(&self) -> &str {
439 &self.name
440 }
441
442 fn code_parameter(&self) -> Option<&u64> {
443 Some(&self.code_distance)
444 }
445
446 fn output_error_rate(&self, input_error_rate: f64) -> f64 {
447 (self.output_error_rate_formula)(
448 input_error_rate,
449 self.clifford_error_rate,
450 self.readout_error_rate,
451 )
452 }
453
454 fn failure_probability(&self, input_error_rate: f64) -> f64 {
455 (self.failure_probability_formula)(
456 input_error_rate,
457 self.clifford_error_rate,
458 self.readout_error_rate,
459 )
460 }
461}
462
463pub type TFactory = RoundBasedFactory<u64>;
464
465pub fn default_t_factory(logical_qubit: &LogicalPatch<Protocol>) -> TFactory {
466 let tfactory_qubit = TFactoryQubit::Logical(logical_qubit);
467 let template = TFactoryDistillationUnitTemplate::create_trivial_distillation_unit_1_to_1();
468 let unit = TFactoryDistillationUnit::by_template(&template, &tfactory_qubit);
469
470 let length = 1;
471 let t_error_rate = logical_qubit.logical_error_rate();
472 let failure_probability_requirement = 0.0;
473
474 let round = DistillationRound::new(&unit, failure_probability_requirement, 0);
475
476 let rounds = vec![round];
477 let input_t_error_rate_before_each_round = vec![t_error_rate; length + 1];
478 let failure_probability_after_each_round: Vec<f64> =
479 vec![failure_probability_requirement; length + 1];
480
481 TFactory::new(
482 length,
483 failure_probability_requirement,
484 rounds,
485 input_t_error_rate_before_each_round,
486 failure_probability_after_each_round,
487 )
488}
489
490impl Serialize for TFactory {
491 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
492 where
493 S: serde::Serializer,
494 {
495 let mut map = serializer.serialize_map(Some(8))?;
496
497 map.serialize_entry("physicalQubits", &self.physical_qubits())?;
498 map.serialize_entry("runtime", &self.duration())?;
499 map.serialize_entry("numTstates", &self.num_output_states())?;
500 map.serialize_entry("numInputTstates", &self.num_input_states())?;
501 map.serialize_entry("numRounds", &self.num_rounds())?;
502 map.serialize_entry("numUnitsPerRound", &self.num_units_per_round())?;
503 map.serialize_entry("unitNamePerRound", &self.unit_names())?;
504 map.serialize_entry("codeDistancePerRound", &self.code_parameter_per_round())?;
505 map.serialize_entry("physicalQubitsPerRound", &self.physical_qubits_per_round())?;
506 map.serialize_entry("runtimePerRound", &self.duration_per_round())?;
507 map.serialize_entry("logicalErrorRate", &self.output_error_rate())?;
508
509 map.end()
510 }
511}
512