diff --git a/compiler/qsc_eval/src/intrinsic.rs b/compiler/qsc_eval/src/intrinsic.rs index bacf573037..24498d970f 100644 --- a/compiler/qsc_eval/src/intrinsic.rs +++ b/compiler/qsc_eval/src/intrinsic.rs @@ -77,7 +77,7 @@ pub(crate) fn call( Err(_) => Err(Error::OutputFail(name_span)), } } - "Relabel" => qubit_relabel(arg, arg_span, |q0, q1| sim.qubit_swap_id(q0, q1)), + "PermuteLabels" => qubit_relabel(arg, arg_span, |q0, q1| sim.qubit_swap_id(q0, q1)), "Message" => match out.message(&arg.unwrap_string()) { Ok(()) => Ok(Value::unit()), Err(_) => Err(Error::OutputFail(name_span)), diff --git a/compiler/qsc_eval/src/tests.rs b/compiler/qsc_eval/src/tests.rs index 35fe245b1d..56aadd3261 100644 --- a/compiler/qsc_eval/src/tests.rs +++ b/compiler/qsc_eval/src/tests.rs @@ -3724,7 +3724,7 @@ fn controlled_operation_with_duplicate_controls_fails() { 1, ), item: LocalItemId( - 131, + 132, ), }, caller: PackageId( @@ -3774,7 +3774,7 @@ fn controlled_operation_with_target_in_controls_fails() { 1, ), item: LocalItemId( - 131, + 132, ), }, caller: PackageId( diff --git a/compiler/qsc_partial_eval/src/lib.rs b/compiler/qsc_partial_eval/src/lib.rs index cfe0ff749f..3f29fffbb3 100644 --- a/compiler/qsc_partial_eval/src/lib.rs +++ b/compiler/qsc_partial_eval/src/lib.rs @@ -1293,7 +1293,7 @@ impl<'a> PartialEvaluator<'a> { // Qubit allocations and measurements have special handling. "__quantum__rt__qubit_allocate" => Ok(self.allocate_qubit()), "__quantum__rt__qubit_release" => Ok(self.release_qubit(args_value)), - "Relabel" => qubit_relabel(args_value, args_span, |q0, q1| { + "PermuteLabels" => qubit_relabel(args_value, args_span, |q0, q1| { self.resource_manager.swap_qubit_ids(Qubit(q0), Qubit(q1)); }) .map_err(std::convert::Into::into), diff --git a/library/src/tests/canon.rs b/library/src/tests/canon.rs index f43ba87eab..dd14c276b2 100644 --- a/library/src/tests/canon.rs +++ b/library/src/tests/canon.rs @@ -176,6 +176,27 @@ fn check_relabel_four_qubit_shuffle_permutation() { ); } +#[test] +fn check_relabel_adjoint_undoes_permutation() { + test_expression( + "{ + use qs = Qubit[3]; + // Prepare |01+⟩ + X(qs[1]); + H(qs[2]); + Relabel([qs[0], qs[1], qs[2]], [qs[1], qs[2], qs[0]]); + // Expected state is |1+0⟩, perform part of the adjoint to correct one of the qubits. + X(qs[0]); + Adjoint Relabel([qs[0], qs[1], qs[2]], [qs[1], qs[2], qs[0]]); + // Expected state is now |00+⟩, perform the rest of the adjoint to get back to ground state, + // using the original qubit ids. + H(qs[2]); + // Qubit release will fail if the state is not |000⟩ + }", + &Value::unit(), + ); +} + #[test] fn check_apply_cnot_chain_2() { test_expression( diff --git a/library/std/src/Std/Canon.qs b/library/std/src/Std/Canon.qs index cec6d95896..8a9c2f753d 100644 --- a/library/std/src/Std/Canon.qs +++ b/library/std/src/Std/Canon.qs @@ -583,8 +583,7 @@ operation ApplyXorInPlaceL(value : BigInt, target : Qubit[]) : Unit is Adj + Ctl } adjoint self; } - -/// # Summary + /// # Summary /// Relabels the qubits in the `current` array with the qubits in the `updated` array. The `updated` array /// must be a valid permutation of the `current` array. /// @@ -612,7 +611,18 @@ operation ApplyXorInPlaceL(value : BigInt, target : Qubit[]) : Unit is Adj + Ctl /// use (q0, q1) = (Qubit(), Qubit()); /// Relabel([q0, q1], [q1, q0]); /// ``` -operation Relabel(current : Qubit[], updated : Qubit[]) : Unit { +/// Note that the adjoint of this operation effectively changes the order of arguments, such that +/// `Adjoint Relabel(qubits, newOrder)` is equivalent to `Relabel(newOrder, qubits)`. +operation Relabel(current : Qubit[], updated : Qubit[]) : Unit is Adj { + body ... { + PermuteLabels(current, updated); + } + adjoint ... { + PermuteLabels(updated, current); + } +} + +operation PermuteLabels(current : Qubit[], updated : Qubit[]) : Unit { body intrinsic; } diff --git a/resource_estimator/src/estimates.rs b/resource_estimator/src/estimates.rs index 1065f7a8e1..6d9f93b985 100644 --- a/resource_estimator/src/estimates.rs +++ b/resource_estimator/src/estimates.rs @@ -19,6 +19,6 @@ pub use physical_estimation::{ }; mod layout; mod logical_qubit; -pub use layout::Overhead; +pub use layout::{Overhead, RealizedOverhead}; pub use logical_qubit::LogicalPatch; pub mod optimization; diff --git a/resource_estimator/src/estimates/layout.rs b/resource_estimator/src/estimates/layout.rs index fb97e7b51b..46af664d56 100644 --- a/resource_estimator/src/estimates/layout.rs +++ b/resource_estimator/src/estimates/layout.rs @@ -1,6 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +use serde::Serialize; + use super::ErrorBudget; /// Trait to model post-layout logical overhead @@ -22,3 +24,66 @@ pub trait Overhead { /// supported by available factory builders in the physical estimation. fn num_magic_states(&self, budget: &ErrorBudget, index: usize) -> u64; } + +/// This is the realized logical overhead after applying an error budget. This +/// structure has two purposes: 1) it is used to store the realized logical +/// overhead, once the error budget partition is decided into the resource +/// estimation result; 2) it can be used to pass a logical overhead to the +/// resource estimation API, if it does not depend on the error budget, since it +/// also implements the [`Overhead`] trait. +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +pub struct RealizedOverhead { + logical_qubits: u64, + logical_depth: u64, + num_magic_states: Vec, +} + +impl RealizedOverhead { + pub fn from_overhead( + overhead: &impl Overhead, + budget: &ErrorBudget, + num_magic_state_types: usize, + ) -> Self { + let logical_qubits = overhead.logical_qubits(); + let logical_depth = overhead.logical_depth(budget); + let num_magic_states = (0..num_magic_state_types) + .map(|index| overhead.num_magic_states(budget, index)) + .collect(); + + Self { + logical_qubits, + logical_depth, + num_magic_states, + } + } + + #[must_use] + pub fn logical_qubits(&self) -> u64 { + self.logical_qubits + } + + #[must_use] + pub fn logical_depth(&self) -> u64 { + self.logical_depth + } + + #[must_use] + pub fn num_magic_states(&self) -> &[u64] { + &self.num_magic_states + } +} + +impl Overhead for RealizedOverhead { + fn logical_qubits(&self) -> u64 { + self.logical_qubits + } + + fn logical_depth(&self, _budget: &ErrorBudget) -> u64 { + self.logical_depth + } + + fn num_magic_states(&self, _budget: &ErrorBudget, index: usize) -> u64 { + self.num_magic_states[index] + } +} diff --git a/resource_estimator/src/estimates/physical_estimation.rs b/resource_estimator/src/estimates/physical_estimation.rs index d70b943645..b33db8854e 100644 --- a/resource_estimator/src/estimates/physical_estimation.rs +++ b/resource_estimator/src/estimates/physical_estimation.rs @@ -253,6 +253,10 @@ impl< } } + pub fn error_correction(&self) -> &E { + &self.ftp + } + pub fn layout_overhead(&self) -> &L { &self.layout_overhead } @@ -274,13 +278,15 @@ impl< self.max_physical_qubits = Some(max_physical_qubits); } + pub fn factory_builder(&self) -> &Builder { + &self.factory_builder + } + pub fn factory_builder_mut(&mut self) -> &mut Builder { &mut self.factory_builder } - pub fn estimate( - &self, - ) -> Result, Error> { + pub fn estimate(&self) -> Result, Error> { match (self.max_duration, self.max_physical_qubits) { (None, None) => self.estimate_without_restrictions(), (None, Some(max_physical_qubits)) => { @@ -294,7 +300,7 @@ impl< #[allow(clippy::too_many_lines, clippy::type_complexity)] pub fn build_frontier( &self, - ) -> Result>, Error> { + ) -> Result>, Error> { if self.factory_builder.num_magic_state_types() != 1 { return Err(Error::MultipleMagicStatesNotSupported); } @@ -329,7 +335,7 @@ impl< } let mut best_estimation_results = - Population::>>::new(); + Population::>>::new(); let mut last_factories = Vec::new(); let mut last_code_parameter = None; @@ -455,7 +461,7 @@ impl< pub fn estimate_without_restrictions( &self, - ) -> Result, Error> { + ) -> Result, Error> { let mut num_cycles = self.compute_num_cycles()?; loop { @@ -616,7 +622,7 @@ impl< pub fn estimate_with_max_duration( &self, max_duration_in_nanoseconds: u64, - ) -> Result, Error> { + ) -> Result, Error> { if self.factory_builder.num_magic_state_types() != 1 { return Err(Error::MultipleMagicStatesNotSupported); } @@ -647,7 +653,7 @@ impl< } let mut best_estimation_result: Option< - PhysicalResourceEstimationResult, + PhysicalResourceEstimationResult, > = None; let mut last_factories = Vec::new(); @@ -767,7 +773,7 @@ impl< pub fn estimate_with_max_num_qubits( &self, max_num_qubits: u64, - ) -> Result, Error> { + ) -> Result, Error> { if self.factory_builder.num_magic_state_types() != 1 { return Err(Error::MultipleMagicStatesNotSupported); } @@ -795,7 +801,7 @@ impl< } let mut best_estimation_result: Option< - PhysicalResourceEstimationResult, + PhysicalResourceEstimationResult, > = None; let mut last_factories = Vec::new(); diff --git a/resource_estimator/src/estimates/physical_estimation/result.rs b/resource_estimator/src/estimates/physical_estimation/result.rs index eecd63f66f..08691b8ce1 100644 --- a/resource_estimator/src/estimates/physical_estimation/result.rs +++ b/resource_estimator/src/estimates/physical_estimation/result.rs @@ -1,19 +1,17 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use std::rc::Rc; - use serde::Serialize; use crate::estimates::{ ErrorBudget, ErrorCorrection, Factory, FactoryBuilder, LogicalPatch, Overhead, - PhysicalResourceEstimation, + PhysicalResourceEstimation, RealizedOverhead, }; /// Resource estimation result #[derive(Serialize)] #[serde(rename_all = "camelCase")] -pub struct PhysicalResourceEstimationResult { +pub struct PhysicalResourceEstimationResult { #[serde(bound = "E::Parameter: Serialize")] logical_patch: LogicalPatch, num_cycles: u64, @@ -25,19 +23,19 @@ pub struct PhysicalResourceEstimationResult { physical_qubits: u64, runtime: u64, rqops: u64, - #[serde(skip)] - layout_overhead: Rc, + layout_overhead: RealizedOverhead, error_budget: ErrorBudget, } -impl< - E: ErrorCorrection, - F: Factory + Clone, - L: Overhead, - > PhysicalResourceEstimationResult +impl, F: Factory + Clone> + PhysicalResourceEstimationResult { pub fn new( - estimation: &PhysicalResourceEstimation, L>, + estimation: &PhysicalResourceEstimation< + E, + impl FactoryBuilder, + impl Overhead, + >, logical_patch: LogicalPatch, num_cycles: u64, factory_parts: Vec>>, @@ -71,13 +69,21 @@ impl< physical_qubits, runtime, rqops, - layout_overhead: estimation.layout_overhead.clone(), + layout_overhead: RealizedOverhead::from_overhead( + estimation.layout_overhead(), + estimation.error_budget(), + estimation.factory_builder().num_magic_state_types(), + ), error_budget: estimation.error_budget().clone(), } } pub fn without_factories( - estimation: &PhysicalResourceEstimation, L>, + estimation: &PhysicalResourceEstimation< + E, + impl FactoryBuilder, + impl Overhead, + >, logical_patch: LogicalPatch, num_cycles: u64, required_logical_patch_error_rate: f64, @@ -136,7 +142,7 @@ impl< self.rqops } - pub fn layout_overhead(&self) -> &Rc { + pub fn layout_overhead(&self) -> &RealizedOverhead { &self.layout_overhead } @@ -145,14 +151,13 @@ impl< } pub fn algorithmic_logical_depth(&self) -> u64 { - self.layout_overhead.logical_depth(&self.error_budget) + self.layout_overhead.logical_depth() } /// The argument index indicates for which type of magic state (starting /// from 0) the number is requested for. pub fn num_magic_states(&self, index: usize) -> u64 { - self.layout_overhead - .num_magic_states(&self.error_budget, index) + self.layout_overhead.num_magic_states()[index] } } diff --git a/resource_estimator/src/system.rs b/resource_estimator/src/system.rs index 9dcbd8e458..a4b4571fdb 100644 --- a/resource_estimator/src/system.rs +++ b/resource_estimator/src/system.rs @@ -88,6 +88,8 @@ fn estimate_single { let estimation_result = estimation.estimate().map_err(std::convert::Into::into); - estimation_result.map(|result| data::Success::new(job_params, result)) + estimation_result + .map(|result| data::Success::new(job_params, logical_resources, result)) } } } diff --git a/resource_estimator/src/system/data/report.rs b/resource_estimator/src/system/data/report.rs index d80647ee7e..1dd1d15566 100644 --- a/resource_estimator/src/system/data/report.rs +++ b/resource_estimator/src/system/data/report.rs @@ -8,7 +8,7 @@ mod tests; use serde::Serialize; -use crate::estimates::{Factory, FactoryPart, Overhead, PhysicalResourceEstimationResult}; +use crate::estimates::{Factory, FactoryPart, PhysicalResourceEstimationResult}; use crate::system::modeling::Protocol; use super::LayoutReportData; @@ -28,14 +28,11 @@ impl Report { #[allow(clippy::vec_init_then_push, clippy::too_many_lines)] pub fn new( job_params: &JobParams, - result: &PhysicalResourceEstimationResult< - Protocol, - TFactory, - impl Overhead + LayoutReportData, - >, + layout_report_data: &impl LayoutReportData, + result: &PhysicalResourceEstimationResult, formatted_counts: &FormattedPhysicalResourceCounts, ) -> Self { - let logical_counts = result.layout_overhead(); + let logical_counts = layout_report_data; // In this system, we consider T as the only magic state type, therefore // there is only one factory part in the result. let part = result.factory_parts()[0].as_ref(); @@ -363,12 +360,9 @@ pub struct FormattedPhysicalResourceCounts { impl FormattedPhysicalResourceCounts { #[allow(clippy::too_many_lines)] pub fn new( - result: &PhysicalResourceEstimationResult< - Protocol, - TFactory, - impl Overhead + LayoutReportData, - >, + result: &PhysicalResourceEstimationResult, job_params: &JobParams, + layout_report_data: &impl LayoutReportData, ) -> Self { // Physical resource estimates let runtime = format_duration(result.runtime().into()); @@ -474,16 +468,16 @@ impl FormattedPhysicalResourceCounts { }); // Pre-layout logical resources - let logical_counts_num_qubits = format_metric_prefix(result.layout_overhead().num_qubits()); - let logical_counts_t_count = format_metric_prefix(result.layout_overhead().t_count()); + let logical_counts_num_qubits = format_metric_prefix(layout_report_data.num_qubits()); + let logical_counts_t_count = format_metric_prefix(layout_report_data.t_count()); let logical_counts_rotation_count = - format_metric_prefix(result.layout_overhead().rotation_count()); + format_metric_prefix(layout_report_data.rotation_count()); let logical_counts_rotation_depth = - format_metric_prefix(result.layout_overhead().rotation_depth()); - let logical_counts_ccz_count = format_metric_prefix(result.layout_overhead().ccz_count()); - let logical_counts_ccix_count = format_metric_prefix(result.layout_overhead().ccix_count()); + format_metric_prefix(layout_report_data.rotation_depth()); + let logical_counts_ccz_count = format_metric_prefix(layout_report_data.ccz_count()); + let logical_counts_ccix_count = format_metric_prefix(layout_report_data.ccix_count()); let logical_counts_measurement_count = - format_metric_prefix(result.layout_overhead().measurement_count()); + format_metric_prefix(layout_report_data.measurement_count()); // Assumed error budget let error_budget = format!("{:.2e}", job_params.error_budget().total()); @@ -491,8 +485,7 @@ impl FormattedPhysicalResourceCounts { let error_budget_tstates = format!("{:.2e}", result.error_budget().magic_states()); let error_budget_rotations = format!("{:.2e}", result.error_budget().rotations()); - let num_ts_per_rotation = result - .layout_overhead() + let num_ts_per_rotation = layout_report_data .num_ts_per_rotation(result.error_budget().rotations()) .map_or_else(|| String::from(no_rotations_msg), format_metric_prefix); diff --git a/resource_estimator/src/system/data/result.rs b/resource_estimator/src/system/data/result.rs index 05c1edc177..85dd4dfdcc 100644 --- a/resource_estimator/src/system/data/result.rs +++ b/resource_estimator/src/system/data/result.rs @@ -4,9 +4,7 @@ use std::ops::Deref; use std::rc::Rc; -use crate::estimates::{ - ErrorBudget, FactoryPart, LogicalPatch, Overhead, PhysicalResourceEstimationResult, -}; +use crate::estimates::{ErrorBudget, FactoryPart, LogicalPatch, PhysicalResourceEstimationResult}; use crate::system::modeling::{Protocol, TFactory}; use super::LayoutReportData; @@ -19,7 +17,7 @@ use serde::{ser::SerializeMap, Serialize, Serializer}; #[derive(Serialize)] #[serde(rename_all(serialize = "camelCase"))] -pub struct Success { +pub struct Success { status: &'static str, job_params: JobParams, #[serde(skip_serializing_if = "Option::is_none")] @@ -38,19 +36,24 @@ pub struct Success { frontier_entries: Vec, } -impl Success { +impl Success { pub fn new( job_params: JobParams, - result: PhysicalResourceEstimationResult, + layout_report_data: Rc, + result: PhysicalResourceEstimationResult, ) -> Self { - let counts = create_physical_resource_counts(&result); + let counts = create_physical_resource_counts(&result, layout_report_data.as_ref()); let formatted_counts: FormattedPhysicalResourceCounts = - FormattedPhysicalResourceCounts::new(&result, &job_params); + FormattedPhysicalResourceCounts::new(&result, &job_params, layout_report_data.as_ref()); - let report_data = Report::new(&job_params, &result, &formatted_counts); + let report_data = Report::new( + &job_params, + layout_report_data.as_ref(), + &result, + &formatted_counts, + ); - let logical_counts = result.layout_overhead().clone(); let (logical_qubit, mut parts, error_budget) = result.take(); let tfactory = parts.swap_remove(0).map(FactoryPart::into_factory); @@ -62,7 +65,7 @@ impl Success { logical_qubit: Some(LogicalQubit(logical_qubit)), tfactory, error_budget: Some(error_budget), - logical_counts, + logical_counts: layout_report_data, report_data, frontier_entries: Vec::new(), } @@ -70,19 +73,22 @@ impl Success { pub fn new_from_multiple( job_params: JobParams, - mut results: Vec>, + layout_report_data: Rc, + mut results: Vec>, ) -> Self { let mut report_data: Option = None; let mut frontier_entries: Vec = Vec::new(); - let logical_counts = results[0].layout_overhead().clone(); - // we will pick the shortest runtime result as the first result. results.sort_by_key(PhysicalResourceEstimationResult::runtime); for result in results { - let (frontier_entry, report) = - create_frontier_entry(&job_params, result, report_data.is_none()); + let (frontier_entry, report) = create_frontier_entry( + &job_params, + result, + layout_report_data.as_ref(), + report_data.is_none(), + ); if report_data.is_none() { report_data = Some(report.expect("error should have report")); @@ -99,7 +105,7 @@ impl Success { logical_qubit: None, tfactory: None, error_budget: None, - logical_counts, + logical_counts: layout_report_data, report_data: report_data.expect("error should have report"), // Here we assume that at least a single solution was found. frontier_entries, } @@ -118,16 +124,22 @@ pub struct FrontierEntry { fn create_frontier_entry( job_params: &JobParams, - result: PhysicalResourceEstimationResult, + result: PhysicalResourceEstimationResult, + layout_report_data: &impl LayoutReportData, create_report: bool, ) -> (FrontierEntry, Option) { - let physical_counts = create_physical_resource_counts(&result); + let physical_counts = create_physical_resource_counts(&result, layout_report_data); let physical_counts_formatted: FormattedPhysicalResourceCounts = - FormattedPhysicalResourceCounts::new(&result, job_params); + FormattedPhysicalResourceCounts::new(&result, job_params, layout_report_data); let report_data = if create_report { - Some(Report::new(job_params, &result, &physical_counts_formatted)) + Some(Report::new( + job_params, + layout_report_data, + &result, + &physical_counts_formatted, + )) } else { None }; @@ -148,9 +160,10 @@ fn create_frontier_entry( } fn create_physical_resource_counts( - result: &PhysicalResourceEstimationResult, + result: &PhysicalResourceEstimationResult, + layout_report_data: &impl LayoutReportData, ) -> PhysicalResourceCounts { - let breakdown = create_physical_resource_counts_breakdown(result); + let breakdown = create_physical_resource_counts_breakdown(result, layout_report_data); PhysicalResourceCounts { physical_qubits: result.physical_qubits(), @@ -161,24 +174,20 @@ fn create_physical_resource_counts( } fn create_physical_resource_counts_breakdown( - result: &PhysicalResourceEstimationResult, + result: &PhysicalResourceEstimationResult, + layout_report_data: &impl LayoutReportData, ) -> PhysicalResourceCountsBreakdown { - let num_ts_per_rotation = result - .layout_overhead() - .num_ts_per_rotation(result.error_budget().rotations()); + let num_ts_per_rotation = + layout_report_data.num_ts_per_rotation(result.error_budget().rotations()); let part = result.factory_parts()[0].as_ref(); PhysicalResourceCountsBreakdown { algorithmic_logical_qubits: result.layout_overhead().logical_qubits(), - algorithmic_logical_depth: result - .layout_overhead() - .logical_depth(result.error_budget()), + algorithmic_logical_depth: result.layout_overhead().logical_depth(), logical_depth: result.num_cycles(), clock_frequency: result.logical_patch().logical_cycles_per_second(), - num_tstates: result - .layout_overhead() - .num_magic_states(result.error_budget(), 0), + num_tstates: result.layout_overhead().num_magic_states()[0], num_tfactories: part.map_or(0, FactoryPart::copies), num_tfactory_runs: part.map_or(0, FactoryPart::runs), physical_qubits_for_tfactories: result.physical_qubits_for_factories(), diff --git a/resource_estimator/src/system/tests.rs b/resource_estimator/src/system/tests.rs index bfbe96169f..3cdb50657f 100644 --- a/resource_estimator/src/system/tests.rs +++ b/resource_estimator/src/system/tests.rs @@ -209,9 +209,7 @@ fn hubbard_overhead_and_partitioning() -> Result<(LogicalResourceCounts, ErrorBu Ok((logical_counts, partitioning)) } -fn validate_result_invariants( - result: &PhysicalResourceEstimationResult, -) { +fn validate_result_invariants(result: &PhysicalResourceEstimationResult) { let part = get_factory(result); assert_eq!( @@ -1056,7 +1054,7 @@ fn strip_numbers(value: &Value) -> Value { // In this system, there is only one magic state type, T states, and therefore // one factory part in the result with information on the factory. fn get_factory( - result: &PhysicalResourceEstimationResult, + result: &PhysicalResourceEstimationResult, ) -> &FactoryPart { result.factory_parts()[0] .as_ref()