From abd8323510b2376efdac4ad22283015d6a2e25f2 Mon Sep 17 00:00:00 2001 From: Matthew Harrigan Date: Tue, 23 Jul 2024 17:41:19 -0700 Subject: [PATCH] [surface code] Organize --- qualtran/surface_code/__init__.py | 43 ++--- ...odel.ipynb => beverland_et_al_model.ipynb} | 42 ++-- ...cost_model.py => beverland_et_al_model.py} | 8 +- ..._test.py => beverland_et_al_model_test.py} | 15 +- qualtran/surface_code/ccz2t_factory.py | 157 +++++++++++++++ qualtran/surface_code/ccz2t_factory_test.py | 37 ++++ qualtran/surface_code/data_block.py | 10 +- qualtran/surface_code/data_block_test.py | 4 +- ...en_to_one.py => fifteen_to_one_factory.py} | 8 +- ...test.py => fifteen_to_one_factory_test.py} | 5 +- ...t_cost_model.py => gidney_fowler_model.py} | 179 +++--------------- ...el_test.py => gidney_fowler_model_test.py} | 4 +- ...sical_cost.py => physical_cost_summary.py} | 2 +- qualtran/surface_code/physical_parameters.py | 20 +- ...ection_scheme_summary.py => qec_scheme.py} | 93 +++------ qualtran/surface_code/qec_scheme_test.py | 56 ++++++ ...um_error_correction_scheme_summary_test.py | 63 ------ qualtran/surface_code/thc_compilation.ipynb | 15 +- qualtran/surface_code/ui.py | 68 ++++--- qualtran/surface_code/ui_test.py | 6 +- 20 files changed, 435 insertions(+), 400 deletions(-) rename qualtran/surface_code/{azure_cost_model.ipynb => beverland_et_al_model.ipynb} (95%) rename qualtran/surface_code/{azure_cost_model.py => beverland_et_al_model.py} (96%) rename qualtran/surface_code/{azure_cost_model_test.py => beverland_et_al_model_test.py} (87%) create mode 100644 qualtran/surface_code/ccz2t_factory.py create mode 100644 qualtran/surface_code/ccz2t_factory_test.py rename qualtran/surface_code/{fifteen_to_one.py => fifteen_to_one_factory.py} (98%) rename qualtran/surface_code/{fifteen_to_one_test.py => fifteen_to_one_factory_test.py} (89%) rename qualtran/surface_code/{ccz2t_cost_model.py => gidney_fowler_model.py} (56%) rename qualtran/surface_code/{ccz2t_cost_model_test.py => gidney_fowler_model_test.py} (95%) rename qualtran/surface_code/{physical_cost.py => physical_cost_summary.py} (97%) rename qualtran/surface_code/{quantum_error_correction_scheme_summary.py => qec_scheme.py} (56%) create mode 100644 qualtran/surface_code/qec_scheme_test.py delete mode 100644 qualtran/surface_code/quantum_error_correction_scheme_summary_test.py diff --git a/qualtran/surface_code/__init__.py b/qualtran/surface_code/__init__.py index 4ae5de414..00959d3b8 100644 --- a/qualtran/surface_code/__init__.py +++ b/qualtran/surface_code/__init__.py @@ -12,38 +12,35 @@ # See the License for the specific language governing permissions and # limitations under the License. -from qualtran.surface_code.algorithm_summary import AlgorithmSummary -from qualtran.surface_code.ccz2t_cost_model import ( - CCZ2TFactory, - get_ccz2t_costs, - get_ccz2t_costs_from_error_budget, - get_ccz2t_costs_from_grid_search, -) -from qualtran.surface_code.data_block import ( +# isort:skip_file + +from .algorithm_summary import AlgorithmSummary +from .physical_cost_summary import PhysicalCostsSummary +from .physical_parameters import PhysicalParameters +from .qec_scheme import LogicalErrorModel, QECScheme +from .magic_state_factory import MagicStateFactory +from .ccz2t_factory import CCZ2TFactory +from .fifteen_to_one_factory import FifteenToOne +from .data_block import ( CompactDataBlock, DataBlock, FastDataBlock, IntermediateDataBlock, SimpleDataBlock, ) -from qualtran.surface_code.magic_count import MagicCount -from qualtran.surface_code.magic_state_factory import MagicStateFactory -from qualtran.surface_code.multi_factory import MultiFactory -from qualtran.surface_code.physical_cost import PhysicalCost -from qualtran.surface_code.physical_parameters import PhysicalParameters -from qualtran.surface_code.quantum_error_correction_scheme_summary import ( - BeverlandMajoranaQubits, - BeverlandSuperconductingQubits, - BeverlandTrappedIonQubits, - FowlerSuperconductingQubits, - LogicalErrorModel, - QuantumErrorCorrectionSchemeSummary, -) -from qualtran.surface_code.reference import Reference -from qualtran.surface_code.rotation_cost_model import ( +from .multi_factory import MultiFactory +from .rotation_cost_model import ( BeverlandEtAlRotationCost, ConstantWithOverheadRotationCost, RotationCostModel, RotationLogarithmicModel, SevenDigitsOfPrecisionConstantCost, ) +from .gidney_fowler_model import ( + get_ccz2t_costs, + get_ccz2t_costs_from_error_budget, + get_ccz2t_costs_from_grid_search, + iter_ccz2t_factories, + iter_simple_data_blocks, +) +from .reference import Reference diff --git a/qualtran/surface_code/azure_cost_model.ipynb b/qualtran/surface_code/beverland_et_al_model.ipynb similarity index 95% rename from qualtran/surface_code/azure_cost_model.ipynb rename to qualtran/surface_code/beverland_et_al_model.ipynb index 55f327f88..a3c6f95f5 100644 --- a/qualtran/surface_code/azure_cost_model.ipynb +++ b/qualtran/surface_code/beverland_et_al_model.ipynb @@ -5,7 +5,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# Azure Cost Model\n", + "# Beverland et. al. Model\n", "In this notebook, we reproduce the physical resource estimates in \"Assessing requirements to scale to practical quantum advantage\" by [Beverland et al](https://arxiv.org/abs/2211.07629), Appendix F.\n", "\n", "The paper describes the formulas used for estimating cost in the various appendices. The final estimation procedure is put together in Appendix E and we reproduce the values found in Appendix F." @@ -49,10 +49,20 @@ "source": [ "from qualtran.surface_code import PhysicalParameters\n", "\n", - "beverland_phys_params = PhysicalParameters.from_beverland('superconducting', optimistic_err_rate=True)\n", + "beverland_phys_params = PhysicalParameters.make_beverland_et_al('superconducting', optimistic_err_rate=True)\n", "beverland_phys_params" ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Compare and contrast with the Gidney and Fowler assumptions\n", + "PhysicalParameters.make_gidney_fowler()" + ] + }, { "attachments": {}, "cell_type": "markdown", @@ -159,11 +169,9 @@ "metadata": {}, "outputs": [], "source": [ - "from qualtran.surface_code.quantum_error_correction_scheme_summary import (\n", - " BeverlandSuperconductingQubits,\n", - ")\n", + "from qualtran.surface_code import QECScheme\n", "\n", - "qec = BeverlandSuperconductingQubits\n", + "qec = QECScheme.make_beverland_et_al()\n", "qec" ] }, @@ -235,11 +243,11 @@ "metadata": {}, "outputs": [], "source": [ - "from qualtran.surface_code import azure_cost_model\n", + "from qualtran.surface_code import beverland_et_al_model\n", "\n", "# Calculate the minimum number of logical time steps (Eq D3)\n", "error_budget = 0.001\n", - "c_min = azure_cost_model.minimum_time_steps(\n", + "c_min = beverland_et_al_model.minimum_time_steps(\n", " error_budget=error_budget,\n", " alg=qd_alg,\n", " rotation_model=BeverlandEtAlRotationCost,\n", @@ -254,7 +262,7 @@ "outputs": [], "source": [ "# And the number of needed T operations (Eq D4)\n", - "t_operations = azure_cost_model.t_states(\n", + "t_operations = beverland_et_al_model.t_states(\n", " error_budget=error_budget,\n", " alg=qd_alg,\n", " rotation_model=BeverlandEtAlRotationCost\n", @@ -293,11 +301,11 @@ "metadata": {}, "outputs": [], "source": [ - "d = azure_cost_model.code_distance(\n", + "d = beverland_et_al_model.code_distance(\n", " error_budget=error_budget,\n", " time_steps=c_min,\n", " alg=qd_alg,\n", - " qec_scheme=BeverlandSuperconductingQubits,\n", + " qec_scheme=qec,\n", " physical_error=beverland_phys_params.physical_error,\n", ")\n", "print(f'{d=}')" @@ -415,7 +423,7 @@ "source": [ "# Calculate the minimum number of logical time steps (Eq D3)\n", "error_budget = 0.01\n", - "c_min = azure_cost_model.minimum_time_steps(\n", + "c_min = beverland_et_al_model.minimum_time_steps(\n", " error_budget=error_budget, alg=chem_alg, rotation_model=BeverlandEtAlRotationCost\n", ")\n", "print('C_min = %g' % c_min)" @@ -428,7 +436,7 @@ "outputs": [], "source": [ "# And the number of needed T operations (Eq D4)\n", - "t_operations = azure_cost_model.t_states(\n", + "t_operations = beverland_et_al_model.t_states(\n", " error_budget=error_budget, alg=chem_alg, rotation_model=BeverlandEtAlRotationCost\n", ")\n", "print('M = %g' % t_operations)" @@ -459,7 +467,7 @@ "metadata": {}, "outputs": [], "source": [ - "d = azure_cost_model.code_distance(\n", + "d = beverland_et_al_model.code_distance(\n", " error_budget=error_budget,\n", " time_steps=c_min,\n", " alg=chem_alg,\n", @@ -591,7 +599,7 @@ "source": [ "# Calculate the minimum number of logical time steps (Eq D3)\n", "error_budget = 1 / 3\n", - "c_min = azure_cost_model.minimum_time_steps(\n", + "c_min = beverland_et_al_model.minimum_time_steps(\n", " error_budget=error_budget, alg=shor_alg, rotation_model=BeverlandEtAlRotationCost\n", ")\n", "print('C_min = %e' % c_min)" @@ -604,7 +612,7 @@ "outputs": [], "source": [ "# And the number of needed T operations (Eq D4)\n", - "t_operations = azure_cost_model.t_states(\n", + "t_operations = beverland_et_al_model.t_states(\n", " error_budget=error_budget, alg=shor_alg, rotation_model=BeverlandEtAlRotationCost\n", ")\n", "print('M = %e' % t_operations)" @@ -624,7 +632,7 @@ "metadata": {}, "outputs": [], "source": [ - "d = azure_cost_model.code_distance(\n", + "d = beverland_et_al_model.code_distance(\n", " error_budget=error_budget,\n", " time_steps=c_min,\n", " alg=shor_alg,\n", diff --git a/qualtran/surface_code/azure_cost_model.py b/qualtran/surface_code/beverland_et_al_model.py similarity index 96% rename from qualtran/surface_code/azure_cost_model.py rename to qualtran/surface_code/beverland_et_al_model.py index 382a516a3..1c9a3e2cc 100644 --- a/qualtran/surface_code/azure_cost_model.py +++ b/qualtran/surface_code/beverland_et_al_model.py @@ -20,11 +20,7 @@ from qualtran.resource_counting import GateCounts if TYPE_CHECKING: - from qualtran.surface_code import ( - AlgorithmSummary, - QuantumErrorCorrectionSchemeSummary, - RotationCostModel, - ) + from qualtran.surface_code import AlgorithmSummary, QECScheme, RotationCostModel def minimum_time_steps( @@ -85,7 +81,7 @@ def code_distance( error_budget: float, time_steps: float, alg: 'AlgorithmSummary', - qec_scheme: 'QuantumErrorCorrectionSchemeSummary', + qec_scheme: 'QECScheme', physical_error: float, ) -> int: r"""Minimum code distance needed to run the algorithm within the error budget. diff --git a/qualtran/surface_code/azure_cost_model_test.py b/qualtran/surface_code/beverland_et_al_model_test.py similarity index 87% rename from qualtran/surface_code/azure_cost_model_test.py rename to qualtran/surface_code/beverland_et_al_model_test.py index 36f54f7ed..7cf6c5eb2 100644 --- a/qualtran/surface_code/azure_cost_model_test.py +++ b/qualtran/surface_code/beverland_et_al_model_test.py @@ -17,10 +17,7 @@ import qualtran.testing as qlt_testing from qualtran.resource_counting import GateCounts -from qualtran.surface_code import AlgorithmSummary, azure_cost_model -from qualtran.surface_code.quantum_error_correction_scheme_summary import ( - BeverlandSuperconductingQubits, -) +from qualtran.surface_code import AlgorithmSummary, beverland_et_al_model, QECScheme from qualtran.surface_code.rotation_cost_model import BeverlandEtAlRotationCost @@ -83,7 +80,7 @@ class Test: @pytest.mark.parametrize('test', _TESTS) def test_minimum_time_step(test: Test): - got = azure_cost_model.minimum_time_steps( + got = beverland_et_al_model.minimum_time_steps( error_budget=test.error_budget, alg=test.alg, rotation_model=BeverlandEtAlRotationCost ) assert got == pytest.approx(test.c_min, rel=0.1) @@ -91,11 +88,11 @@ def test_minimum_time_step(test: Test): @pytest.mark.parametrize('test', _TESTS) def test_code_distance(test: Test): - got = azure_cost_model.code_distance( + got = beverland_et_al_model.code_distance( error_budget=test.error_budget, time_steps=test.time_steps, alg=test.alg, - qec_scheme=BeverlandSuperconductingQubits, + qec_scheme=QECScheme.make_beverland_et_al(), physical_error=1e-4, ) assert got == test.code_distance @@ -103,11 +100,11 @@ def test_code_distance(test: Test): @pytest.mark.parametrize('test', _TESTS) def test_t_states(test: Test): - got = azure_cost_model.t_states( + got = beverland_et_al_model.t_states( error_budget=test.error_budget, alg=test.alg, rotation_model=BeverlandEtAlRotationCost ) assert got == pytest.approx(test.t_states, rel=0.1) def test_notebook(): - qlt_testing.execute_notebook('azure_cost_model') + qlt_testing.execute_notebook('beverland_et_al_model') diff --git a/qualtran/surface_code/ccz2t_factory.py b/qualtran/surface_code/ccz2t_factory.py new file mode 100644 index 000000000..937748981 --- /dev/null +++ b/qualtran/surface_code/ccz2t_factory.py @@ -0,0 +1,157 @@ +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import math +from typing import TYPE_CHECKING + +from attrs import frozen + +from .magic_state_factory import MagicStateFactory + +if TYPE_CHECKING: + from qualtran.resource_counting import GateCounts + from qualtran.surface_code import LogicalErrorModel + + +@frozen +class CCZ2TFactory(MagicStateFactory): + """Magic state factory costs using the model from catalyzed CCZ to 2T paper. + + Args: + distillation_l1_d: Code distance used for level 1 factories. + distillation_l2_d: Code distance used for level 2 factories. + + References: + Efficient magic state factories with a catalyzed |CCZ> to 2|T> transformation. + https://arxiv.org/abs/1812.01238 + """ + + distillation_l1_d: int = 15 + distillation_l2_d: int = 31 + + # ------------------------------------------------------------------------------- + # ---- Level 0 --------- + # ------------------------------------------------------------------------------- + + def l0_state_injection_error(self, error_model: 'LogicalErrorModel') -> float: + """Error rate associated with the level-0 creation of a |T> state. + + By using the techniques of Ying Li (https://arxiv.org/abs/1410.7808), this can be + done with approximately the same error rate as the underlying physical error rate. + """ + return error_model.physical_error + + def l0_topo_error_t_gate(self, error_model: 'LogicalErrorModel') -> float: + """Topological error associated with level-0 distillation. + + For a level-1 code distance of `d1`, this construction uses a `d1/2` distance code + for storing level-0 T states. + """ + + # The chance of a logical error occurring within a lattice surgery unit cell at + # code distance d1*0.5. + topo_error_per_unit_cell = error_model(code_distance=self.distillation_l1_d // 2) + + # It takes approximately 100 L0 unit cells to get the injected state where + # it needs to be and perform the T gate. + return 100 * topo_error_per_unit_cell + + def l0_error(self, error_model: 'LogicalErrorModel') -> float: + """Chance of failure of a T gate performed with an injected (level-0) T state. + + As a simplifying approximation here (and elsewhere) we assume different sources + of error are independent, and we merely add the probabilities. + """ + return self.l0_state_injection_error(error_model) + self.l0_topo_error_t_gate(error_model) + + # ------------------------------------------------------------------------------- + # ---- Level 1 --------- + # ------------------------------------------------------------------------------- + + def l1_topo_error_factory(self, error_model: 'LogicalErrorModel') -> float: + """Topological error associated with a L1 T factory.""" + + # The L1 T factory uses approximately 1000 L1 unit cells. + return 1000 * error_model(code_distance=self.distillation_l1_d) + + def l1_topo_error_t_gate(self, error_model: 'LogicalErrorModel') -> float: + # It takes approximately 100 L1 unit cells to get the L1 state produced by the + # factory to where it needs to be and perform the T gate. + return 100 * error_model(code_distance=self.distillation_l1_d) + + def l1_distillation_error(self, error_model: 'LogicalErrorModel') -> float: + """The error due to level-0 faulty T states making it through distillation undetected. + + The level 1 distillation procedure detects any two errors. There are 35 weight-three + errors that can make it through undetected. + """ + return 35 * self.l0_error(error_model) ** 3 + + def l1_error(self, error_model: 'LogicalErrorModel') -> float: + """Chance of failure of a T gate performed with a T state produced from the L1 factory.""" + return ( + self.l1_topo_error_factory(error_model) + + self.l1_topo_error_t_gate(error_model) + + self.l1_distillation_error(error_model) + ) + + # ------------------------------------------------------------------------------- + # ---- Level 2 --------- + # ------------------------------------------------------------------------------- + + def l2_error(self, error_model: 'LogicalErrorModel') -> float: + """Chance of failure of the level two factory. + + This is the chance of failure of a CCZ gate or a pair of T gates performed with a CCZ state. + """ + + # The L2 CCZ factory and catalyzed T factory both use approximately 1000 L2 unit cells. + l2_topo_error_factory = 1000 * error_model(self.distillation_l2_d) + + # Distillation error for this level. + l2_distillation_error = 28 * self.l1_error(error_model) ** 2 + + return l2_topo_error_factory + l2_distillation_error + + # ------------------------------------------------------------------------------- + # ---- Totals --------- + # ------------------------------------------------------------------------------- + + def n_physical_qubits(self) -> int: + l1 = 4 * 8 * 2 * self.distillation_l1_d**2 + l2 = 4 * 8 * 2 * self.distillation_l2_d**2 + return 6 * l1 + l2 + + def factory_error( + self, n_logical_gates: 'GateCounts', logical_error_model: 'LogicalErrorModel' + ) -> float: + """Error resulting from the magic state distillation part of the computation.""" + counts = n_logical_gates.total_t_and_ccz_count() + total_ccz_states = counts['n_ccz'] + math.ceil(counts['n_t'] / 2) + return self.l2_error(logical_error_model) * total_ccz_states + + def n_cycles( + self, n_logical_gates: 'GateCounts', logical_error_model: 'LogicalErrorModel' + ) -> int: + """The number of error-correction cycles to distill enough magic states.""" + distillation_d = max(2 * self.distillation_l1_d + 1, self.distillation_l2_d) + counts = n_logical_gates.total_t_and_ccz_count() + n_ccz_states = counts['n_ccz'] + math.ceil(counts['n_t'] / 2) + catalyzations = math.ceil(counts['n_t'] / 2) + + # Naive depth of 8.5, but can be overlapped to effective depth of 5.5 + # See section 2, paragraph 2 of the reference. + ccz_depth = 5.5 + + return math.ceil((n_ccz_states * ccz_depth + catalyzations) * distillation_d) diff --git a/qualtran/surface_code/ccz2t_factory_test.py b/qualtran/surface_code/ccz2t_factory_test.py new file mode 100644 index 000000000..4bf7e9dab --- /dev/null +++ b/qualtran/surface_code/ccz2t_factory_test.py @@ -0,0 +1,37 @@ +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from qualtran.resource_counting import GateCounts +from qualtran.surface_code import AlgorithmSummary, CCZ2TFactory, LogicalErrorModel, QECScheme + + +def test_ccz_2t_factory(): + factory = CCZ2TFactory() + worse_factory = CCZ2TFactory(distillation_l1_d=7, distillation_l2_d=15) + + alg = AlgorithmSummary( + n_logical_gates=GateCounts(t=10**8, toffoli=10**8), n_algo_qubits=100 + ) + lem = LogicalErrorModel(qec_scheme=QECScheme.make_gidney_fowler(), physical_error=1e-3) + + err1 = factory.factory_error(n_logical_gates=alg.n_logical_gates, logical_error_model=lem) + err2 = worse_factory.factory_error(n_logical_gates=alg.n_logical_gates, logical_error_model=lem) + assert err2 > err1 + + cyc1 = factory.n_cycles(n_logical_gates=alg.n_logical_gates, logical_error_model=lem) + cyc2 = worse_factory.n_cycles(n_logical_gates=alg.n_logical_gates, logical_error_model=lem) + assert cyc2 < cyc1 + + foot1 = factory.n_physical_qubits() + foot2 = worse_factory.n_physical_qubits() + assert foot2 < foot1 diff --git a/qualtran/surface_code/data_block.py b/qualtran/surface_code/data_block.py index 82a0bb662..c0237613c 100644 --- a/qualtran/surface_code/data_block.py +++ b/qualtran/surface_code/data_block.py @@ -20,7 +20,7 @@ if TYPE_CHECKING: from qualtran.resource_counting import GateCounts - from qualtran.surface_code import LogicalErrorModel, QuantumErrorCorrectionSchemeSummary + from qualtran.surface_code import LogicalErrorModel, QECScheme class DataBlock(metaclass=abc.ABCMeta): @@ -183,7 +183,6 @@ class IntermediateDataBlock(DataBlock): Attributes: data_d: The code distance `d` for protecting the qubits in the data block. - qec_scheme: Underlying quantum error correction scheme. References: [A Game of Surface Codes](https://arxiv.org/abs/1808.02892). @@ -212,7 +211,6 @@ class FastDataBlock(DataBlock): Attributes: data_d: The code distance `d` for protecting the qubits in the data block. - qec_scheme: Underlying quantum error correction scheme. References: [A Game of Surface Codes](https://arxiv.org/abs/1808.02892). @@ -236,11 +234,7 @@ def n_steps_to_consume_a_magic_state(self) -> int: @classmethod def from_error_budget( - cls, - error_budget: float, - n_algo_qubits: int, - qec_scheme: 'QuantumErrorCorrectionSchemeSummary', - phys_err_rate: float, + cls, error_budget: float, n_algo_qubits: int, qec_scheme: 'QECScheme', phys_err_rate: float ) -> 'FastDataBlock': q = FastDataBlock.get_n_tiles(n_algo_qubits) d = qec_scheme.code_distance_from_budget(phys_err_rate, error_budget / q) diff --git a/qualtran/surface_code/data_block_test.py b/qualtran/surface_code/data_block_test.py index ccfb18dc9..86931572a 100644 --- a/qualtran/surface_code/data_block_test.py +++ b/qualtran/surface_code/data_block_test.py @@ -15,7 +15,7 @@ import pytest -from qualtran.surface_code import FastDataBlock, FowlerSuperconductingQubits, LogicalErrorModel +from qualtran.surface_code import FastDataBlock, LogicalErrorModel, QECScheme @pytest.mark.parametrize( @@ -24,7 +24,7 @@ ) def test_fast_block(logical_qubits, logical_qubits_with_routing, data_error): assert FastDataBlock.get_n_tiles(n_algo_qubits=logical_qubits) == logical_qubits_with_routing - err_model = LogicalErrorModel(qec_scheme=FowlerSuperconductingQubits, physical_error=1e-3) + err_model = LogicalErrorModel(qec_scheme=QECScheme.make_gidney_fowler(), physical_error=1e-3) assert FastDataBlock(3).data_error( n_algo_qubits=logical_qubits, n_cycles=3, logical_error_model=err_model ) == pytest.approx(data_error) diff --git a/qualtran/surface_code/fifteen_to_one.py b/qualtran/surface_code/fifteen_to_one_factory.py similarity index 98% rename from qualtran/surface_code/fifteen_to_one.py rename to qualtran/surface_code/fifteen_to_one_factory.py index 7dc4860d1..407039d78 100644 --- a/qualtran/surface_code/fifteen_to_one.py +++ b/qualtran/surface_code/fifteen_to_one_factory.py @@ -115,11 +115,11 @@ def _build_factory( material of https://arxiv.org/abs/1905.06903. Args: - phys_err: physical error rate. d_X: Side length of the surface code along which X measurements happen. d_Z: Side length of the surface code along which Z measurements happen. d_m: Number of code cycles used in lattice surgery. - qec: Quantum error correction scheme being used. + logical_error_model: The logical error model for determining the logical error + rate at a given code distance. Returns: @@ -382,7 +382,3 @@ def _build_factory( ), ) return factory - - -FifteenToOne733 = FifteenToOne(7, 3, 3) -FifteenToOne933 = FifteenToOne(9, 3, 3) diff --git a/qualtran/surface_code/fifteen_to_one_test.py b/qualtran/surface_code/fifteen_to_one_factory_test.py similarity index 89% rename from qualtran/surface_code/fifteen_to_one_test.py rename to qualtran/surface_code/fifteen_to_one_factory_test.py index 1775e4bff..616820dc0 100644 --- a/qualtran/surface_code/fifteen_to_one_test.py +++ b/qualtran/surface_code/fifteen_to_one_factory_test.py @@ -18,8 +18,7 @@ from attrs import frozen from qualtran.resource_counting import GateCounts -from qualtran.surface_code import FowlerSuperconductingQubits, LogicalErrorModel -from qualtran.surface_code.fifteen_to_one import FifteenToOne +from qualtran.surface_code import FifteenToOne, LogicalErrorModel, QECScheme @frozen @@ -53,7 +52,7 @@ class FifteenToOneTestCase: @pytest.mark.parametrize("test", PAPER_RESULTS) def test_compare_with_paper(test: FifteenToOneTestCase): factory = FifteenToOne(d_X=test.d_X, d_Z=test.d_Z, d_m=test.d_m) - lem = LogicalErrorModel(qec_scheme=FowlerSuperconductingQubits, physical_error=test.phys_err) + lem = LogicalErrorModel(qec_scheme=QECScheme.make_gidney_fowler(), physical_error=test.phys_err) assert f'{factory.factory_error(GateCounts(t=1), lem):.1e}' == str(test.p_out) assert round(factory.n_physical_qubits(), -1) == test.footprint # rounding to the 10s digit. assert factory.n_cycles(GateCounts(t=1), lem) == math.ceil(test.cycles + 1e-9) diff --git a/qualtran/surface_code/ccz2t_cost_model.py b/qualtran/surface_code/gidney_fowler_model.py similarity index 56% rename from qualtran/surface_code/ccz2t_cost_model.py rename to qualtran/surface_code/gidney_fowler_model.py index 233136401..d225f1b62 100644 --- a/qualtran/surface_code/ccz2t_cost_model.py +++ b/qualtran/surface_code/gidney_fowler_model.py @@ -15,149 +15,14 @@ import math from typing import Callable, cast, Iterable, Iterator, Optional, Tuple, TYPE_CHECKING -from attrs import frozen - +from .ccz2t_factory import CCZ2TFactory from .data_block import DataBlock, SimpleDataBlock from .magic_state_factory import MagicStateFactory from .multi_factory import MultiFactory -from .physical_cost import PhysicalCost +from .physical_cost_summary import PhysicalCostsSummary if TYPE_CHECKING: from qualtran.resource_counting import GateCounts - from qualtran.surface_code import DataBlock, LogicalErrorModel - - -@frozen -class CCZ2TFactory(MagicStateFactory): - """Magic state factory costs using the model from catalyzed CCZ to 2T paper. - - Args: - distillation_l1_d: Code distance used for level 1 factories. - distillation_l2_d: Code distance used for level 2 factories. - - References: - Efficient magic state factories with a catalyzed |CCZ> to 2|T> transformation. - https://arxiv.org/abs/1812.01238 - """ - - distillation_l1_d: int = 15 - distillation_l2_d: int = 31 - - # ------------------------------------------------------------------------------- - # ---- Level 0 --------- - # ------------------------------------------------------------------------------- - - def l0_state_injection_error(self, error_model: 'LogicalErrorModel') -> float: - """Error rate associated with the level-0 creation of a |T> state. - - By using the techniques of Ying Li (https://arxiv.org/abs/1410.7808), this can be - done with approximately the same error rate as the underlying physical error rate. - """ - return error_model.physical_error - - def l0_topo_error_t_gate(self, error_model: 'LogicalErrorModel') -> float: - """Topological error associated with level-0 distillation. - - For a level-1 code distance of `d1`, this construction uses a `d1/2` distance code - for storing level-0 T states. - """ - - # The chance of a logical error occurring within a lattice surgery unit cell at - # code distance d1*0.5. - topo_error_per_unit_cell = error_model(code_distance=self.distillation_l1_d // 2) - - # It takes approximately 100 L0 unit cells to get the injected state where - # it needs to be and perform the T gate. - return 100 * topo_error_per_unit_cell - - def l0_error(self, error_model: 'LogicalErrorModel') -> float: - """Chance of failure of a T gate performed with an injected (level-0) T state. - - As a simplifying approximation here (and elsewhere) we assume different sources - of error are independent, and we merely add the probabilities. - """ - return self.l0_state_injection_error(error_model) + self.l0_topo_error_t_gate(error_model) - - # ------------------------------------------------------------------------------- - # ---- Level 1 --------- - # ------------------------------------------------------------------------------- - - def l1_topo_error_factory(self, error_model: 'LogicalErrorModel') -> float: - """Topological error associated with a L1 T factory.""" - - # The L1 T factory uses approximately 1000 L1 unit cells. - return 1000 * error_model(code_distance=self.distillation_l1_d) - - def l1_topo_error_t_gate(self, error_model: 'LogicalErrorModel') -> float: - # It takes approximately 100 L1 unit cells to get the L1 state produced by the - # factory to where it needs to be and perform the T gate. - return 100 * error_model(code_distance=self.distillation_l1_d) - - def l1_distillation_error(self, error_model: 'LogicalErrorModel') -> float: - """The error due to level-0 faulty T states making it through distillation undetected. - - The level 1 distillation procedure detects any two errors. There are 35 weight-three - errors that can make it through undetected. - """ - return 35 * self.l0_error(error_model) ** 3 - - def l1_error(self, error_model: 'LogicalErrorModel') -> float: - """Chance of failure of a T gate performed with a T state produced from the L1 factory.""" - return ( - self.l1_topo_error_factory(error_model) - + self.l1_topo_error_t_gate(error_model) - + self.l1_distillation_error(error_model) - ) - - # ------------------------------------------------------------------------------- - # ---- Level 2 --------- - # ------------------------------------------------------------------------------- - - def l2_error(self, error_model: 'LogicalErrorModel') -> float: - """Chance of failure of the level two factory. - - This is the chance of failure of a CCZ gate or a pair of T gates performed with a CCZ state. - """ - - # The L2 CCZ factory and catalyzed T factory both use approximately 1000 L2 unit cells. - l2_topo_error_factory = 1000 * error_model(self.distillation_l2_d) - - # Distillation error for this level. - l2_distillation_error = 28 * self.l1_error(error_model) ** 2 - - return l2_topo_error_factory + l2_distillation_error - - # ------------------------------------------------------------------------------- - # ---- Totals --------- - # ------------------------------------------------------------------------------- - - def n_physical_qubits(self) -> int: - l1 = 4 * 8 * 2 * self.distillation_l1_d**2 - l2 = 4 * 8 * 2 * self.distillation_l2_d**2 - return 6 * l1 + l2 - - def factory_error( - self, n_logical_gates: 'GateCounts', logical_error_model: 'LogicalErrorModel' - ) -> float: - """Error resulting from the magic state distillation part of the computation.""" - counts = n_logical_gates.total_t_and_ccz_count() - total_ccz_states = counts['n_ccz'] + math.ceil(counts['n_t'] / 2) - return self.l2_error(logical_error_model) * total_ccz_states - - def n_cycles( - self, n_logical_gates: 'GateCounts', logical_error_model: 'LogicalErrorModel' - ) -> int: - """The number of error-correction cycles to distill enough magic states.""" - distillation_d = max(2 * self.distillation_l1_d + 1, self.distillation_l2_d) - counts = n_logical_gates.total_t_and_ccz_count() - n_ccz_states = counts['n_ccz'] + math.ceil(counts['n_t'] / 2) - catalyzations = math.ceil(counts['n_t'] / 2) - - # Naive depth of 8.5, but can be overlapped to effective depth of 5.5 - # See section 2, paragraph 2 of the reference. - ccz_depth = 5.5 - - return math.ceil((n_ccz_states * ccz_depth + catalyzations) * distillation_d) def get_ccz2t_costs( @@ -168,7 +33,7 @@ def get_ccz2t_costs( cycle_time_us: float, factory: MagicStateFactory, data_block: DataBlock, -) -> PhysicalCost: +) -> PhysicalCostsSummary: """Generate spacetime cost and failure probability given physical and logical parameters. Note that this function can return failure probabilities larger than 1. @@ -181,9 +46,11 @@ def get_ccz2t_costs( factory: magic state factory configuration. Used to evaluate distillation error and cost. data_block: data block configuration. Used to evaluate data error and footprint. """ - from qualtran.surface_code import FowlerSuperconductingQubits, LogicalErrorModel + from qualtran.surface_code import LogicalErrorModel, QECScheme - err_model = LogicalErrorModel(qec_scheme=FowlerSuperconductingQubits, physical_error=phys_err) + err_model = LogicalErrorModel( + qec_scheme=QECScheme.make_gidney_fowler(), physical_error=phys_err + ) distillation_error = factory.factory_error( n_logical_gates=n_logical_gates, logical_error_model=err_model ) @@ -203,7 +70,9 @@ def get_ccz2t_costs( ) duration_hr = (cycle_time_us * n_cycles) / (1_000_000 * 60 * 60) - return PhysicalCost(failure_prob=failure_prob, footprint=footprint, duration_hr=duration_hr) + return PhysicalCostsSummary( + failure_prob=failure_prob, footprint=footprint, duration_hr=duration_hr + ) def get_ccz2t_costs_from_error_budget( @@ -216,7 +85,7 @@ def get_ccz2t_costs_from_error_budget( routing_overhead: float = 0.5, factory: Optional[MagicStateFactory] = None, data_block: Optional[DataBlock] = None, -) -> PhysicalCost: +) -> PhysicalCostsSummary: """Physical costs using the model from catalyzed CCZ to 2T paper. Args: @@ -247,26 +116,27 @@ def get_ccz2t_costs_from_error_budget( if factory is None: factory = CCZ2TFactory() - from qualtran.surface_code import FowlerSuperconductingQubits, LogicalErrorModel + from qualtran.surface_code import LogicalErrorModel, QECScheme - err_model = LogicalErrorModel(qec_scheme=FowlerSuperconductingQubits, physical_error=phys_err) - distillation_error = factory.factory_error( + qec_scheme = QECScheme.make_gidney_fowler() + err_model = LogicalErrorModel(qec_scheme=qec_scheme, physical_error=phys_err) + factory_error = factory.factory_error( n_logical_gates=n_logical_gates, logical_error_model=err_model ) n_cycles = factory.n_cycles(n_logical_gates=n_logical_gates, logical_error_model=err_model) if data_block is None: # Use "left over" budget for data qubits. - err_budget = error_budget - distillation_error + err_budget = error_budget - factory_error if err_budget < 0: raise ValueError( - f"distillation error {distillation_error} is larger than the error budget {error_budget}" + f"Factory error {factory_error} is larger than the error budget {error_budget}" ) n_logical_qubits = math.ceil((1 + routing_overhead) * n_algo_qubits) data_unit_cells = n_logical_qubits * n_cycles target_err_per_round = err_budget / data_unit_cells - data_d = FowlerSuperconductingQubits.code_distance_from_budget( - physical_error_rate=phys_err, budget=target_err_per_round + data_d = qec_scheme.code_distance_from_budget( + physical_error=phys_err, budget=target_err_per_round ) data_block = SimpleDataBlock(data_d=data_d, routing_overhead=routing_overhead) @@ -325,8 +195,8 @@ def get_ccz2t_costs_from_grid_search( cycle_time_us: float = 1.0, factory_iter: Iterable[MagicStateFactory] = tuple(iter_ccz2t_factories()), data_block_iter: Iterable[DataBlock] = tuple(iter_simple_data_blocks()), - cost_function: Callable[[PhysicalCost], float] = (lambda pc: pc.qubit_hours), -) -> Tuple[PhysicalCost, MagicStateFactory, SimpleDataBlock]: + cost_function: Callable[[PhysicalCostsSummary], float] = (lambda pc: pc.qubit_hours), +) -> Tuple[PhysicalCostsSummary, MagicStateFactory, SimpleDataBlock]: """Grid search over parameters to minimize the space-time volume. Args: @@ -339,7 +209,7 @@ def get_ccz2t_costs_from_grid_search( cycle_time_us: The number of microseconds it takes to execute a surface code cycle. factory_iter: iterable containing the instances of MagicStateFactory to search over. data_block_iter: iterable containing the instances of SimpleDataBlock to search over. - cost_function: function of PhysicalCost to be minimized. Defaults to spacetime volume. + cost_function: function of PhysicalCostsSummary to be minimized. Defaults to spacetime volume. Set `cost_function = (lambda pc: pc.duration_hr)` to mimimize wall time. Returns: @@ -349,7 +219,7 @@ def get_ccz2t_costs_from_grid_search( A similar search was conducted manually in https://arxiv.org/abs/2011.03494, using a tweaked version of the spreadsheet from https://arxiv.org/abs/1812.01238 """ - best_cost: Optional[PhysicalCost] = None + best_cost: Optional[PhysicalCostsSummary] = None best_params: Optional[Tuple[MagicStateFactory, SimpleDataBlock]] = None for factory in factory_iter: for data_block in data_block_iter: @@ -373,6 +243,3 @@ def get_ccz2t_costs_from_grid_search( best_factory, best_data_block = best_params return best_cost, best_factory, best_data_block - - -GidneyFowlerCCZ = CCZ2TFactory() diff --git a/qualtran/surface_code/ccz2t_cost_model_test.py b/qualtran/surface_code/gidney_fowler_model_test.py similarity index 95% rename from qualtran/surface_code/ccz2t_cost_model_test.py rename to qualtran/surface_code/gidney_fowler_model_test.py index c7365a286..006dbb728 100644 --- a/qualtran/surface_code/ccz2t_cost_model_test.py +++ b/qualtran/surface_code/gidney_fowler_model_test.py @@ -15,13 +15,13 @@ import numpy as np from qualtran.resource_counting import GateCounts -from qualtran.surface_code.ccz2t_cost_model import ( +from qualtran.surface_code import ( CCZ2TFactory, get_ccz2t_costs_from_error_budget, get_ccz2t_costs_from_grid_search, iter_ccz2t_factories, + MultiFactory, ) -from qualtran.surface_code.multi_factory import MultiFactory def test_vs_spreadsheet(): diff --git a/qualtran/surface_code/physical_cost.py b/qualtran/surface_code/physical_cost_summary.py similarity index 97% rename from qualtran/surface_code/physical_cost.py rename to qualtran/surface_code/physical_cost_summary.py index f1840f9b7..fa6806ae0 100644 --- a/qualtran/surface_code/physical_cost.py +++ b/qualtran/surface_code/physical_cost_summary.py @@ -16,7 +16,7 @@ @frozen -class PhysicalCost: +class PhysicalCostsSummary: failure_prob: float """Approximate probability of an error occurring during execution of the algorithm. diff --git a/qualtran/surface_code/physical_parameters.py b/qualtran/surface_code/physical_parameters.py index eeb750532..f61ab7955 100644 --- a/qualtran/surface_code/physical_parameters.py +++ b/qualtran/surface_code/physical_parameters.py @@ -29,10 +29,10 @@ class PhysicalParameters: cycle_time_us: float = 1.0 @classmethod - def from_beverland( + def make_beverland_et_al( cls, qubit_modality: str = 'superconducting', optimistic_err_rate: bool = False ): - """The physical parameters considered in the reference. + """The physical parameters considered in the Beverland et. al. reference. Args: qubit_modality: One of "superconducting", "ion", or "majorana". This sets the @@ -42,7 +42,7 @@ def from_beverland( error rates. References: - [https://arxiv.org/abs/2211.07629](Assessing requirements to scale to practical quantum advantage). + [Assessing requirements to scale to practical quantum advantage](https://arxiv.org/abs/2211.07629). Beverland et. al. (2022). """ if optimistic_err_rate: @@ -74,3 +74,17 @@ def from_beverland( return PhysicalParameters( physical_error=phys_err_rate, cycle_time_us=cycle_time_ns / 1000.0 ) + + @classmethod + def make_gidney_fowler(cls, optimistic_err_rate: bool = False): + """The physical parameters considered in the Gidney and Fowler reference. + + References: + [Efficient magic state factories with a catalyzed |CCZ> to 2|T> transformation](https://arxiv.org/abs/1812.01238). + Gidney and Fowler (2018). + """ + if optimistic_err_rate: + phys_err_rate = 1e-4 + else: + phys_err_rate = 1e-3 + return PhysicalParameters(physical_error=phys_err_rate, cycle_time_us=1.0) diff --git a/qualtran/surface_code/quantum_error_correction_scheme_summary.py b/qualtran/surface_code/qec_scheme.py similarity index 56% rename from qualtran/surface_code/quantum_error_correction_scheme_summary.py rename to qualtran/surface_code/qec_scheme.py index 26e8f3aca..721485814 100644 --- a/qualtran/surface_code/quantum_error_correction_scheme_summary.py +++ b/qualtran/surface_code/qec_scheme.py @@ -13,19 +13,20 @@ # limitations under the License. import abc import math -from typing import Optional from attrs import field, frozen -from qualtran.surface_code.reference import Reference - @frozen -class QuantumErrorCorrectionSchemeSummary(metaclass=abc.ABCMeta): - r"""QuantumErrorCorrectionSchemeSummary represents a high-level view of a QEC scheme. +class QECScheme: + r"""A model of the error-correction scheme used to suppress errors + + This class primarily provides a formula for estimating the logical error rate + given code parameters (i.e. the distance) and a physical error rate. The class + attributes parameterize this formula. - QuantumErrorCorrectionSchemeSummary provides estimates for the logical error rate, - number of physical qubits and the time of an error detection cycle. + This class also provides the formula for computing the number of physical + qubits required to store one logical qubit. The logical error rate as a function of code distance $d$ and physical error rate $p$ is given by @@ -39,14 +40,12 @@ class QuantumErrorCorrectionSchemeSummary(metaclass=abc.ABCMeta): Attributes: error_rate_scaler: Logical error rate coefficient. error_rate_threshold: Logical error rate threshold. - reference: source of the estimates. """ error_rate_scaler: float = field(repr=lambda x: f'{x:g}') error_rate_threshold: float = field(repr=lambda x: f'{x:g}') - reference: Optional[Reference] = None - def logical_error_rate(self, code_distance: int, physical_error_rate: float) -> float: + def logical_error_rate(self, code_distance: int, physical_error: float) -> float: """Logical error suppressed with code distance for this physical error rate. This is an estimate, see the references section. @@ -67,16 +66,16 @@ def logical_error_rate(self, code_distance: int, physical_error_rate: float) -> Note: this doesn't actually contain the formula from the above reference. """ return self.error_rate_scaler * math.pow( - physical_error_rate / self.error_rate_threshold, (code_distance + 1) / 2 + physical_error / self.error_rate_threshold, (code_distance + 1) / 2 ) - def code_distance_from_budget(self, physical_error_rate: float, budget: float) -> int: + def code_distance_from_budget(self, physical_error: float, budget: float) -> int: """Get the code distance that keeps one below the logical error `budget`.""" # See: `logical_error_rate()`. p_l = a Λ^(-r) where r = (d+1)/2 # Which we invert: r = ln(p_l/a) / ln(1/Λ) r = math.log(budget / self.error_rate_scaler) / math.log( - physical_error_rate / self.error_rate_threshold + physical_error / self.error_rate_threshold ) d = 2 * math.ceil(r) - 1 if d < 3: @@ -86,28 +85,27 @@ def code_distance_from_budget(self, physical_error_rate: float, budget: float) - @abc.abstractmethod def physical_qubits(self, code_distance: int) -> int: """The number of physical qubits per logical qubit used by the error detection circuit.""" + return 2 * code_distance**2 - @abc.abstractmethod - def error_detection_circuit_time_us(self, code_distance: int) -> float: - """The time of a quantum error detection cycle in microseconds.""" - - -@frozen -class SimpliedSurfaceCode(QuantumErrorCorrectionSchemeSummary): - """A Surface Code Quantum Error Correction Scheme. - - Attributes: - single_stabilizer_time_us: Max time of a single X or Z stabilizer measurement. - """ + @classmethod + def make_gidney_fowler(cls): + """The qec scheme parameters considered in the Gidney/Fowler set of references. - single_stabilizer_time_us: float = 1 + References: + [Low overhead quantum computation using lattice surgery](https://arxiv.org/abs/1808.06709). + Fowler and Gidney (2018). + """ + return cls(error_rate_scaler=0.1, error_rate_threshold=0.01) - def physical_qubits(self, code_distance: int) -> int: - return 2 * code_distance**2 + @classmethod + def make_beverland_et_al(cls): + """The qec scheme parameters considered in Beverland et. al. reference. - def error_detection_circuit_time_us(self, code_distance: int) -> float: - """Equals the time to measure a stabilizer times the depth of the circuit.""" - return self.single_stabilizer_time_us * code_distance + References: + [https://arxiv.org/abs/2211.07629](Assessing requirements to scale to practical quantum advantage). + Beverland et. al. (2022). + """ + return cls(error_rate_scaler=0.03, error_rate_threshold=0.01) @frozen @@ -119,38 +117,9 @@ class LogicalErrorModel: """ physical_error: float - _qec_scheme: 'QuantumErrorCorrectionSchemeSummary' + _qec_scheme: 'QECScheme' def __call__(self, code_distance: int): return self._qec_scheme.logical_error_rate( - code_distance=code_distance, physical_error_rate=self.physical_error + code_distance=code_distance, physical_error=self.physical_error ) - - -BeverlandSuperconductingQubits = SimpliedSurfaceCode( - error_rate_scaler=0.03, - error_rate_threshold=0.01, - single_stabilizer_time_us=0.4, # Equals 4*t_gate+2*t_meas where t_gate=50ns and t_meas=100ns. - reference=Reference(url='https://arxiv.org/abs/2211.07629', page=20), -) - -FowlerSuperconductingQubits = SimpliedSurfaceCode( - error_rate_scaler=0.1, - error_rate_threshold=0.01, - single_stabilizer_time_us=1, - reference=Reference(url='https://arxiv.org/abs/1808.06709'), -) - -BeverlandMajoranaQubits = SimpliedSurfaceCode( - error_rate_scaler=0.03, - error_rate_threshold=0.01, - single_stabilizer_time_us=0.6, # Equals 4*t_gate+2*t_meas where t_gate=100ns and t_meas=100ns. - reference=Reference(url='https://arxiv.org/abs/2211.07629', page=20), -) - -BeverlandTrappedIonQubits = SimpliedSurfaceCode( - error_rate_scaler=0.03, - error_rate_threshold=0.01, - single_stabilizer_time_us=600, # Equals 4*t_gate+2*t_meas where t_gate=100us and t_meas=100us. - reference=Reference(url='https://arxiv.org/abs/2211.07629', page=20), -) diff --git a/qualtran/surface_code/qec_scheme_test.py b/qualtran/surface_code/qec_scheme_test.py new file mode 100644 index 000000000..43b935b1d --- /dev/null +++ b/qualtran/surface_code/qec_scheme_test.py @@ -0,0 +1,56 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import numpy as np +import pytest + +from qualtran.surface_code import LogicalErrorModel, QECScheme + + +@pytest.mark.parametrize('qec,want', [(QECScheme.make_beverland_et_al(), 3e-4)]) +def test_logical_error_rate(qec: QECScheme, want: float): + assert qec.logical_error_rate(3, 1e-3) == pytest.approx(want) + + +@pytest.mark.parametrize('qec,want', [[QECScheme.make_beverland_et_al(), 242]]) +def test_physical_qubits(qec: QECScheme, want: int): + assert qec.physical_qubits(11) == want + + +def test_invert_error_at(): + phys_err = 1e-3 + budgets = np.logspace(-1, -18) + for budget in budgets: + d = QECScheme.make_gidney_fowler().code_distance_from_budget( + physical_error=phys_err, budget=budget + ) + assert d % 2 == 1 + assert ( + QECScheme.make_gidney_fowler().logical_error_rate( + physical_error=phys_err, code_distance=d + ) + <= budget + ) + if d > 3: + assert ( + QECScheme.make_gidney_fowler().logical_error_rate( + physical_error=phys_err, code_distance=d - 2 + ) + > budget + ) + + +def test_logical_error_model(): + ler = LogicalErrorModel(qec_scheme=QECScheme.make_beverland_et_al(), physical_error=1e-3) + assert ler(code_distance=3) == pytest.approx(3e-4) diff --git a/qualtran/surface_code/quantum_error_correction_scheme_summary_test.py b/qualtran/surface_code/quantum_error_correction_scheme_summary_test.py deleted file mode 100644 index 922c75dee..000000000 --- a/qualtran/surface_code/quantum_error_correction_scheme_summary_test.py +++ /dev/null @@ -1,63 +0,0 @@ -# Copyright 2023 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import numpy as np -import pytest - -from qualtran.surface_code import quantum_error_correction_scheme_summary as qecs - - -@pytest.mark.parametrize('qec,want', [(qecs.BeverlandSuperconductingQubits, 3e-4)]) -def test_logical_error_rate(qec: qecs.QuantumErrorCorrectionSchemeSummary, want: float): - assert qec.logical_error_rate(3, 1e-3) == pytest.approx(want) - - -@pytest.mark.parametrize('qec,want', [[qecs.BeverlandSuperconductingQubits, 242]]) -def test_physical_qubits(qec: qecs.QuantumErrorCorrectionSchemeSummary, want: int): - assert qec.physical_qubits(11) == want - - -@pytest.mark.parametrize('qec,want', [[qecs.BeverlandSuperconductingQubits, 4.8]]) -def test_error_detection_cycle_time(qec: qecs.QuantumErrorCorrectionSchemeSummary, want: float): - assert qec.error_detection_circuit_time_us(12) == pytest.approx(want) - - -def test_invert_error_at(): - phys_err = 1e-3 - budgets = np.logspace(-1, -18) - for budget in budgets: - d = qecs.FowlerSuperconductingQubits.code_distance_from_budget( - physical_error_rate=phys_err, budget=budget - ) - assert d % 2 == 1 - assert ( - qecs.FowlerSuperconductingQubits.logical_error_rate( - physical_error_rate=phys_err, code_distance=d - ) - <= budget - ) - if d > 3: - assert ( - qecs.FowlerSuperconductingQubits.logical_error_rate( - physical_error_rate=phys_err, code_distance=d - 2 - ) - > budget - ) - - -def test_logical_error_model(): - ler = qecs.LogicalErrorModel( - qec_scheme=qecs.BeverlandSuperconductingQubits, physical_error=1e-3 - ) - assert ler(code_distance=3) == pytest.approx(3e-4) diff --git a/qualtran/surface_code/thc_compilation.ipynb b/qualtran/surface_code/thc_compilation.ipynb index b1e1a767b..e82d0fee4 100644 --- a/qualtran/surface_code/thc_compilation.ipynb +++ b/qualtran/surface_code/thc_compilation.ipynb @@ -20,9 +20,8 @@ "metadata": {}, "outputs": [], "source": [ - "from qualtran.surface_code.ccz2t_cost_model import get_ccz2t_costs, CCZ2TFactory\n", - "from qualtran.surface_code.multi_factory import MultiFactory\n", - "from qualtran.surface_code.data_block import SimpleDataBlock\n", + "from qualtran.surface_code.gidney_fowler_model import get_ccz2t_costs\n", + "from qualtran.surface_code import CCZ2TFactory, MultiFactory, SimpleDataBlock\n", "from qualtran.resource_counting import GateCounts\n", "\n", "n_logical_gates = GateCounts(toffoli=6665400000) # pag. 26\n", @@ -60,8 +59,8 @@ "metadata": {}, "outputs": [], "source": [ - "from qualtran.surface_code import LogicalErrorModel, FowlerSuperconductingQubits\n", - "err_model = LogicalErrorModel(qec_scheme=FowlerSuperconductingQubits, physical_error=1e-3)" + "from qualtran.surface_code import LogicalErrorModel, QECScheme\n", + "err_model = LogicalErrorModel(qec_scheme=QECScheme.make_gidney_fowler(), physical_error=1e-3)" ] }, { @@ -70,8 +69,10 @@ "metadata": {}, "outputs": [], "source": [ - "from qualtran.surface_code.ccz2t_cost_model import get_ccz2t_costs_from_grid_search, \\\n", - " iter_ccz2t_factories\n", + "from qualtran.surface_code.gidney_fowler_model import (\n", + " get_ccz2t_costs_from_grid_search,\n", + " iter_ccz2t_factories\n", + ")\n", "\n", "best_cost, best_factory, best_data_block = get_ccz2t_costs_from_grid_search(\n", " n_logical_gates=n_logical_gates,\n", diff --git a/qualtran/surface_code/ui.py b/qualtran/surface_code/ui.py index 0d2db59ea..85f4010c4 100644 --- a/qualtran/surface_code/ui.py +++ b/qualtran/surface_code/ui.py @@ -24,20 +24,18 @@ from qualtran.resource_counting import GateCounts from qualtran.surface_code import ( AlgorithmSummary, - azure_cost_model, - ccz2t_cost_model, - fifteen_to_one, + beverland_et_al_model, + CCZ2TFactory, + FastDataBlock, + FifteenToOne, + gidney_fowler_model, LogicalErrorModel, - magic_state_factory, + MagicStateFactory, + MultiFactory, + PhysicalParameters, + QECScheme, + rotation_cost_model, ) -from qualtran.surface_code import quantum_error_correction_scheme_summary as qecs -from qualtran.surface_code import rotation_cost_model -from qualtran.surface_code.ccz2t_cost_model import ( - get_ccz2t_costs_from_grid_search, - iter_ccz2t_factories, -) -from qualtran.surface_code.data_block import FastDataBlock -from qualtran.surface_code.multi_factory import MultiFactory def get_objects(modules, object_type): @@ -49,10 +47,15 @@ def get_objects(modules, object_type): yield obj_name, obj -_QEC_SCHEMES = dict(get_objects([qecs], qecs.QuantumErrorCorrectionSchemeSummary)) -_MAGIC_FACTORIES = dict( - get_objects([fifteen_to_one, ccz2t_cost_model], magic_state_factory.MagicStateFactory) -) +_QEC_SCHEMES = { + 'BeverlandEtAl': QECScheme.make_beverland_et_al(), + 'GidneyFowler': QECScheme.make_gidney_fowler(), +} +_MAGIC_FACTORIES = { + 'FifteenToOne733': FifteenToOne(7, 3, 3), + 'FifteenToOne933': FifteenToOne(9, 3, 3), + 'GidneyFowlerCCZ': CCZ2TFactory(), +} _ROTATION_MODELS = dict(get_objects([rotation_cost_model], rotation_cost_model.RotationCostModel)) _GIDNEY_FOWLER_MODEL = 'GidneyFowler (arxiv:1812.01238)' @@ -360,18 +363,21 @@ def create_qubit_pie_chart( error_budget: float, estimation_model: str, algorithm: 'AlgorithmSummary', - magic_factory: magic_state_factory.MagicStateFactory, + magic_factory: MagicStateFactory, magic_count: int, n_logical_gates: 'GateCounts', ) -> go.Figure: """Create a pie chart of the physical qubit utilization.""" if estimation_model == _GIDNEY_FOWLER_MODEL: - res, factory, _ = get_ccz2t_costs_from_grid_search( + res, factory, _ = gidney_fowler_model.get_ccz2t_costs_from_grid_search( n_logical_gates=n_logical_gates, n_algo_qubits=int(algorithm.n_algo_qubits), phys_err=physical_error_rate, error_budget=error_budget, - factory_iter=[MultiFactory(f, int(magic_count)) for f in iter_ccz2t_factories()], + factory_iter=[ + MultiFactory(f, int(magic_count)) + for f in gidney_fowler_model.iter_ccz2t_factories() + ], ) memory_footprint = pd.DataFrame(columns=['source', 'qubits']) memory_footprint['source'] = [ @@ -434,8 +440,8 @@ def create_runtime_plot( error_budget: float, estimation_model: str, algorithm: 'AlgorithmSummary', - qec: qecs.QuantumErrorCorrectionSchemeSummary, - magic_factory: magic_state_factory.MagicStateFactory, + qec: QECScheme, + magic_factory: MagicStateFactory, magic_count: int, rotation_model: rotation_cost_model.RotationCostModel, n_logical_gates: 'GateCounts', @@ -447,7 +453,7 @@ def create_runtime_plot( if estimation_model == _GIDNEY_FOWLER_MODEL: return {'display': 'none'}, go.Figure() factory = MultiFactory(magic_factory, int(magic_count)) - c_min = azure_cost_model.minimum_time_steps( + c_min = beverland_et_al_model.minimum_time_steps( error_budget=error_budget, alg=algorithm, rotation_model=rotation_model ) err_model = LogicalErrorModel(qec_scheme=qec, physical_error=physical_error_rate) @@ -464,7 +470,7 @@ def create_runtime_plot( magic_counts[0] = min_num_factories time_steps[0] = c_min cds = [ - azure_cost_model.code_distance( + beverland_et_al_model.code_distance( error_budget=error_budget, time_steps=t, alg=algorithm, @@ -473,7 +479,9 @@ def create_runtime_plot( ) for t in time_steps ] - duration = [qec.error_detection_circuit_time_us(d) * t for t, d in zip(time_steps, cds)] + # TODO: plumb through PhysicalParameters + cycle_time_us = PhysicalParameters.make_gidney_fowler().cycle_time_us + duration = [cycle_time_us * t for t, d in zip(time_steps, cds)] unit, duration = format_duration(duration) duration_name = f'Duration ({unit})' num_qubits = ( @@ -532,7 +540,7 @@ def update( qec = _QEC_SCHEMES[qec_name] magic_factory = _MAGIC_FACTORIES[magic_name] rotation_model = _ROTATION_MODELS[rotaion_model_name] - n_logical_gates = azure_cost_model.n_discrete_logical_gates( + n_logical_gates = beverland_et_al_model.n_discrete_logical_gates( eps_syn=error_budget / 3, alg=algorithm, rotation_model=rotation_model ) # n_logical_gates = GateCounts(t=int(n_logical_gates.t), toffoli=int(n_logical_gates.toffoli)) @@ -597,12 +605,12 @@ def min_num_factories( estimation_model: str, algorithm: 'AlgorithmSummary', rotation_model: rotation_cost_model.RotationCostModel, - magic_factory: magic_state_factory.MagicStateFactory, + magic_factory: MagicStateFactory, n_logical_gates: 'GateCounts', ) -> Tuple[Dict[str, Any], int]: if estimation_model == _GIDNEY_FOWLER_MODEL: return {'display': 'none'}, 1 - c_min = azure_cost_model.minimum_time_steps( + c_min = beverland_et_al_model.minimum_time_steps( error_budget=error_budget, alg=algorithm, rotation_model=rotation_model ) return {'display': 'block'}, int( @@ -629,12 +637,14 @@ def compute_duration( Currently displays the result only for GidneyFowler (arxiv:1812.01238). """ if estimation_model == _GIDNEY_FOWLER_MODEL: - res, _, _ = get_ccz2t_costs_from_grid_search( + res, _, _ = gidney_fowler_model.get_ccz2t_costs_from_grid_search( n_logical_gates=n_logical_gates, n_algo_qubits=int(algorithm.n_algo_qubits), phys_err=physical_error_rate, error_budget=error_budget, - factory_iter=[MultiFactory(f, magic_count) for f in iter_ccz2t_factories()], + factory_iter=[ + MultiFactory(f, magic_count) for f in gidney_fowler_model.iter_ccz2t_factories() + ], ) unit, duration = format_duration([res.duration_hr * 60 * 60 * 10**6]) return {'display': 'block'}, f'{duration[0]:g} {unit}' diff --git a/qualtran/surface_code/ui_test.py b/qualtran/surface_code/ui_test.py index 51767b2b6..4fa2944a0 100644 --- a/qualtran/surface_code/ui_test.py +++ b/qualtran/surface_code/ui_test.py @@ -26,7 +26,7 @@ def test_ensure_support_for_all_supported_models(estimation_model: str): error_budget=1e-3, estimation_model=estimation_model, algorithm_data=(10**11,) * 6, - qec_name='FowlerSuperconductingQubits', + qec_name='GidneyFowler', magic_name='FifteenToOne733', magic_count=1, rotaion_model_name='BeverlandEtAlRotationCost', @@ -78,7 +78,7 @@ def test_update(estimation_model: str, desired): error_budget=1e-3, estimation_model=estimation_model, algorithm_data=(10**11,) * 6, - qec_name='FowlerSuperconductingQubits', + qec_name='GidneyFowler', magic_name='FifteenToOne733', magic_count=1, rotaion_model_name='BeverlandEtAlRotationCost', @@ -101,7 +101,7 @@ def test_update_bad_input(): error_budget=1e-3, estimation_model=ui._SUPPORTED_ESTIMATION_MODELS[0], algorithm_data=(10**11,) * 6, - qec_name='FowlerSuperconductingQubits', + qec_name='GidneyFowler', magic_name='FifteenToOne733', magic_count=1, rotaion_model_name='BeverlandEtAlRotationCost',