microsoft/qdk
Publicmirrored from https://github.com/microsoft/qdkAvailable
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)] |
| 5 | mod tests; |
| 6 | |
| 7 | use core::fmt; |
| 8 | use std::{collections::BTreeMap, fmt::Display, vec}; |
| 9 | |
| 10 | use serde::{Serialize, ser::SerializeMap}; |
| 11 | |
| 12 | use crate::estimates::{ |
| 13 | DistillationRound, DistillationUnit, Factory, LogicalPatch, RoundBasedFactory, |
| 14 | }; |
| 15 | |
| 16 | use super::{ |
| 17 | super::{ |
| 18 | compiled_expression::CompiledExpression, |
| 19 | error::IO::{self, CannotParseJSON}, |
| 20 | }, |
| 21 | PhysicalQubit, Protocol, |
| 22 | }; |
| 23 | |
| 24 | pub enum TFactoryQubit<'a> { |
| 25 | Logical(&'a LogicalPatch<Protocol>), |
| 26 | Physical(&'a PhysicalQubit), |
| 27 | } |
| 28 | |
| 29 | impl 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 | |
| 83 | impl 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` |
| 96 | pub type TFactoryFormula = Box<dyn Fn(f64, f64, f64) -> f64>; |
| 97 | |
| 98 | impl 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)] |
| 119 | pub enum TFactoryDistillationUnitType { |
| 120 | Logical, |
| 121 | Physical, |
| 122 | Combined, |
| 123 | } |
| 124 | |
| 125 | pub 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 | |
| 132 | pub 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 | |
| 154 | impl 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 | |
| 270 | pub 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 | |
| 289 | impl 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 | |
| 316 | impl<'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 | |
| 413 | impl 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 | |
| 463 | pub type TFactory = RoundBasedFactory<u64>; |
| 464 | |
| 465 | pub 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 | |
| 490 | impl 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 | |