diff --git a/qualtran/surface_code/ccz2t_cost_model.py b/qualtran/surface_code/ccz2t_cost_model.py index d5c5cc286..fab5e9f13 100644 --- a/qualtran/surface_code/ccz2t_cost_model.py +++ b/qualtran/surface_code/ccz2t_cost_model.py @@ -17,9 +17,9 @@ from attrs import frozen +import qualtran.surface_code.quantum_error_correction_scheme_summary as qec from qualtran.surface_code.algorithm_summary import AlgorithmSummary from qualtran.surface_code.data_block import DataBlock, SimpleDataBlock -from qualtran.surface_code.formulae import code_distance_from_budget, error_at from qualtran.surface_code.magic_state_factory import MagicStateFactory from qualtran.surface_code.physical_cost import PhysicalCost @@ -39,6 +39,7 @@ class CCZ2TFactory(MagicStateFactory): distillation_l1_d: int = 15 distillation_l2_d: int = 31 + qec_scheme: qec.QuantumErrorCorrectionSchemeSummary = qec.FowlerSuperconductingQubits # ------------------------------------------------------------------------------- # ---- Level 0 --------- @@ -61,7 +62,9 @@ def l0_topo_error_t_gate(self, phys_err: float) -> float: # 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_at(phys_err, d=self.distillation_l1_d // 2) + topo_error_per_unit_cell = self.qec_scheme.logical_error_rate( + physical_error_rate=phys_err, 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. @@ -83,12 +86,16 @@ def l1_topo_error_factory(self, phys_err: float) -> float: """Topological error associated with a L1 T factory.""" # The L1 T factory uses approximately 1000 L1 unit cells. - return 1000 * error_at(phys_err, d=self.distillation_l1_d) + return 1000 * self.qec_scheme.logical_error_rate( + physical_error_rate=phys_err, code_distance=self.distillation_l1_d + ) def l1_topo_error_t_gate(self, phys_err: float) -> 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_at(phys_err, d=self.distillation_l1_d) + return 100 * self.qec_scheme.logical_error_rate( + physical_error_rate=phys_err, code_distance=self.distillation_l1_d + ) def l1_distillation_error(self, phys_err: float) -> float: """The error due to level-0 faulty T states making it through distillation undetected. @@ -117,7 +124,9 @@ def l2_error(self, phys_err: float) -> float: """ # The L2 CCZ factory and catalyzed T factory both use approximately 1000 L2 unit cells. - l2_topo_error_factory = 1000 * error_at(phys_err, d=self.distillation_l2_d) + l2_topo_error_factory = 1000 * self.qec_scheme.logical_error_rate( + physical_error_rate=phys_err, code_distance=self.distillation_l2_d + ) # Distillation error for this level. l2_distillation_error = 28 * self.l1_error(phys_err) ** 2 @@ -156,11 +165,11 @@ def get_ccz2t_costs( n_magic: AlgorithmSummary, n_algo_qubits: int, phys_err: float = 1e-3, - error_budget: Optional[float] = 1e-2, + error_budget: float = 1e-2, cycle_time_us: float = 1.0, - routing_overhead: Optional[float] = 0.5, - factory: MagicStateFactory = None, - data_block: DataBlock = None, + routing_overhead: float = 0.5, + factory: Optional[MagicStateFactory] = None, + data_block: Optional[DataBlock] = None, ) -> PhysicalCost: """Physical costs using the model from catalyzed CCZ to 2T paper. @@ -198,11 +207,21 @@ def get_ccz2t_costs( if data_block is None: # Use "left over" budget for data qubits. err_budget = error_budget - distillation_error + if err_budget < 0: + raise ValueError( + f'distillation error {distillation_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 = code_distance_from_budget(phys_err=phys_err, budget=target_err_per_round) - data_block = SimpleDataBlock(data_d=data_d, routing_overhead=routing_overhead) + data_d = qec.FowlerSuperconductingQubits.code_distance_from_budget( + physical_error_rate=phys_err, budget=target_err_per_round + ) + data_block = SimpleDataBlock( + data_d=data_d, + routing_overhead=routing_overhead, + qec_scheme=qec.FowlerSuperconductingQubits, + ) data_error = data_block.data_error( n_algo_qubits=n_algo_qubits, n_cycles=n_cycles, phys_err=phys_err diff --git a/qualtran/surface_code/ccz2t_cost_model_test.py b/qualtran/surface_code/ccz2t_cost_model_test.py index 329ebc626..6e984a1da 100644 --- a/qualtran/surface_code/ccz2t_cost_model_test.py +++ b/qualtran/surface_code/ccz2t_cost_model_test.py @@ -13,6 +13,7 @@ # limitations under the License. import numpy as np +import pytest from qualtran.surface_code.algorithm_summary import AlgorithmSummary from qualtran.surface_code.ccz2t_cost_model import get_ccz2t_costs @@ -30,3 +31,8 @@ def test_vs_spreadsheet(): np.testing.assert_allclose(re.failure_prob, 0.0084, rtol=1e-3) np.testing.assert_allclose(re.footprint, 4.00e5, rtol=1e-3) np.testing.assert_allclose(re.duration_hr, 7.53, rtol=1e-3) + + +def test_invalid_input(): + with pytest.raises(ValueError): + _ = get_ccz2t_costs(n_magic=AlgorithmSummary(toffoli_gates=3.2e10), n_algo_qubits=2196) diff --git a/qualtran/surface_code/data_block.py b/qualtran/surface_code/data_block.py index fb482abda..319e7592b 100644 --- a/qualtran/surface_code/data_block.py +++ b/qualtran/surface_code/data_block.py @@ -17,7 +17,7 @@ from attrs import frozen -from qualtran.surface_code.formulae import error_at +import qualtran.surface_code.quantum_error_correction_scheme_summary as qec class DataBlock(metaclass=abc.ABCMeta): @@ -28,6 +28,7 @@ class DataBlock(metaclass=abc.ABCMeta): called the data block, and we provide its costs here. """ + @abc.abstractmethod def footprint(self, n_algo_qubits: int) -> int: """The number of physical qubits used by the data block. @@ -36,6 +37,7 @@ def footprint(self, n_algo_qubits: int) -> int: accessed. """ + @abc.abstractmethod def data_error(self, n_algo_qubits: int, n_cycles: int, phys_err: float) -> float: """The error associated with storing data on `n_algo_qubits` for `n_cycles`.""" @@ -52,6 +54,7 @@ class SimpleDataBlock(DataBlock): data_d: int routing_overhead: float = 0.5 + qec_scheme: qec.QuantumErrorCorrectionSchemeSummary = qec.FowlerSuperconductingQubits def n_logical_qubits(self, n_algo_qubits: int) -> int: """Number of logical qubits including overhead. @@ -64,10 +67,12 @@ def n_logical_qubits(self, n_algo_qubits: int) -> int: def footprint(self, n_algo_qubits: int) -> int: """The number of physical qubits used by the data block.""" - n_phys_per_logical = 2 * self.data_d**2 + n_phys_per_logical = self.qec_scheme.physical_qubits(self.data_d) return self.n_logical_qubits(n_algo_qubits) * n_phys_per_logical def data_error(self, n_algo_qubits: int, n_cycles: int, phys_err: float) -> float: """The error associated with storing data on `n_algo_qubits` for `n_cycles`.""" data_cells = self.n_logical_qubits(n_algo_qubits) * n_cycles - return data_cells * error_at(phys_err, d=self.data_d) + return data_cells * self.qec_scheme.logical_error_rate( + physical_error_rate=phys_err, code_distance=self.data_d + ) diff --git a/qualtran/surface_code/formulae.py b/qualtran/surface_code/formulae.py deleted file mode 100644 index 6283f20ae..000000000 --- a/qualtran/surface_code/formulae.py +++ /dev/null @@ -1,50 +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 math - - -def error_at(phys_err: float, *, d: int) -> float: - """Logical error suppressed with code distance `d` for this physical error rate. - - This is an estimate, see the references section. - - The formula was originally expressed as $p_l = a (b * p_p)^((d+1)/2)$ physical - error rate $p_p$ and parameters $a$ and $b$. This can alternatively be expressed with - $p_th = (1/b)$ roughly corresponding to the code threshold. This is sometimes also - expressed with $lambda = p_th / p_p$. A lambda of 10, for example, would be p_p = 1e-3 - and p_th = 0.01. The pre-factor $a$ has no clear provenance. - - References: - Low overhead quantum computation using lattice surgery. Fowler and Gidney (2018). - https://arxiv.org/abs/1808.06709. - See section XV for introduction of this formula, with citation to below. - - Surface code quantum error correction incorporating accurate error propagation. - Fowler et. al. (2010). https://arxiv.org/abs/1004.0255. - Note: this doesn't actually contain the formula from the above reference. - """ - return 0.1 * (100 * phys_err) ** ((d + 1) / 2) - - -def code_distance_from_budget(phys_err: float, budget: float) -> int: - """Get the code distance that keeps one below the logical error `budget`.""" - - # See: `error_at()`. p_l = a Λ^(-r) where r = (d+1)/2 - # Which we invert: r = ln(p_l/a) / ln(1/Λ) - r = math.log(10 * budget) / math.log(100 * phys_err) - d = 2 * math.ceil(r) - 1 - if d < 3: - return 3 - return d diff --git a/qualtran/surface_code/formulae_test.py b/qualtran/surface_code/formulae_test.py deleted file mode 100644 index bb237a72f..000000000 --- a/qualtran/surface_code/formulae_test.py +++ /dev/null @@ -1,28 +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 - -from qualtran.surface_code.formulae import code_distance_from_budget, error_at - - -def test_invert_error_at(): - phys_err = 1e-3 - budgets = np.logspace(-1, -18) - for budget in budgets: - d = code_distance_from_budget(phys_err=phys_err, budget=budget) - assert d % 2 == 1 - assert error_at(phys_err=phys_err, d=d) <= budget - if d > 3: - assert error_at(phys_err=phys_err, d=d - 2) > budget diff --git a/qualtran/surface_code/quantum_error_correction_scheme_summary.py b/qualtran/surface_code/quantum_error_correction_scheme_summary.py new file mode 100644 index 000000000..3e54522db --- /dev/null +++ b/qualtran/surface_code/quantum_error_correction_scheme_summary.py @@ -0,0 +1,123 @@ +# 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 abc +import math +from typing import Optional + +from attrs import field, frozen + + +@frozen +class QuantumErrorCorrectionSchemeSummary(abc.ABC): + r"""QuantumErrorCorrectionSchemeSummary represents a high-level view of a QEC scheme. + + QuantumErrorCorrectionSchemeSummary provides estimates for the logical error rate, + number of physical qubits and the time of an error detection cycle. + + The logical error rate as a function of code distance $d$ and physical error rate $p$ + is given by + $$ + a \left ( \frac{p}{p^*} \right )^\frac{d + 1}{2} + $$ + Where $a$ is the error_rate_scaler and $p^*$ is the error_rate_threshold. + + Note: The logical error-suppression factor $\Lambda = \frac{p^*}{p}$ + + Attributes: + error_rate_scaler: Logical error rate coefficient. + error_rate_threshold: Logical error rate threshold. + reference: source of the estimates in a human-readable format. + """ + + error_rate_scaler: float = field(repr=lambda x: f'{x:g}') + error_rate_threshold: float = field(repr=lambda x: f'{x:g}') + reference: Optional[str] = None + + def logical_error_rate(self, code_distance: int, physical_error_rate: float) -> float: + """Logical error suppressed with code distance for this physical error rate. + + This is an estimate, see the references section. + + The formula was originally expressed as $p_l = a (b * p_p)^((d+1)/2)$ physical + error rate $p_p$ and parameters $a$ and $b$. This can alternatively be expressed with + $p_th = (1/b)$ roughly corresponding to the code threshold. This is sometimes also + expressed with $lambda = p_th / p_p$. A lambda of 10, for example, would be p_p = 1e-3 + and p_th = 0.01. The pre-factor $a$ has no clear provenance. + + References: + Low overhead quantum computation using lattice surgery. Fowler and Gidney (2018). + https://arxiv.org/abs/1808.06709. + See section XV for introduction of this formula, with citation to below. + + Surface code quantum error correction incorporating accurate error propagation. + Fowler et. al. (2010). https://arxiv.org/abs/1004.0255. + 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 + ) + + def code_distance_from_budget(self, physical_error_rate: 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 + ) + d = 2 * math.ceil(r) - 1 + if d < 3: + return 3 + return d + + @abc.abstractmethod + def physical_qubits(self, code_distance: int) -> int: + """The number of physical qubits per logical qubit used by the error detection circuit.""" + + @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. + """ + + single_stabilizer_time_us: float = 1 + + def physical_qubits(self, code_distance: int) -> int: + return 2 * code_distance**2 + + 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 + + +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='https://arxiv.org/abs/2211.07629', +) + +FowlerSuperconductingQubits = SimpliedSurfaceCode( + error_rate_scaler=0.1, + error_rate_threshold=0.01, + single_stabilizer_time_us=1, + reference='https://arxiv.org/abs/1808.06709', +) diff --git a/qualtran/surface_code/quantum_error_correction_scheme_summary_test.py b/qualtran/surface_code/quantum_error_correction_scheme_summary_test.py new file mode 100644 index 000000000..77e98bea8 --- /dev/null +++ b/qualtran/surface_code/quantum_error_correction_scheme_summary_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 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 + )