From 3743568fce68b4ab1647cc3481edd37e7d4dfb16 Mon Sep 17 00:00:00 2001 From: Mathias Soeken Date: Sat, 5 Oct 2024 13:52:03 +0200 Subject: [PATCH 1/2] Method to prune error budget. --- .../examples/basic_logical_counts.rs | 6 +- resource_estimator/src/estimates.rs | 2 +- .../src/estimates/error_budget.rs | 21 ++- resource_estimator/src/estimates/layout.rs | 7 +- .../src/estimates/physical_estimation.rs | 120 +++++++++----- .../physical_estimation/estimate_frontier.rs | 38 +++-- .../estimate_without_restrictions.rs | 77 +++++++-- .../estimates/physical_estimation/result.rs | 7 +- resource_estimator/src/system.rs | 7 +- .../src/system/data/logical_counts.rs | 18 ++- resource_estimator/src/system/tests.rs | 152 ++++++++++-------- 11 files changed, 299 insertions(+), 156 deletions(-) diff --git a/resource_estimator/examples/basic_logical_counts.rs b/resource_estimator/examples/basic_logical_counts.rs index 1e1ed2227c..6190d6fee2 100644 --- a/resource_estimator/examples/basic_logical_counts.rs +++ b/resource_estimator/examples/basic_logical_counts.rs @@ -52,11 +52,13 @@ fn main() { // After we have set up all required inputs for the resource estimation // task, we can set up an estimation instance. - let estimation = PhysicalResourceEstimation::new(code, qubit, builder, logical_counts, budget); + let estimation = PhysicalResourceEstimation::new(code, qubit, builder, logical_counts); // In this example, we perform a standard estimation without any further // constraints. - let result = estimation.estimate().expect("estimation does not fail"); + let result = estimation + .estimate(&budget) + .expect("estimation does not fail"); // There is a lot of data contained in the resource estimation result // object, but in this sample we are only printing the total number of diff --git a/resource_estimator/src/estimates.rs b/resource_estimator/src/estimates.rs index 9d39df009b..b8c5d6f76a 100644 --- a/resource_estimator/src/estimates.rs +++ b/resource_estimator/src/estimates.rs @@ -4,7 +4,7 @@ mod error; pub use error::Error; mod error_budget; -pub use error_budget::ErrorBudget; +pub use error_budget::{ErrorBudget, ErrorBudgetStrategy}; mod error_correction; pub use error_correction::{ CodeWithThresholdAndDistance, CodeWithThresholdAndDistanceEvaluator, ErrorCorrection, diff --git a/resource_estimator/src/estimates/error_budget.rs b/resource_estimator/src/estimates/error_budget.rs index 22256e431d..1d2e993ff8 100644 --- a/resource_estimator/src/estimates/error_budget.rs +++ b/resource_estimator/src/estimates/error_budget.rs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -#[derive(Clone)] +#[derive(Debug, Clone)] pub struct ErrorBudget { /// Probability of at least one logical error logical: f64, @@ -36,15 +36,34 @@ impl ErrorBudget { self.logical } + pub fn set_logical(&mut self, logical: f64) { + self.logical = logical; + } + /// Get the error budget's tstates. #[must_use] pub fn magic_states(&self) -> f64 { self.magic_states } + pub fn set_magic_states(&mut self, magic_states: f64) { + self.magic_states = magic_states; + } + /// Get the error budget's rotations. #[must_use] pub fn rotations(&self) -> f64 { self.rotations } + + pub fn set_rotations(&mut self, rotations: f64) { + self.rotations = rotations; + } +} + +#[derive(Default, Copy, Clone)] +pub enum ErrorBudgetStrategy { + #[default] + Static, + PruneLogicalAndRotations, } diff --git a/resource_estimator/src/estimates/layout.rs b/resource_estimator/src/estimates/layout.rs index 46af664d56..3f8f2a6280 100644 --- a/resource_estimator/src/estimates/layout.rs +++ b/resource_estimator/src/estimates/layout.rs @@ -3,7 +3,7 @@ use serde::Serialize; -use super::ErrorBudget; +use super::{ErrorBudget, ErrorBudgetStrategy}; /// Trait to model post-layout logical overhead pub trait Overhead { @@ -23,6 +23,11 @@ pub trait Overhead { /// The index is used to indicate the type of magic states and must be /// supported by available factory builders in the physical estimation. fn num_magic_states(&self, budget: &ErrorBudget, index: usize) -> u64; + + /// When implemented, prunes the error budget with respect to the provided + /// strategy + #[allow(unused_variables)] + fn prune_error_budget(&self, budget: &mut ErrorBudget, strategy: ErrorBudgetStrategy) {} } /// This is the realized logical overhead after applying an error budget. This diff --git a/resource_estimator/src/estimates/physical_estimation.rs b/resource_estimator/src/estimates/physical_estimation.rs index 0b70fe7dcd..232c7c5dca 100644 --- a/resource_estimator/src/estimates/physical_estimation.rs +++ b/resource_estimator/src/estimates/physical_estimation.rs @@ -5,7 +5,10 @@ mod estimate_frontier; mod estimate_without_restrictions; mod result; -use super::{Error, ErrorBudget, ErrorCorrection, Factory, FactoryBuilder, LogicalPatch, Overhead}; +use super::{ + Error, ErrorBudget, ErrorBudgetStrategy, ErrorCorrection, Factory, FactoryBuilder, + LogicalPatch, Overhead, +}; use std::{borrow::Cow, rc::Rc}; use estimate_frontier::EstimateFrontier; @@ -18,12 +21,12 @@ pub struct PhysicalResourceEstimation { qubit: Rc, factory_builder: Builder, layout_overhead: Rc, - error_budget: ErrorBudget, // optional constraint parameters logical_depth_factor: Option, max_factories: Option, max_duration: Option, max_physical_qubits: Option, + error_budget_strategy: ErrorBudgetStrategy, } impl< @@ -37,18 +40,17 @@ impl< qubit: Rc, factory_builder: Builder, layout_overhead: Rc, - error_budget: ErrorBudget, ) -> Self { Self { ftp, qubit, factory_builder, layout_overhead, - error_budget, logical_depth_factor: None, max_factories: None, max_duration: None, max_physical_qubits: None, + error_budget_strategy: ErrorBudgetStrategy::default(), } } @@ -60,10 +62,6 @@ impl< &self.layout_overhead } - pub fn error_budget(&self) -> &ErrorBudget { - &self.error_budget - } - pub fn set_logical_depth_factor(&mut self, logical_depth_factor: f64) { self.logical_depth_factor = Some(logical_depth_factor); } @@ -77,6 +75,14 @@ impl< self.max_physical_qubits = Some(max_physical_qubits); } + pub fn error_budget_strategy(&self) -> ErrorBudgetStrategy { + self.error_budget_strategy + } + + pub fn set_error_budget_strategy(&mut self, error_budget_strategy: ErrorBudgetStrategy) { + self.error_budget_strategy = error_budget_strategy; + } + pub fn factory_builder(&self) -> &Builder { &self.factory_builder } @@ -85,42 +91,52 @@ impl< &mut self.factory_builder } - pub fn estimate(&self) -> Result, Error> { + pub fn estimate( + &self, + error_budget: &ErrorBudget, + ) -> Result, Error> { match (self.max_duration, self.max_physical_qubits) { - (None, None) => self.estimate_without_restrictions(), + (None, None) => self.estimate_without_restrictions(error_budget), (None, Some(max_physical_qubits)) => { - self.estimate_with_max_num_qubits(max_physical_qubits) + self.estimate_with_max_num_qubits(error_budget, max_physical_qubits) + } + (Some(max_duration), None) => { + self.estimate_with_max_duration(error_budget, max_duration) } - (Some(max_duration), None) => self.estimate_with_max_duration(max_duration), _ => Err(Error::BothDurationAndPhysicalQubitsProvided), } } pub fn build_frontier( &self, + error_budget: &ErrorBudget, ) -> Result>, Error> { - EstimateFrontier::new(self)?.estimate() + EstimateFrontier::new(self, error_budget)?.estimate() } pub fn estimate_without_restrictions( &self, + error_budget: &ErrorBudget, ) -> Result, Error> { - EstimateWithoutRestrictions::new(self).estimate() + EstimateWithoutRestrictions::new(self).estimate(error_budget) } fn compute_initial_optimization_values( &self, + error_budget: &ErrorBudget, ) -> Result, Error> { - let num_cycles_required_by_layout_overhead = self.compute_num_cycles()?; + let num_cycles_required_by_layout_overhead = self.compute_num_cycles(error_budget)?; // The required magic state error rate is computed by dividing the total // error budget for magic states by the number of magic states required // for the algorithm. - let required_logical_magic_state_error_rate = self.error_budget.magic_states() - / (self.layout_overhead.num_magic_states(&self.error_budget, 0) as f64); + let required_logical_magic_state_error_rate = error_budget.magic_states() + / (self.layout_overhead.num_magic_states(error_budget, 0) as f64); - let required_logical_error_rate = - self.required_logical_error_rate(num_cycles_required_by_layout_overhead); + let required_logical_error_rate = self.required_logical_error_rate( + error_budget.logical(), + num_cycles_required_by_layout_overhead, + ); let min_code_parameter = self.compute_code_parameter(required_logical_error_rate)?; @@ -135,6 +151,7 @@ impl< #[allow(clippy::too_many_lines)] pub fn estimate_with_max_duration( &self, + error_budget: &ErrorBudget, max_duration_in_nanoseconds: u64, ) -> Result, Error> { if self.factory_builder.num_magic_state_types() != 1 { @@ -146,9 +163,9 @@ impl< num_cycles_required_by_layout_overhead, required_logical_error_rate, required_logical_magic_state_error_rate, - } = self.compute_initial_optimization_values()?; + } = self.compute_initial_optimization_values(error_budget)?; - let num_magic_states = self.layout_overhead.num_magic_states(&self.error_budget, 0); + let num_magic_states = self.layout_overhead.num_magic_states(error_budget, 0); if num_magic_states == 0 { let logical_patch = LogicalPatch::new(&self.ftp, min_code_parameter, self.qubit.clone())?; @@ -159,6 +176,7 @@ impl< return Ok(PhysicalResourceEstimationResult::without_factories( self, logical_patch, + error_budget, num_cycles_required_by_layout_overhead, required_logical_error_rate, )); @@ -191,7 +209,7 @@ impl< } let max_num_cycles_allowed_by_error_rate = - self.logical_cycles_for_code_parameter(&code_parameter)?; + self.logical_cycles_for_code_parameter(error_budget.logical(), &code_parameter)?; if max_num_cycles_allowed_by_error_rate < num_cycles_required_by_layout_overhead { continue; @@ -230,8 +248,13 @@ impl< &logical_patch, max_num_cycles_allowed, ) { - let num_factories = - self.num_factories(&logical_patch, 0, &factory, max_num_cycles_allowed); + let num_factories = self.num_factories( + &logical_patch, + 0, + &factory, + error_budget, + max_num_cycles_allowed, + ); let num_cycles_required_for_magic_states = self .compute_num_cycles_required_for_magic_states( @@ -239,6 +262,7 @@ impl< num_factories, &factory, &logical_patch, + error_budget, ); // This num_cycles could be larger than num_cycles_required_by_layout_overhead @@ -256,6 +280,7 @@ impl< let result = PhysicalResourceEstimationResult::new( self, LogicalPatch::new(&self.ftp, code_parameter.clone(), self.qubit.clone())?, + error_budget, num_cycles, vec![Some(FactoryPart::new( factory.into_owned(), @@ -281,6 +306,7 @@ impl< #[allow(clippy::too_many_lines)] pub fn estimate_with_max_num_qubits( &self, + error_budget: &ErrorBudget, max_num_qubits: u64, ) -> Result, Error> { if self.factory_builder.num_magic_state_types() != 1 { @@ -292,9 +318,9 @@ impl< num_cycles_required_by_layout_overhead, required_logical_error_rate, required_logical_magic_state_error_rate, - } = self.compute_initial_optimization_values()?; + } = self.compute_initial_optimization_values(error_budget)?; - let num_magic_states = self.layout_overhead.num_magic_states(&self.error_budget, 0); + let num_magic_states = self.layout_overhead.num_magic_states(error_budget, 0); if num_magic_states == 0 { let logical_patch = LogicalPatch::new(&self.ftp, min_code_parameter, self.qubit.clone())?; @@ -302,6 +328,7 @@ impl< return Ok(PhysicalResourceEstimationResult::without_factories( self, logical_patch, + error_budget, num_cycles_required_by_layout_overhead, required_logical_error_rate, )); @@ -335,7 +362,7 @@ impl< max_num_qubits - physical_qubits_for_algorithm; let max_num_cycles_allowed_by_error_rate = - self.logical_cycles_for_code_parameter(&code_parameter)?; + self.logical_cycles_for_code_parameter(error_budget.logical(), &code_parameter)?; if max_num_cycles_allowed_by_error_rate < num_cycles_required_by_layout_overhead { continue; @@ -384,6 +411,7 @@ impl< num_factories, &factory, &logical_patch, + error_budget, ); let num_cycles = num_cycles_required_for_magic_states @@ -402,6 +430,7 @@ impl< let result = PhysicalResourceEstimationResult::new( self, logical_patch, + error_budget, num_cycles, vec![Some(FactoryPart::new( factory.into_owned(), @@ -433,12 +462,13 @@ impl< num_factories: u64, factory: &Builder::Factory, logical_patch: &LogicalPatch, + error_budget: &ErrorBudget, ) -> u64 { let magic_states_per_run = num_factories * factory.num_output_states(); let required_runs = self .layout_overhead - .num_magic_states(&self.error_budget, magic_state_index) + .num_magic_states(error_budget, magic_state_index) .div_ceil(magic_states_per_run); let required_duration = required_runs * factory.duration(); @@ -497,16 +527,18 @@ impl< num_logical_patches * patch.physical_qubits() } + fn volume(&self, num_cycles: u64) -> u64 { + self.layout_overhead.logical_qubits() * num_cycles + } + /// Computes required logical error rate for a logical operation one one /// qubit /// /// The logical volume is the number of logical qubits times the number of /// cycles. We obtain the required logical error rate by dividing the error /// budget for logical operations by the volume. - fn required_logical_error_rate(&self, num_cycles: u64) -> f64 { - let volume = self.layout_overhead.logical_qubits() * num_cycles; - - self.error_budget.logical() / volume as f64 + fn required_logical_error_rate(&self, logical_error_budget: f64, num_cycles: u64) -> f64 { + logical_error_budget / self.volume(num_cycles) as f64 } /// Computes the code parameter for the required logical error rate @@ -519,6 +551,7 @@ impl< /// Computes the number of possible cycles given a chosen code parameter fn logical_cycles_for_code_parameter( &self, + logical_error_budget: f64, code_parameter: &E::Parameter, ) -> Result { // Compute the achievable error rate for the code parameter @@ -527,15 +560,16 @@ impl< .logical_error_rate(&self.qubit, code_parameter) .map_err(Error::LogicalErrorRateComputationFailed)?; - Ok((self.error_budget.logical() - / (self.layout_overhead.logical_qubits() as f64 * error_rate)) - .floor() as u64) + Ok( + (logical_error_budget / (self.layout_overhead.logical_qubits() as f64 * error_rate)) + .floor() as u64, + ) } // Possibly adjusts number of cycles C from initial starting point C_min - fn compute_num_cycles(&self) -> Result { + fn compute_num_cycles(&self, error_budget: &ErrorBudget) -> Result { // Start loop with C = C_min - let mut num_cycles = self.layout_overhead.logical_depth(&self.error_budget); + let mut num_cycles = self.layout_overhead.logical_depth(error_budget); // Perform logical depth scaling if given by constraint if let Some(logical_depth_scaling) = self.logical_depth_factor { @@ -545,11 +579,8 @@ impl< // We cannot perform resource estimation when there are neither magic states nor cycles if num_cycles == 0 - && (0..self.factory_builder.num_magic_state_types()).all(|index| { - self.layout_overhead - .num_magic_states(&self.error_budget, index) - == 0 - }) + && (0..self.factory_builder.num_magic_state_types()) + .all(|index| self.layout_overhead.num_magic_states(error_budget, index) == 0) { return Err(Error::AlgorithmHasNoResources); } @@ -565,6 +596,7 @@ impl< logical_patch: &LogicalPatch, magic_state_index: usize, factory: &Builder::Factory, + error_budget: &ErrorBudget, num_cycles: u64, ) -> u64 { // first, try with the exact calculation; if that does not work, use @@ -574,12 +606,12 @@ impl< let num_states_per_run = (total_duration / factory.duration()) * factory.num_output_states(); self.layout_overhead - .num_magic_states(&self.error_budget, magic_state_index) + .num_magic_states(error_budget, magic_state_index) .div_ceil(num_states_per_run) } else { let magic_states_per_cycles = self.layout_overhead - .num_magic_states(&self.error_budget, magic_state_index) as f64 + .num_magic_states(error_budget, magic_state_index) as f64 / (factory.num_output_states() * num_cycles) as f64; let factory_duration_fraction = diff --git a/resource_estimator/src/estimates/physical_estimation/estimate_frontier.rs b/resource_estimator/src/estimates/physical_estimation/estimate_frontier.rs index dc1047ddda..15f1582713 100644 --- a/resource_estimator/src/estimates/physical_estimation/estimate_frontier.rs +++ b/resource_estimator/src/estimates/physical_estimation/estimate_frontier.rs @@ -2,7 +2,7 @@ use std::{borrow::Cow, ops::Deref}; use crate::estimates::{ optimization::{Point2D, Population}, - Error, ErrorCorrection, Factory, FactoryBuilder, LogicalPatch, Overhead, + Error, ErrorBudget, ErrorCorrection, Factory, FactoryBuilder, LogicalPatch, Overhead, }; use super::{ @@ -12,6 +12,7 @@ use super::{ pub struct EstimateFrontier<'a, E: ErrorCorrection, B: FactoryBuilder, L> { estimator: &'a PhysicalResourceEstimation, + error_budget: ErrorBudget, min_cycles: u64, required_logical_error_rate: f64, required_logical_magic_state_error_rate: f64, @@ -25,26 +26,27 @@ impl< L: Overhead, > EstimateFrontier<'a, E, B, L> { - pub fn new(estimator: &'a PhysicalResourceEstimation) -> Result { + pub fn new( + estimator: &'a PhysicalResourceEstimation, + error_budget: &ErrorBudget, + ) -> Result { if estimator.factory_builder.num_magic_state_types() == 1 { - let min_cycles = estimator.compute_num_cycles()?; + let min_cycles = estimator.compute_num_cycles(error_budget)?; - let required_logical_error_rate = estimator.required_logical_error_rate(min_cycles); + let required_logical_error_rate = + estimator.required_logical_error_rate(error_budget.logical(), min_cycles); // The required magic state error rate is computed by dividing the total // error budget for magic states by the number of magic states required // for the algorithm. - let required_logical_magic_state_error_rate = estimator.error_budget.magic_states() - / estimator - .layout_overhead - .num_magic_states(&estimator.error_budget, 0) as f64; + let required_logical_magic_state_error_rate = error_budget.magic_states() + / estimator.layout_overhead.num_magic_states(error_budget, 0) as f64; - let num_magic_states = estimator - .layout_overhead - .num_magic_states(&estimator.error_budget, 0); + let num_magic_states = estimator.layout_overhead.num_magic_states(error_budget, 0); Ok(Self { estimator, + error_budget: error_budget.clone(), min_cycles, required_logical_error_rate, required_logical_magic_state_error_rate, @@ -65,6 +67,7 @@ impl< return Ok(vec![PhysicalResourceEstimationResult::without_factories( self, logical_patch, + &self.error_budget, self.min_cycles, self.required_logical_error_rate, )]); @@ -139,7 +142,7 @@ impl< LogicalPatch::new(&self.ftp, code_parameter.clone(), self.qubit.clone())?; let max_num_cycles_allowed_by_error_rate = - self.logical_cycles_for_code_parameter(code_parameter)?; + self.logical_cycles_for_code_parameter(self.error_budget.logical(), code_parameter)?; if max_num_cycles_allowed_by_error_rate < self.min_cycles { return Ok(()); @@ -157,8 +160,13 @@ impl< // Here we compute the number of factories required limited by the // maximum number of cycles allowed by the duration constraint (and // the error rate). - let min_num_factories = - self.num_factories(&logical_patch, 0, &factory, max_num_cycles_allowed); + let min_num_factories = self.num_factories( + &logical_patch, + 0, + &factory, + &self.error_budget, + max_num_cycles_allowed, + ); for num_factories in min_num_factories.. { let num_cycles_required_for_magic_states = self @@ -167,6 +175,7 @@ impl< num_factories, factory.as_ref(), &logical_patch, + &self.error_budget, ); // This num_cycles could be larger than min_cycles but must @@ -185,6 +194,7 @@ impl< let result = PhysicalResourceEstimationResult::new( self, LogicalPatch::new(&self.ftp, code_parameter.clone(), self.qubit.clone())?, + &self.error_budget, num_cycles, vec![Some(factory_part)], self.required_logical_error_rate, diff --git a/resource_estimator/src/estimates/physical_estimation/estimate_without_restrictions.rs b/resource_estimator/src/estimates/physical_estimation/estimate_without_restrictions.rs index 9d6a874ea3..83109f2c1b 100644 --- a/resource_estimator/src/estimates/physical_estimation/estimate_without_restrictions.rs +++ b/resource_estimator/src/estimates/physical_estimation/estimate_without_restrictions.rs @@ -1,6 +1,9 @@ use std::{borrow::Cow, ops::Deref}; -use crate::estimates::{Error, ErrorCorrection, Factory, FactoryBuilder, LogicalPatch, Overhead}; +use crate::estimates::{ + Error, ErrorBudget, ErrorBudgetStrategy, ErrorCorrection, Factory, FactoryBuilder, + LogicalPatch, Overhead, +}; use super::{ FactoryForCycles, FactoryPart, PhysicalResourceEstimation, PhysicalResourceEstimationResult, @@ -21,14 +24,39 @@ impl< Self { estimator } } - pub fn estimate(&self) -> Result, Error> { - let mut num_cycles = self.compute_num_cycles()?; + pub fn estimate( + &self, + error_budget: &ErrorBudget, + ) -> Result, Error> { + let mut num_cycles = self.compute_num_cycles(error_budget)?; loop { - let required_logical_error_rate = self.required_logical_error_rate(num_cycles); + let mut error_budget = error_budget.clone(); + + self.layout_overhead() + .prune_error_budget(&mut error_budget, self.error_budget_strategy()); + + let required_logical_error_rate = + self.required_logical_error_rate(error_budget.logical(), num_cycles); let code_parameter = self.compute_code_parameter(required_logical_error_rate)?; - let max_allowed_num_cycles_for_code_parameter = - self.logical_cycles_for_code_parameter(&code_parameter)?; + + let max_allowed_num_cycles_for_code_parameter = match self.error_budget_strategy { + ErrorBudgetStrategy::Static => { + self.logical_cycles_for_code_parameter(error_budget.logical(), &code_parameter)? + } + ErrorBudgetStrategy::PruneLogicalAndRotations => { + let new_logical = self + .ftp + .logical_error_rate(&self.qubit, &code_parameter) + .map_err(Error::LogicalErrorRateComputationFailed)? + * (self.volume(num_cycles) as f64); + let diff = error_budget.logical() - new_logical; + error_budget.set_logical(new_logical); + let new_magic_states = error_budget.magic_states() + diff; + error_budget.set_magic_states(new_magic_states); + num_cycles + } + }; let logical_patch = LogicalPatch::new(&self.ftp, code_parameter.clone(), self.qubit.clone())?; @@ -40,6 +68,7 @@ impl< &logical_patch, num_cycles, max_allowed_num_cycles_for_code_parameter, + &error_budget, index, )? { FactoryPartsResult::NoMagicStates => { @@ -62,6 +91,7 @@ impl< return Ok(PhysicalResourceEstimationResult::new( self, logical_patch, + &error_budget, num_cycles, factory_parts, required_logical_error_rate, @@ -77,17 +107,16 @@ impl< logical_patch: &LogicalPatch, min_cycles: u64, max_cycles: u64, + error_budget: &ErrorBudget, index: usize, ) -> Result, Error> { - let num_magic_states = self - .layout_overhead - .num_magic_states(&self.error_budget, index); + let num_magic_states = self.layout_overhead.num_magic_states(error_budget, index); if num_magic_states == 0 { return Ok(FactoryPartsResult::NoMagicStates); } - let required_logical_magic_state_error_rate = (self.error_budget.magic_states() + let required_logical_magic_state_error_rate = (error_budget.magic_states() / self.factory_builder.num_magic_state_types() as f64) / (num_magic_states as f64); @@ -111,10 +140,21 @@ impl< if let Some(FactoryForCycles { factory, num_cycles: num_cycles_required, - }) = self.find_factory(index, &factories, logical_patch, min_cycles, max_cycles) - { - let num_factories = - self.num_factories(logical_patch, index, &factory, num_cycles_required); + }) = self.find_factory( + index, + &factories, + logical_patch, + error_budget, + min_cycles, + max_cycles, + ) { + let num_factories = self.num_factories( + logical_patch, + index, + &factory, + error_budget, + num_cycles_required, + ); Ok(FactoryPartsResult::Success { factory_part: FactoryPart::new( factory.into_owned(), @@ -134,6 +174,7 @@ impl< magic_state_index: usize, factories: &[Cow<'b, B::Factory>], logical_patch: &LogicalPatch, + error_budget: &ErrorBudget, min_cycles: u64, max_cycles: u64, ) -> Option> { @@ -147,6 +188,7 @@ impl< && self.is_max_factories_constraint_satisfied( logical_patch, factory, + error_budget, min_cycles, ) }) @@ -161,6 +203,7 @@ impl< magic_state_index, factories, logical_patch, + error_budget, max_cycles, ) { return Some(factory); @@ -174,6 +217,7 @@ impl< magic_state_index: usize, factories: &[Cow<'b, B::Factory>], logical_patch: &LogicalPatch, + error_budget: &ErrorBudget, max_cycles: u64, ) -> Option> { self.max_factories.map_or_else( @@ -196,7 +240,7 @@ impl< let magic_states_per_run = max_factories * factory.num_output_states(); let required_runs = self .layout_overhead - .num_magic_states(&self.error_budget, magic_state_index) + .num_magic_states(error_budget, magic_state_index) .div_ceil(magic_states_per_run); let required_duration = required_runs * factory.duration(); let num = required_duration.div_ceil(logical_patch.logical_cycle_time()); @@ -215,10 +259,11 @@ impl< &self, logical_patch: &LogicalPatch, factory: &B::Factory, + error_budget: &ErrorBudget, num_cycles: u64, ) -> bool { self.max_factories.map_or(true, |max_factories| { - max_factories >= self.num_factories(logical_patch, 0, factory, num_cycles) + max_factories >= self.num_factories(logical_patch, 0, factory, error_budget, num_cycles) }) } } diff --git a/resource_estimator/src/estimates/physical_estimation/result.rs b/resource_estimator/src/estimates/physical_estimation/result.rs index 08691b8ce1..cf4937182c 100644 --- a/resource_estimator/src/estimates/physical_estimation/result.rs +++ b/resource_estimator/src/estimates/physical_estimation/result.rs @@ -37,6 +37,7 @@ impl, F: Factory, logical_patch: LogicalPatch, + error_budget: &ErrorBudget, num_cycles: u64, factory_parts: Vec>>, required_logical_error_rate: f64, @@ -71,10 +72,10 @@ impl, F: Factory, F: Factory, logical_patch: LogicalPatch, + error_budget: &ErrorBudget, num_cycles: u64, required_logical_patch_error_rate: f64, ) -> Self { Self::new( estimation, logical_patch, + error_budget, num_cycles, std::iter::repeat(()) .map(|()| None) diff --git a/resource_estimator/src/system.rs b/resource_estimator/src/system.rs index dda190d7e4..ea49779b85 100644 --- a/resource_estimator/src/system.rs +++ b/resource_estimator/src/system.rs @@ -98,7 +98,6 @@ fn estimate_single { - let estimation_result = estimation.estimate().map_err(std::convert::Into::into); + let estimation_result = estimation + .estimate(&partitioning) + .map_err(std::convert::Into::into); estimation_result .map(|result| data::Success::new(job_params, logical_resources, result)) } diff --git a/resource_estimator/src/system/data/logical_counts.rs b/resource_estimator/src/system/data/logical_counts.rs index a4dfde3df9..f61dc19ba9 100644 --- a/resource_estimator/src/system/data/logical_counts.rs +++ b/resource_estimator/src/system/data/logical_counts.rs @@ -2,7 +2,7 @@ // Licensed under the MIT License. use crate::{ - estimates::{ErrorBudget, Overhead}, + estimates::{ErrorBudget, ErrorBudgetStrategy, Overhead}, system::constants::{ NUM_MEASUREMENTS_PER_R, NUM_MEASUREMENTS_PER_TOF, NUM_TS_PER_ROTATION_A_COEFFICIENT, NUM_TS_PER_ROTATION_B_COEFFICIENT, @@ -77,6 +77,22 @@ impl Overhead for LogicalResourceCounts { .unwrap_or_default() * self.rotation_count } + + fn prune_error_budget(&self, budget: &mut ErrorBudget, strategy: ErrorBudgetStrategy) { + if matches![strategy, ErrorBudgetStrategy::PruneLogicalAndRotations] { + if let Some(num_ts_per_rotation) = self.num_ts_per_rotation(budget.rotations()) { + let new_rotations_budget = (self.rotation_count as f64) + / 2.0_f64.powf( + ((num_ts_per_rotation as f64) - NUM_TS_PER_ROTATION_B_COEFFICIENT) + / NUM_TS_PER_ROTATION_A_COEFFICIENT, + ); + + let diff = budget.rotations() - new_rotations_budget; + budget.set_rotations(new_rotations_budget); + budget.set_magic_states(budget.magic_states() + diff); + } + } + } } impl PartitioningOverhead for LogicalResourceCounts { diff --git a/resource_estimator/src/system/tests.rs b/resource_estimator/src/system/tests.rs index 3cdb50657f..842b00c2af 100644 --- a/resource_estimator/src/system/tests.rs +++ b/resource_estimator/src/system/tests.rs @@ -5,8 +5,8 @@ use serde_json::{json, Map, Value}; use crate::{ estimates::{ - ErrorBudget, ErrorCorrection, Factory, FactoryBuilder, FactoryPart, Overhead, - PhysicalResourceEstimation, PhysicalResourceEstimationResult, + ErrorBudget, ErrorBudgetStrategy, ErrorCorrection, Factory, FactoryBuilder, FactoryPart, + Overhead, PhysicalResourceEstimation, PhysicalResourceEstimationResult, }, system::modeling::{floquet_code, surface_code_gate_based}, }; @@ -148,10 +148,9 @@ pub fn test_no_tstates() { qubit, create_factory_builder(), Rc::new(layout_overhead), - partitioning, ); - assert!(estimation.estimate().is_err()); + assert!(estimation.estimate(&partitioning).is_err()); } #[test] @@ -167,10 +166,9 @@ pub fn single_tstate() -> Result<()> { qubit, create_factory_builder(), Rc::new(layout_overhead), - partitioning, ); - estimation.estimate()?; + estimation.estimate(&partitioning)?; Ok(()) } @@ -191,10 +189,9 @@ pub fn perfect_tstate() -> Result<()> { qubit, create_factory_builder(), Rc::new(layout_overhead), - partitioning, ); - estimation.estimate()?; + estimation.estimate(&partitioning)?; Ok(()) } @@ -237,10 +234,9 @@ pub fn test_hubbard_e2e() -> Result<()> { qubit.clone(), create_factory_builder(), Rc::new(layout_overhead), - partitioning, ); - let result = estimation.estimate()?; + let result = estimation.estimate(&partitioning)?; let part = get_factory(&result); let logical_qubit = result.logical_patch(); @@ -334,10 +330,9 @@ pub fn test_hubbard_e2e_measurement_based() -> Result<()> { qubit.clone(), create_factory_builder(), Rc::new(layout_overhead), - partitioning, ); - let result = estimation.estimate()?; + let result = estimation.estimate(&partitioning)?; let part = get_factory(&result); let logical_qubit = result.logical_patch(); @@ -428,14 +423,15 @@ pub fn test_hubbard_e2e_increasing_max_duration() -> Result<()> { qubit, create_factory_builder(), Rc::new(layout_overhead), - partitioning, ); let max_duration_in_nanoseconds1: u64 = 50_000_000_u64; let max_duration_in_nanoseconds2: u64 = 500_000_000_u64; - let result1 = estimation.estimate_with_max_duration(max_duration_in_nanoseconds1)?; - let result2 = estimation.estimate_with_max_duration(max_duration_in_nanoseconds2)?; + let result1 = + estimation.estimate_with_max_duration(&partitioning, max_duration_in_nanoseconds1)?; + let result2 = + estimation.estimate_with_max_duration(&partitioning, max_duration_in_nanoseconds2)?; assert!(result1.runtime() <= max_duration_in_nanoseconds1); assert!(result2.runtime() <= max_duration_in_nanoseconds2); @@ -456,14 +452,13 @@ pub fn test_hubbard_e2e_increasing_max_num_qubits() -> Result<()> { qubit, create_factory_builder(), Rc::new(layout_overhead), - partitioning, ); let max_num_qubits1: u64 = 11000; let max_num_qubits2: u64 = 20000; - let result1 = estimation.estimate_with_max_num_qubits(max_num_qubits1)?; - let result2 = estimation.estimate_with_max_num_qubits(max_num_qubits2)?; + let result1 = estimation.estimate_with_max_num_qubits(&partitioning, max_num_qubits1)?; + let result2 = estimation.estimate_with_max_num_qubits(&partitioning, max_num_qubits2)?; assert!(result1.physical_qubits() <= max_num_qubits1); assert!(result2.physical_qubits() <= max_num_qubits2); @@ -474,8 +469,10 @@ pub fn test_hubbard_e2e_increasing_max_num_qubits() -> Result<()> { Ok(()) } -fn prepare_chemistry_estimation_with_expected_majorana( -) -> PhysicalResourceEstimation { +fn prepare_chemistry_estimation_with_expected_majorana() -> ( + PhysicalResourceEstimation, + ErrorBudget, +) { let ftp = floquet_code(); let qubit = Rc::new(PhysicalQubit::qubit_maj_ns_e6()); @@ -494,11 +491,8 @@ fn prepare_chemistry_estimation_with_expected_majorana( let partitioning = ErrorBudgetSpecification::Total(0.01) .partitioning(&counts) .expect("partitioning should succeed"); - PhysicalResourceEstimation::new( - ftp, - qubit, - create_factory_builder(), - Rc::new(counts), + ( + PhysicalResourceEstimation::new(ftp, qubit, create_factory_builder(), Rc::new(counts)), partitioning, ) } @@ -507,9 +501,9 @@ fn prepare_chemistry_estimation_with_expected_majorana( pub fn test_chemistry_small_max_duration() { let max_duration_in_nanoseconds: u64 = 1_000_000_000_u64; - let estimation = prepare_chemistry_estimation_with_expected_majorana(); + let (estimation, budget) = prepare_chemistry_estimation_with_expected_majorana(); - let result = estimation.estimate_with_max_duration(max_duration_in_nanoseconds); + let result = estimation.estimate_with_max_duration(&budget, max_duration_in_nanoseconds); match result { Err(crate::estimates::Error::MaxDurationTooSmall) => {} @@ -520,9 +514,9 @@ pub fn test_chemistry_small_max_duration() { #[test] pub fn test_chemistry_small_max_num_qubits() { let max_num_qubits: u64 = 10_000; - let estimation = prepare_chemistry_estimation_with_expected_majorana(); + let (estimation, budget) = prepare_chemistry_estimation_with_expected_majorana(); - let result = estimation.estimate_with_max_num_qubits(max_num_qubits); + let result = estimation.estimate_with_max_num_qubits(&budget, max_num_qubits); match result { Err(crate::estimates::Error::MaxPhysicalQubitsTooSmall) => {} @@ -534,9 +528,9 @@ pub fn test_chemistry_small_max_num_qubits() { pub fn test_chemistry_based_max_duration() -> Result<()> { let max_duration_in_nanoseconds: u64 = 365 * 24 * 3600 * 1_000_000_000_u64; - let estimation = prepare_chemistry_estimation_with_expected_majorana(); + let (estimation, budget) = prepare_chemistry_estimation_with_expected_majorana(); - let result = estimation.estimate_with_max_duration(max_duration_in_nanoseconds)?; + let result = estimation.estimate_with_max_duration(&budget, max_duration_in_nanoseconds)?; let part = get_factory(&result); let logical_qubit = result.logical_patch(); @@ -591,9 +585,9 @@ pub fn test_chemistry_based_max_duration() -> Result<()> { pub fn test_chemistry_based_max_num_qubits() -> Result<()> { let max_num_qubits: u64 = 4_923_120; - let estimation = prepare_chemistry_estimation_with_expected_majorana(); + let (estimation, budget) = prepare_chemistry_estimation_with_expected_majorana(); - let result = estimation.estimate_with_max_num_qubits(max_num_qubits)?; + let result = estimation.estimate_with_max_num_qubits(&budget, max_num_qubits)?; let part = get_factory(&result); let logical_qubit = result.logical_patch(); @@ -643,8 +637,10 @@ pub fn test_chemistry_based_max_num_qubits() -> Result<()> { Ok(()) } -fn prepare_factorization_estimation_with_optimistic_majorana( -) -> PhysicalResourceEstimation { +fn prepare_factorization_estimation_with_optimistic_majorana() -> ( + PhysicalResourceEstimation, + ErrorBudget, +) { let ftp = floquet_code(); let qubit = Rc::new(PhysicalQubit::qubit_maj_ns_e6()); @@ -663,26 +659,23 @@ fn prepare_factorization_estimation_with_optimistic_majorana( let partitioning = ErrorBudgetSpecification::Total(1e-3) .partitioning(&counts) .expect("partitioning should succeed"); - PhysicalResourceEstimation::new( - ftp, - qubit, - create_factory_builder(), - Rc::new(counts), + ( + PhysicalResourceEstimation::new(ftp, qubit, create_factory_builder(), Rc::new(counts)), partitioning, ) } #[test] pub fn test_factorization_2048_max_duration_matches_regular_estimate() -> Result<()> { - let estimation = prepare_factorization_estimation_with_optimistic_majorana(); + let (estimation, budget) = prepare_factorization_estimation_with_optimistic_majorana(); - let result_no_max_duration = estimation.estimate_without_restrictions()?; + let result_no_max_duration = estimation.estimate_without_restrictions(&budget)?; let part_no_max_duration = get_factory(&result_no_max_duration); let logical_qubit_no_max_duration = result_no_max_duration.logical_patch(); let max_duration_in_nanoseconds: u64 = result_no_max_duration.runtime(); - let result = estimation.estimate_with_max_duration(max_duration_in_nanoseconds)?; + let result = estimation.estimate_with_max_duration(&budget, max_duration_in_nanoseconds)?; let part = get_factory(&result); let logical_qubit = result.logical_patch(); @@ -726,15 +719,15 @@ pub fn test_factorization_2048_max_duration_matches_regular_estimate() -> Result #[test] pub fn test_factorization_2048_max_num_qubits_matches_regular_estimate() -> Result<()> { - let estimation = prepare_factorization_estimation_with_optimistic_majorana(); + let (estimation, budget) = prepare_factorization_estimation_with_optimistic_majorana(); - let result_no_max_num_qubits = estimation.estimate_without_restrictions()?; + let result_no_max_num_qubits = estimation.estimate_without_restrictions(&budget)?; let part_no_max_num_qubits = get_factory(&result_no_max_num_qubits); let logical_qubit_no_max_num_qubits = result_no_max_num_qubits.logical_patch(); let max_num_qubits = result_no_max_num_qubits.physical_qubits(); - let result = estimation.estimate_with_max_num_qubits(max_num_qubits)?; + let result = estimation.estimate_with_max_num_qubits(&budget, max_num_qubits)?; let part = get_factory(&result); let logical_qubit = result.logical_patch(); @@ -776,8 +769,10 @@ pub fn test_factorization_2048_max_num_qubits_matches_regular_estimate() -> Resu Ok(()) } -fn prepare_ising20x20_estimation_with_pessimistic_gate_based( -) -> PhysicalResourceEstimation { +fn prepare_ising20x20_estimation_with_pessimistic_gate_based() -> ( + PhysicalResourceEstimation, + ErrorBudget, +) { let ftp = surface_code_gate_based(); let qubit = Rc::new(PhysicalQubit::qubit_gate_us_e3()); @@ -796,11 +791,8 @@ fn prepare_ising20x20_estimation_with_pessimistic_gate_based( let partitioning = ErrorBudgetSpecification::Total(1e-3) .partitioning(&counts) .expect("cannot setup error budget partitioning"); - PhysicalResourceEstimation::new( - ftp, - qubit, - create_factory_builder(), - Rc::new(counts), + ( + PhysicalResourceEstimation::new(ftp, qubit, create_factory_builder(), Rc::new(counts)), partitioning, ) } @@ -808,10 +800,10 @@ fn prepare_ising20x20_estimation_with_pessimistic_gate_based( #[test] fn test_chemistry_based_max_factories() { for max_factories in 1..=14 { - let mut estimation = prepare_chemistry_estimation_with_expected_majorana(); + let (mut estimation, budget) = prepare_chemistry_estimation_with_expected_majorana(); estimation.set_max_factories(max_factories); - let result = estimation.estimate().expect("failed to estimate"); + let result = estimation.estimate(&budget).expect("failed to estimate"); let actual_factories = result.factory_parts()[0] .as_ref() .expect("has factories") @@ -824,11 +816,31 @@ fn test_chemistry_based_max_factories() { } } +#[test] +fn test_budget_pruning() { + let (mut estimation, budget) = prepare_ising20x20_estimation_with_pessimistic_gate_based(); + estimation.set_error_budget_strategy(ErrorBudgetStrategy::Static); + let result1 = estimation.estimate(&budget).expect("failed to estimate"); + + estimation.set_error_budget_strategy(ErrorBudgetStrategy::PruneLogicalAndRotations); + let result2 = estimation.estimate(&budget).expect("failed to estimate"); + + assert_eq!(result1.physical_qubits(), 2_938_540); + assert_eq!(result1.runtime(), 1_734_282_000_000); + + assert_eq!(result2.physical_qubits(), 2_154_380); + assert_eq!(result2.runtime(), 1_734_282_000_000); + + assert!(result1.error_budget().logical() >= result2.error_budget().logical()); + assert!(result1.error_budget().rotations() >= result2.error_budget().rotations()); + assert!(result1.error_budget().magic_states() <= result2.error_budget().magic_states()); +} + #[test] fn build_frontier_test() { - let estimation = prepare_ising20x20_estimation_with_pessimistic_gate_based(); + let (estimation, budget) = prepare_ising20x20_estimation_with_pessimistic_gate_based(); - let frontier_result = estimation.build_frontier(); + let frontier_result = estimation.build_frontier(&budget); let points = frontier_result.expect("failed to estimate"); assert_eq!(points.len(), 189); @@ -843,7 +855,7 @@ fn build_frontier_test() { ); } - let shortest_runtime_result = estimation.estimate().expect("failed to estimate"); + let shortest_runtime_result = estimation.estimate(&budget).expect("failed to estimate"); assert_eq!(points[0].runtime(), shortest_runtime_result.runtime()); assert_eq!( points[0].physical_qubits(), @@ -864,7 +876,7 @@ fn build_frontier_test() { for _ in 0..num_iterations { max_duration = (max_duration as f64 * coefficient) as u64; let result = estimation - .estimate_with_max_duration(max_duration) + .estimate_with_max_duration(&budget, max_duration) .expect("failed to estimate"); assert!( @@ -882,7 +894,7 @@ fn build_frontier_test() { let coefficient = 1.10; for _ in 0..num_iterations { max_num_qubits = (max_num_qubits as f64 / coefficient) as u64; - let result = estimation.estimate_with_max_num_qubits(max_num_qubits); + let result = estimation.estimate_with_max_num_qubits(&budget, max_num_qubits); if let Ok(result) = result { assert!( @@ -897,8 +909,10 @@ fn build_frontier_test() { } } -fn prepare_bit_flip_code_resources_and_majorana_n6_qubit( -) -> PhysicalResourceEstimation { +fn prepare_bit_flip_code_resources_and_majorana_n6_qubit() -> ( + PhysicalResourceEstimation, + ErrorBudget, +) { let qubit = Rc::new(PhysicalQubit::qubit_maj_ns_e6()); let ftp = floquet_code(); @@ -917,27 +931,23 @@ fn prepare_bit_flip_code_resources_and_majorana_n6_qubit( let partitioning = ErrorBudgetSpecification::Total(1e-3) .partitioning(&counts) .expect("cannot setup error budget partitioning"); - PhysicalResourceEstimation::new( - ftp, - qubit, - create_factory_builder(), - Rc::new(counts), + ( + PhysicalResourceEstimation::new(ftp, qubit, create_factory_builder(), Rc::new(counts)), partitioning, ) } #[test] fn build_frontier_bit_flip_code_test() { - let estimation: PhysicalResourceEstimation = - prepare_bit_flip_code_resources_and_majorana_n6_qubit(); + let (estimation, budget) = prepare_bit_flip_code_resources_and_majorana_n6_qubit(); - let frontier_result = estimation.build_frontier(); + let frontier_result = estimation.build_frontier(&budget); let points = frontier_result.expect("failed to estimate"); assert_eq!(points.len(), 10); let part0 = get_factory(&points[0]); - let shortest_runtime_result = estimation.estimate().expect("failed to estimate"); + let shortest_runtime_result = estimation.estimate(&budget).expect("failed to estimate"); let part_shortest_runtime = get_factory(&shortest_runtime_result); assert_eq!(points[0].runtime(), shortest_runtime_result.runtime()); From dabfcb983c4a5892fb1545afb3a214c107bc1c9d Mon Sep 17 00:00:00 2001 From: Mathias Soeken Date: Tue, 8 Oct 2024 19:09:22 +0200 Subject: [PATCH 2/2] No pruning when max_factories constraint is set. --- .../estimate_without_restrictions.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/resource_estimator/src/estimates/physical_estimation/estimate_without_restrictions.rs b/resource_estimator/src/estimates/physical_estimation/estimate_without_restrictions.rs index 83109f2c1b..b9534293c0 100644 --- a/resource_estimator/src/estimates/physical_estimation/estimate_without_restrictions.rs +++ b/resource_estimator/src/estimates/physical_estimation/estimate_without_restrictions.rs @@ -30,6 +30,15 @@ impl< ) -> Result, Error> { let mut num_cycles = self.compute_num_cycles(error_budget)?; + // NOTE: for now we reset the error_budget_strategy if also + // max_factories is set, because it may lead to an inconsistent + // configuration. + let adjusted_strategy = if self.max_factories.is_some() { + ErrorBudgetStrategy::Static + } else { + self.error_budget_strategy + }; + loop { let mut error_budget = error_budget.clone(); @@ -40,7 +49,7 @@ impl< self.required_logical_error_rate(error_budget.logical(), num_cycles); let code_parameter = self.compute_code_parameter(required_logical_error_rate)?; - let max_allowed_num_cycles_for_code_parameter = match self.error_budget_strategy { + let max_allowed_num_cycles_for_code_parameter = match adjusted_strategy { ErrorBudgetStrategy::Static => { self.logical_cycles_for_code_parameter(error_budget.logical(), &code_parameter)? }