From 3b75fa1611f9f03883090d8556b5de6064165456 Mon Sep 17 00:00:00 2001 From: Matthew Harrigan Date: Thu, 18 Jul 2024 13:07:33 -0700 Subject: [PATCH 1/2] Azure model and new AlgorithmSummary --- qualtran/resource_counting/_bloq_counts.py | 25 +++ qualtran/surface_code/algorithm_summary.py | 109 ++--------- .../surface_code/algorithm_summary_test.py | 101 ++-------- qualtran/surface_code/azure_cost_model.ipynb | 180 +++++++++--------- qualtran/surface_code/azure_cost_model.py | 111 +++++++---- .../surface_code/azure_cost_model_test.py | 47 +++-- qualtran/surface_code/physical_parameters.py | 65 +++++-- qualtran/surface_code/rotation_cost_model.py | 58 +++--- .../surface_code/rotation_cost_model_test.py | 12 +- qualtran/surface_code/ui.py | 53 ++++-- 10 files changed, 359 insertions(+), 402 deletions(-) diff --git a/qualtran/resource_counting/_bloq_counts.py b/qualtran/resource_counting/_bloq_counts.py index bcba4e918..14c694ae4 100644 --- a/qualtran/resource_counting/_bloq_counts.py +++ b/qualtran/resource_counting/_bloq_counts.py @@ -195,6 +195,31 @@ def total_t_and_ccz_count(self, ts_per_rotation: int = 11) -> Dict[str, int]: n_t = self.t + ts_per_rotation * self.rotation return {'n_t': n_t, 'n_ccz': n_ccz} + def total_beverland_count(self) -> Dict[str, int]: + r"""Counts used by Beverland. et. al. using notation from the reference. + + - $M_\mathrm{meas}$ is the number of measurements. + - $M_R$ is the number of rotations. + - $M_T$ is the number of T operations. + - $3*M_mathrm{Tof}$ is the number of Toffoli operations. + - $D_R$ is the number of layers containing at least one rotation. This can be smaller than + the total number of non-Clifford layers since it excludes layers consisting only of T or + Toffoli gates. Since we don't compile the 'layers' explicitly, we set this to be the + number of rotations. + + Reference: + https://arxiv.org/abs/2211.07629. + Equation D3. + """ + toffoli = self.toffoli + self.and_bloq + self.cswap + return { + 'meas': self.measurement, + 'R': self.rotation, + 'T': self.t, + 'Tof': toffoli, + 'D_R': self.rotation, + } + @frozen class QECGatesCost(CostKey[GateCounts]): diff --git a/qualtran/surface_code/algorithm_summary.py b/qualtran/surface_code/algorithm_summary.py index 54f05f4d5..05d6a72b5 100644 --- a/qualtran/surface_code/algorithm_summary.py +++ b/qualtran/surface_code/algorithm_summary.py @@ -14,118 +14,35 @@ from typing import Optional, TYPE_CHECKING -from attrs import field, frozen +from attrs import frozen -from qualtran.resource_counting import get_cost_value, QECGatesCost, QubitCount -from qualtran.surface_code.magic_count import MagicCount -from qualtran.surface_code.rotation_cost_model import RotationCostModel +from qualtran.resource_counting import GateCounts, get_cost_value, QECGatesCost, QubitCount if TYPE_CHECKING: from qualtran import Bloq -_PRETTY_FLOAT = field(default=0.0, converter=float, repr=lambda x: f'{x:g}') _QUBIT_COUNT = QubitCount() _QEC_COUNT = QECGatesCost() -@frozen +@frozen(kw_only=True) class AlgorithmSummary: - """Properties of a quantum algorithm that impact its physical cost + """Logical costs of a quantum algorithm that impact modeling of its physical cost. - Counts of different properties that affect the physical cost of - running an algorithm (e.g. number of T gates). - All counts default to zero. - Attributes: - algorithm_qubits: Number of qubits used by the algorithm $Q_{alg}$. - measurements: Number of Measurements $M_R$. - t_gates: Number of T gates $M_T$. - toffoli_gates: Number of Toffoli gates $M_{Tof}$. - rotation_gates: Number of Rotations $M_R$. - rotation_circuit_depth: Depth of rotation circuit $D_R$. + n_rotation_layers: In Qualtran, we don't actually push all the cliffords out and count + the number of rotation layers, so this is just the number of rotations $M_R$ by default. + If you are trying to reproduce numbers exactly, you can provide an explicit + number of rotation layers. """ - algorithm_qubits: float = _PRETTY_FLOAT - measurements: float = _PRETTY_FLOAT - t_gates: float = _PRETTY_FLOAT - toffoli_gates: float = _PRETTY_FLOAT - rotation_gates: float = _PRETTY_FLOAT - rotation_circuit_depth: float = _PRETTY_FLOAT - - def __mul__(self, other: int) -> 'AlgorithmSummary': - if not isinstance(other, int): - raise TypeError( - f"Multiplication isn't supported between AlgorithmSummary and non integer type {type(other)}" - ) - - return AlgorithmSummary( - algorithm_qubits=self.algorithm_qubits * other, - measurements=self.measurements * other, - t_gates=self.t_gates * other, - toffoli_gates=self.toffoli_gates * other, - rotation_gates=self.rotation_gates * other, - rotation_circuit_depth=self.rotation_circuit_depth * other, - ) - - def __rmul__(self, other: int) -> 'AlgorithmSummary': - return self.__mul__(other) - - def __add__(self, other: 'AlgorithmSummary') -> 'AlgorithmSummary': - if not isinstance(other, AlgorithmSummary): - raise TypeError( - f"Addition isn't supported between AlgorithmSummary and type {type(other)}" - ) - return AlgorithmSummary( - algorithm_qubits=self.algorithm_qubits + other.algorithm_qubits, - measurements=self.measurements + other.measurements, - t_gates=self.t_gates + other.t_gates, - toffoli_gates=self.toffoli_gates + other.toffoli_gates, - rotation_gates=self.rotation_gates + other.rotation_gates, - rotation_circuit_depth=self.rotation_circuit_depth + other.rotation_circuit_depth, - ) - - def __sub__(self, other: 'AlgorithmSummary') -> 'AlgorithmSummary': - if not isinstance(other, AlgorithmSummary): - raise TypeError( - f"Subtraction isn't supported between AlgorithmSummary and type {type(other)}" - ) - return AlgorithmSummary( - algorithm_qubits=self.algorithm_qubits - other.algorithm_qubits, - measurements=self.measurements - other.measurements, - t_gates=self.t_gates - other.t_gates, - toffoli_gates=self.toffoli_gates - other.toffoli_gates, - rotation_gates=self.rotation_gates - other.rotation_gates, - rotation_circuit_depth=self.rotation_circuit_depth - other.rotation_circuit_depth, - ) - - def to_magic_count( - self, - rotation_model: Optional[RotationCostModel] = None, - error_budget: Optional[float] = None, - ) -> MagicCount: - ret = MagicCount(n_t=self.t_gates, n_ccz=self.toffoli_gates) - if self.rotation_gates > 0: - if rotation_model is None or error_budget is None: - raise ValueError( - 'Rotation cost model and error budget must be provided to calculate rotation cost' - ) - ret = ( - ret - + rotation_model.prepartion_overhead(error_budget) - + self.rotation_gates - * rotation_model.rotation_cost(error_budget / self.rotation_gates) - ) - return ret + n_algo_qubits: int + n_logical_gates: GateCounts + n_rotation_layers: Optional[int] = None @staticmethod def from_bloq(bloq: 'Bloq') -> 'AlgorithmSummary': gate_count = get_cost_value(bloq, _QEC_COUNT) - return AlgorithmSummary( - t_gates=gate_count.t, - toffoli_gates=gate_count.toffoli + gate_count.and_bloq + gate_count.cswap, - rotation_gates=gate_count.rotation, - measurements=gate_count.measurement, - rotation_circuit_depth=gate_count.rotation, - algorithm_qubits=float(get_cost_value(bloq, _QUBIT_COUNT)), - ) + qubit_count = int(get_cost_value(bloq, _QUBIT_COUNT)) + return AlgorithmSummary(n_algo_qubits=qubit_count, n_logical_gates=gate_count) diff --git a/qualtran/surface_code/algorithm_summary_test.py b/qualtran/surface_code/algorithm_summary_test.py index f1ff8ab9e..5ec2280cf 100644 --- a/qualtran/surface_code/algorithm_summary_test.py +++ b/qualtran/surface_code/algorithm_summary_test.py @@ -16,105 +16,40 @@ import pytest from qualtran.bloqs import basic_gates, mcmt, rotations -from qualtran.surface_code.algorithm_summary import AlgorithmSummary -from qualtran.surface_code.magic_count import MagicCount - - -def test_mul(): - assert AlgorithmSummary(t_gates=9) == 3 * AlgorithmSummary(t_gates=3) - - with pytest.raises(TypeError): - _ = complex(1, 0) * AlgorithmSummary(rotation_gates=1) # type: ignore[operator] - - -def test_addition(): - with pytest.raises(TypeError): - _ = AlgorithmSummary() + 5 # type: ignore[operator] - - a = AlgorithmSummary( - algorithm_qubits=7, - measurements=8, - t_gates=8, - toffoli_gates=9, - rotation_gates=8, - rotation_circuit_depth=3, - ) - b = AlgorithmSummary( - algorithm_qubits=4, - measurements=1, - t_gates=1, - toffoli_gates=4, - rotation_gates=2, - rotation_circuit_depth=1, - ) - assert a + b == AlgorithmSummary( - algorithm_qubits=11, - measurements=9, - t_gates=9, - toffoli_gates=13, - rotation_gates=10, - rotation_circuit_depth=4, - ) - - -def test_subtraction(): - with pytest.raises(TypeError): - _ = AlgorithmSummary() - 5 # type: ignore[operator] - - a = AlgorithmSummary( - algorithm_qubits=7, - measurements=8, - t_gates=8, - toffoli_gates=9, - rotation_gates=8, - rotation_circuit_depth=3, - ) - b = AlgorithmSummary( - algorithm_qubits=4, - measurements=1, - t_gates=1, - toffoli_gates=4, - rotation_gates=2, - rotation_circuit_depth=1, - ) - assert a - b == AlgorithmSummary( - algorithm_qubits=3, - measurements=7, - t_gates=7, - toffoli_gates=5, - rotation_gates=6, - rotation_circuit_depth=2, - ) - - assert AlgorithmSummary(t_gates=1, toffoli_gates=4).to_magic_count() == MagicCount( - n_ccz=4, n_t=1 - ) - - with pytest.raises(ValueError): - _ = AlgorithmSummary(rotation_gates=1).to_magic_count() +from qualtran.resource_counting import GateCounts +from qualtran.surface_code import AlgorithmSummary @pytest.mark.parametrize( ['bloq', 'summary'], [ - [basic_gates.TGate(is_adjoint=False), AlgorithmSummary(algorithm_qubits=1, t_gates=1)], - [basic_gates.Toffoli(), AlgorithmSummary(algorithm_qubits=3, toffoli_gates=1)], - [basic_gates.TwoBitCSwap(), AlgorithmSummary(algorithm_qubits=3, toffoli_gates=1)], - [mcmt.And(), AlgorithmSummary(algorithm_qubits=3, toffoli_gates=1)], + [ + basic_gates.TGate(is_adjoint=False), + AlgorithmSummary(n_algo_qubits=1, n_logical_gates=GateCounts(t=1)), + ], + [ + basic_gates.Toffoli(), + AlgorithmSummary(n_algo_qubits=3, n_logical_gates=GateCounts(toffoli=1)), + ], + [ + basic_gates.TwoBitCSwap(), + AlgorithmSummary(n_algo_qubits=3, n_logical_gates=GateCounts(cswap=1)), + ], + [mcmt.And(), AlgorithmSummary(n_algo_qubits=3, n_logical_gates=GateCounts(and_bloq=1))], [ basic_gates.ZPowGate(exponent=0.1, global_shift=0.0, eps=1e-11), - AlgorithmSummary(algorithm_qubits=1, rotation_gates=1, rotation_circuit_depth=1), + AlgorithmSummary(n_algo_qubits=1, n_logical_gates=GateCounts(rotation=1)), ], [ rotations.phase_gradient.PhaseGradientUnitary( bitsize=10, exponent=1, is_controlled=False, eps=1e-10 ), - AlgorithmSummary(algorithm_qubits=10, rotation_gates=10, rotation_circuit_depth=10), + AlgorithmSummary(n_algo_qubits=10, n_logical_gates=GateCounts(rotation=10)), ], [ mcmt.MultiControlPauli(cvs=(1, 1, 1), target_gate=cirq.X), AlgorithmSummary( - algorithm_qubits=6, toffoli_gates=2, rotation_circuit_depth=0, measurements=2 + n_algo_qubits=6, n_logical_gates=GateCounts(and_bloq=2, measurement=2, clifford=3) ), ], ], diff --git a/qualtran/surface_code/azure_cost_model.ipynb b/qualtran/surface_code/azure_cost_model.ipynb index 20a4f1944..55f327f88 100644 --- a/qualtran/surface_code/azure_cost_model.ipynb +++ b/qualtran/surface_code/azure_cost_model.ipynb @@ -6,9 +6,9 @@ "metadata": {}, "source": [ "# Azure Cost Model\n", - "In this notebook, we go through the cost model in \"Assessing requirements to scale to practical quantum advantage\" by [Beverland et al](https://arxiv.org/abs/2211.07629) to reproduce the estimated costs in Appendix F.\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. Throughout, we reproduce the costs from the paper; in particular Appendix F." + "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." ] }, { @@ -34,11 +34,11 @@ "## The Inputs\n", "### Physical Parameters\n", "These are assumptions about the quantum hardware and are:\n", - "- `t_gate_ns`: Latency time of Clifford gates. \n", - "- `t_meas_ns`: Latency time of Measurement operations.\n", - "- `physical_error_rate`: Physical error rate ($p$).\n", + "- `t_gate_ns`: Execution time of Clifford gates. \n", + "- `t_meas_ns`: Execution time of Measurement operations.\n", + "- `physical_error`: Physical error rate ($p$).\n", "\n", - "In Qualtran these are represented by the `PhysicalParameters` dataclass." + "In Qualtran these are represented by the `PhysicalParameters` dataclass. Since the total execution time depends only on the cycle time, we combine the Beverland gate time estimates to get a cycle time." ] }, { @@ -47,9 +47,10 @@ "metadata": {}, "outputs": [], "source": [ - "from qualtran.surface_code.physical_parameters import BEVERLAND_PARAMS\n", + "from qualtran.surface_code import PhysicalParameters\n", "\n", - "BEVERLAND_PARAMS" + "beverland_phys_params = PhysicalParameters.from_beverland('superconducting', optimistic_err_rate=True)\n", + "beverland_phys_params" ] }, { @@ -59,12 +60,13 @@ "source": [ "### Algorithm Summary\n", "This is a summary of the circuit or algorithm to execute. This summary is several simple counts:\n", - "- `algorithm_qubits` is the number of algorithm qubits.\n", - "- `measurements` is the number of Clifford measurements ($M_R$).\n", - "- `t_gates` is the number of T gates ($M_T$).\n", - "- `toffoli_gates` is the number of Toffoli gates ($M_{Tof}$).\n", - "- `rotation_gates` is the number of rotations ($M_R$).\n", - "- `rotation_circuit_depth` is the depth of rotation circuit ($D_R$).\n", + "- `n_algo_qubits` is the number of algorithm qubits.\n", + "- `n_logical_gates` captures the number of gates, including\n", + " - `measurement` is the number of Clifford measurements ($M_R$).\n", + " - `t` is the number of T gates ($M_T$).\n", + " - `toffoli` is the number of Toffoli gates ($M_{Tof}$).\n", + " - `rotation` is the number of rotations ($M_R$).\n", + "- `n_rotation_layers` is the number of rotation layers ($D_R$).\n", "\n", "Note: The symbol in parentheses corresponds to the notation in the paper\n", "\n", @@ -77,11 +79,10 @@ "metadata": {}, "outputs": [], "source": [ - "from qualtran.surface_code.algorithm_summary import AlgorithmSummary\n", + "from qualtran.resource_counting import GateCounts\n", + "from qualtran.surface_code import AlgorithmSummary\n", "\n", - "# A circuit that applies a T then measures the qubit.\n", - "circuit_summary = AlgorithmSummary(algorithm_qubits=1, t_gates=1, measurements=1)\n", - "circuit_summary" + "AlgorithmSummary(n_algo_qubits=1, n_logical_gates=GateCounts(t=1))" ] }, { @@ -108,7 +109,7 @@ "\n", "Where $a$ and $b$ are constants that depend on the gate set and the approximation protocol. Table 1 of [Kliuchnikov et al](https://arxiv.org/abs/2203.10064) gives estimates for those constants for different combinations of gate sets and protocols.\n", "\n", - "Though [Beverland et al](https://arxiv.org/abs/2211.07629) includes slightly different coefficient values, the rotation approximation model used by Azure cost model is using $a=0.53$ and $b=4.86$ from Table 1 of the [Kliuchnikov et al](https://arxiv.org/abs/2203.10064)." + "Though [Beverland et al](https://arxiv.org/abs/2211.07629) includes slightly different coefficient values, the rotation approximation model used by Azure cost model is using $a=0.53$ and $b=4.86$ from Table 1 of [Kliuchnikov et al](https://arxiv.org/abs/2203.10064)." ] }, { @@ -131,7 +132,7 @@ "The quantum error correction scheme determines three things:\n", "1. The logical error rate given a code distance and physical error rate: $P(d)$.\n", "1. The number of physical qubits needed per logical qubit: $n(d)$.\n", - "1. The number of logical time steps: $\\tau(d)$.\n", + "1. The duration of a logical time step: $\\tau(d)$.\n", "\n", "Table V of the paper lists how these are related to the QEC scheme.\n", "\n", @@ -139,19 +140,17 @@ "$$\n", "P(d) = 0.03 \\left ( \\frac{p}{0.01} \\right) ^ \\frac{d+1}{2}\\\\\n", "n(d) = 2 d^2\\\\\n", - "\\tau(d) = \\textrm{\\{single stabilizer time\\}} \\cdot d\\\\\n", + "\\tau(d) = \\textrm{\\{single cycle time\\}} \\cdot d\\\\\n", "$$\n", "\n", "The error detection circuit time depends on several factors physical factors including the time to apply a Clifford, measurement and reset operations as well as classical processing.\n", "\n", "In Table V they don't take into account the classical processing part and assume that a reset takes the same time as a measurement leading to the formula:\n", "$$\n", - "\\textrm{\\{single stabilizer time\\}} = 4t_\\textrm{gate} + 2t_\\textrm{meas}\n", + "\\textrm{\\{single cycle time\\}} = 4t_\\textrm{gate} + 2t_\\textrm{meas}\n", "$$\n", "\n", - "Other authors (e.g. [Fowler, Gidney](https://arxiv.org/abs/1808.06709)) assume that the entire process takes a specific time (e.g. $\\approx 1\\mu s$).\n", - "\n", - "In Qualtran, we use \"single stabilizer time\" rather than fall to the low-level hardware parameters. We also default to the Fowler, Gidney parameters (i.e. `FowlerSuperconductingQubits`) when none are given." + "Other authors (e.g. [Fowler, Gidney](https://arxiv.org/abs/1808.06709)) assume that the entire process takes a specific time (e.g. $1\\mu s$)." ] }, { @@ -185,10 +184,9 @@ "### Quantum Dynamics\n", "The algorithm specs of this circuit are given as:\n", "- number of algorithm qubits: $100$\n", - "- number of rotation gates: $30,100$\n", - "- number of measurements: $1.4 \\times 10^6$\n", - "- number of T gates: $0$\n", - "- number of Toffoli gates: $0$\n", + "- number of rotation gates: $30{,}100$\n", + "- number of measurements: $1.4 \\times 10^5$\n", + "- no T or Toffoli gates\n", "- depth of rotation circuit: $501$\n", "\n", "with an error budget $\\epsilon$ of $0.001$" @@ -200,15 +198,16 @@ "metadata": {}, "outputs": [], "source": [ - "quantum_dynamics_specs = AlgorithmSummary(\n", - " algorithm_qubits=100,\n", - " rotation_gates=30100,\n", - " # Note in the paper the number of measurements\n", - " # has an extra zero which we assume to be a typo.\n", - " measurements=1.4e5,\n", - " rotation_circuit_depth=501,\n", - ")\n", - "quantum_dynamics_specs" + "qd_alg = AlgorithmSummary(\n", + " n_algo_qubits = 100,\n", + " n_logical_gates = GateCounts(\n", + " rotation=30_100,\n", + " # Note in the paper the number of measurements\n", + " # has an extra zero which we assume to be a typo.\n", + " measurement=1.4e5,\n", + " ),\n", + " n_rotation_layers = 501\n", + ")" ] }, { @@ -224,10 +223,9 @@ "metadata": {}, "outputs": [], "source": [ - "from qualtran.surface_code.data_block import FastDataBlock\n", - "\n", - "logical_qubits = FastDataBlock.get_n_tiles(n_algo_qubits=quantum_dynamics_specs.algorithm_qubits)\n", + "from qualtran.surface_code import FastDataBlock\n", "\n", + "logical_qubits = FastDataBlock.get_n_tiles(n_algo_qubits=qd_alg.n_algo_qubits)\n", "print('Q =', logical_qubits)" ] }, @@ -242,7 +240,9 @@ "# 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", - " error_budget=error_budget, alg=quantum_dynamics_specs, rotation_model=BeverlandEtAlRotationCost\n", + " error_budget=error_budget,\n", + " alg=qd_alg,\n", + " rotation_model=BeverlandEtAlRotationCost,\n", ")\n", "print('C_min = %e' % c_min)" ] @@ -255,7 +255,9 @@ "source": [ "# And the number of needed T operations (Eq D4)\n", "t_operations = azure_cost_model.t_states(\n", - " error_budget=error_budget, alg=quantum_dynamics_specs, rotation_model=BeverlandEtAlRotationCost\n", + " error_budget=error_budget,\n", + " alg=qd_alg,\n", + " rotation_model=BeverlandEtAlRotationCost\n", ")\n", "print('M = %e' % t_operations)" ] @@ -265,7 +267,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Comparing our esimates of $Q = 230, C_{min} = 1.8 \\times 10^5, M = 6 \\times 10^5$ to the paper estimates of \n", + "Comparing our esimates of $Q = 230, C_{min} = 1.8 \\times 10^5, M = 9 \\times 10^5$ to the paper estimates of \n", "$Q = 230, C_{min} = 1.5 \\times 10^5, M = 2.4 \\times 10^6$. We find a match for $Q$ and $C_{min}$ however we are off by 4x for the number of T gates $M$.\n", "\n", "D4 gives the formula for $M$ as $$M = M_T + 4 M_{Tof} + M_R \\lceil A \\log_2{M_R/\\epsilon_{syn}} + B\\rceil$$\n", @@ -294,9 +296,9 @@ "d = azure_cost_model.code_distance(\n", " error_budget=error_budget,\n", " time_steps=c_min,\n", - " alg=quantum_dynamics_specs,\n", - " qec=BeverlandSuperconductingQubits,\n", - " physical_error_rate=BEVERLAND_PARAMS.physical_error_rate,\n", + " alg=qd_alg,\n", + " qec_scheme=BeverlandSuperconductingQubits,\n", + " physical_error=beverland_phys_params.physical_error,\n", ")\n", "print(f'{d=}')" ] @@ -315,8 +317,8 @@ "metadata": {}, "outputs": [], "source": [ - "t_s = qec.error_detection_circuit_time_us(d) * 1e-6 * c_min\n", - "f'algorithm run time of {t_s:g} seconds'.format()" + "t_s = d * beverland_phys_params.cycle_time_us * 1e-6 * c_min\n", + "print(f'algorithm run time of {t_s:g} seconds')" ] }, { @@ -324,7 +326,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Next, we examine the magic state factories. In the paper, for the quantum dynamics example, we are given $199$ factories each producing one T state every $46.8 \\mu s$ at an error rate of $5.6e-11$ while consuming $3,240$ qubits. " + "Next, we examine the magic state factories. In the paper, for the quantum dynamics example, we are given $199$ factories each producing one T state every $46.8 \\mu s$ at an error rate of $5.6\\times 10^{-11}$ while consuming $3{,}240$ physical qubits. " ] }, { @@ -334,7 +336,7 @@ "outputs": [], "source": [ "num_factories = 199\n", - "factory_qubits = 3240" + "factory_qubits = 3_240" ] }, { @@ -365,7 +367,7 @@ "source": [ "### Quantum Chemistry\n", "The algorithm specs of this circuit are given as:\n", - "- number of algorithm qubits: 1318\n", + "- number of algorithm qubits: $1318$\n", "- number of rotation gates: $2.06 \\times 10^8$\n", "- number of measurements: $1.37 \\times 10^9$\n", "- number of T gates: $5.53 \\times 10^7$\n", @@ -381,15 +383,17 @@ "metadata": {}, "outputs": [], "source": [ - "quantum_chemistry_specs = AlgorithmSummary(\n", - " algorithm_qubits=1318,\n", - " rotation_gates=2.06e8,\n", - " measurements=1.37e9,\n", - " toffoli_gates=1.35e11,\n", - " t_gates=5.53e7,\n", - " rotation_circuit_depth=2.05e8,\n", + "chem_alg = AlgorithmSummary(\n", + " n_algo_qubits=1318,\n", + " n_logical_gates=GateCounts(\n", + " rotation=2.06e8,\n", + " measurement=1.37e9,\n", + " toffoli=1.35e11,\n", + " t=5.53e7,\n", + " ),\n", + " n_rotation_layers=2.05e8,\n", ")\n", - "quantum_chemistry_specs" + "chem_alg" ] }, { @@ -398,7 +402,7 @@ "metadata": {}, "outputs": [], "source": [ - "logical_qubits = FastDataBlock.get_n_tiles(n_algo_qubits=quantum_chemistry_specs.algorithm_qubits)\n", + "logical_qubits = FastDataBlock.get_n_tiles(n_algo_qubits=chem_alg.n_algo_qubits)\n", "\n", "print('Q =', logical_qubits)" ] @@ -412,9 +416,9 @@ "# 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", - " error_budget=error_budget, alg=quantum_chemistry_specs, rotation_model=BeverlandEtAlRotationCost\n", + " error_budget=error_budget, alg=chem_alg, rotation_model=BeverlandEtAlRotationCost\n", ")\n", - "print('C_{min} = %g' % c_min)" + "print('C_min = %g' % c_min)" ] }, { @@ -425,7 +429,7 @@ "source": [ "# And the number of needed T operations (Eq D4)\n", "t_operations = azure_cost_model.t_states(\n", - " error_budget=error_budget, alg=quantum_chemistry_specs, rotation_model=BeverlandEtAlRotationCost\n", + " error_budget=error_budget, alg=chem_alg, rotation_model=BeverlandEtAlRotationCost\n", ")\n", "print('M = %g' % t_operations)" ] @@ -458,9 +462,9 @@ "d = azure_cost_model.code_distance(\n", " error_budget=error_budget,\n", " time_steps=c_min,\n", - " alg=quantum_chemistry_specs,\n", - " qec=qec,\n", - " physical_error_rate=BEVERLAND_PARAMS.physical_error_rate,\n", + " alg=chem_alg,\n", + " qec_scheme=qec,\n", + " physical_error=beverland_phys_params.physical_error,\n", ")\n", "print(f'{d=}')" ] @@ -479,9 +483,9 @@ "metadata": {}, "outputs": [], "source": [ - "total_seconds = qec.error_detection_circuit_time_us(d) * 1e-6 * c_min\n", + "total_seconds = beverland_phys_params.cycle_time_us * d * 1e-6 * c_min\n", "total_days = total_seconds / 3600 / 24\n", - "'algorithm run time of %g days' % (total_days)" + "print(f'algorithm run time of {total_days:g} days')" ] }, { @@ -507,7 +511,7 @@ "outputs": [], "source": [ "num_factories = 17\n", - "factory_qubits = 16000" + "factory_qubits = 16_000" ] }, { @@ -538,7 +542,7 @@ "source": [ "### Shor Factoring\n", "The algorithm specs of this circuit are given as:\n", - "- number of algorithm qubits: $12,581$\n", + "- number of algorithm qubits: $12{,}581$\n", "- number of rotation gates: $12$\n", "- number of measurements: $1.08 \\times 10^9$\n", "- number of T gates: 12\n", @@ -554,15 +558,17 @@ "metadata": {}, "outputs": [], "source": [ - "shor_specs = AlgorithmSummary(\n", - " algorithm_qubits=12581,\n", - " rotation_gates=12,\n", - " measurements=1.08e9,\n", - " rotation_circuit_depth=12,\n", - " # Note in the paper the number of Toffoli operations is 3.73e10.\n", - " # However we assume that the exponent has a typo and that the number is 3.73e9.\n", - " toffoli_gates=3.73e9,\n", - " t_gates=12,\n", + "shor_alg = AlgorithmSummary(\n", + " n_algo_qubits=12581,\n", + " n_logical_gates=GateCounts(\n", + " rotation=12,\n", + " measurement=1.08e9,\n", + " # Note in the paper the number of Toffoli operations is 3.73e10.\n", + " # However we assume that the exponent has a typo and that the number is 3.73e9.\n", + " toffoli=3.73e9,\n", + " t=12,\n", + " ),\n", + " n_rotation_layers=12,\n", ")" ] }, @@ -572,7 +578,7 @@ "metadata": {}, "outputs": [], "source": [ - "logical_qubits = FastDataBlock.get_n_tiles(n_algo_qubits=shor_specs.algorithm_qubits)\n", + "logical_qubits = FastDataBlock.get_n_tiles(n_algo_qubits=shor_alg.n_algo_qubits)\n", "\n", "print('Q =', logical_qubits)" ] @@ -586,7 +592,7 @@ "# 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", - " error_budget=error_budget, alg=shor_specs, rotation_model=BeverlandEtAlRotationCost\n", + " error_budget=error_budget, alg=shor_alg, rotation_model=BeverlandEtAlRotationCost\n", ")\n", "print('C_min = %e' % c_min)" ] @@ -599,7 +605,7 @@ "source": [ "# And the number of needed T operations (Eq D4)\n", "t_operations = azure_cost_model.t_states(\n", - " error_budget=error_budget, alg=shor_specs, rotation_model=BeverlandEtAlRotationCost\n", + " error_budget=error_budget, alg=shor_alg, rotation_model=BeverlandEtAlRotationCost\n", ")\n", "print('M = %e' % t_operations)" ] @@ -621,9 +627,9 @@ "d = azure_cost_model.code_distance(\n", " error_budget=error_budget,\n", " time_steps=c_min,\n", - " alg=shor_specs,\n", - " qec=qec,\n", - " physical_error_rate=BEVERLAND_PARAMS.physical_error_rate,\n", + " alg=shor_alg,\n", + " qec_scheme=qec,\n", + " physical_error=beverland_phys_params.physical_error,\n", ")\n", "print(f'{d=}')" ] @@ -642,7 +648,7 @@ "metadata": {}, "outputs": [], "source": [ - "total_seconds = qec.error_detection_circuit_time_us(d) * 1e-6 * c_min\n", + "total_seconds = beverland_phys_params.cycle_time_us * d * 1e-6 * c_min\n", "total_hours = total_seconds / 3600\n", "h = int(total_hours)\n", "m = (total_hours - h) * 60\n", diff --git a/qualtran/surface_code/azure_cost_model.py b/qualtran/surface_code/azure_cost_model.py index 19610df68..c84051387 100644 --- a/qualtran/surface_code/azure_cost_model.py +++ b/qualtran/surface_code/azure_cost_model.py @@ -13,17 +13,22 @@ # limitations under the License. import math +from typing import TYPE_CHECKING -from qualtran.surface_code.algorithm_summary import AlgorithmSummary -from qualtran.surface_code.data_block import FastDataBlock -from qualtran.surface_code.quantum_error_correction_scheme_summary import ( - QuantumErrorCorrectionSchemeSummary, -) -from qualtran.surface_code.rotation_cost_model import RotationCostModel +import attrs + +from qualtran.resource_counting import GateCounts + +if TYPE_CHECKING: + from qualtran.surface_code import ( + AlgorithmSummary, + QuantumErrorCorrectionSchemeSummary, + RotationCostModel, + ) def minimum_time_steps( - error_budget: float, alg: AlgorithmSummary, rotation_model: RotationCostModel + *, error_budget: float, alg: 'AlgorithmSummary', rotation_model: 'RotationCostModel' ) -> int: r"""Minimum number of time steps needed for the algorithm. @@ -31,37 +36,52 @@ def minimum_time_steps( $$ M_\mathrm{meas} + M_R + M_T + 3 M_\mathrm{Tof} + D_R \textrm{rotation cost} $$ + Where: - $M_\mathrm{meas}$ is the number of measurements. - $M_R$ is the number of rotations. - $M_T$ is the number of T operations. - $M_mathrm{Tof}$ is the number of toffoli operations. - $D_R$ is the depth of the rotation circuit. - $\textrm{rotation cost}$ is the number of T operations needed to approximate a rotation to $\epsilon/(3*M_R)$. - Source: Equation D3 in https://arxiv.org/abs/2211.07629. + - $M_\mathrm{meas}$ is the number of measurements. + - $M_R$ is the number of rotations. + - $M_T$ is the number of T operations. + - $M_mathrm{Tof}$ is the number of toffoli operations. + - $D_R$ is the number of layers containing at least one rotation. This can be smaller than + the total number of non-Clifford layers since it excludes layers consisting only of T or + Toffoli gates. + - $\textrm{rotation cost}$ is the number of T operations needed to approximate a rotation to $\epsilon/(3*M_R)$. + + Reference: + https://arxiv.org/abs/2211.07629. + Equation D3. Args: - error_budget: Error Budget. - alg: A summary of an algorithm/circuit. + error_budget: The total error budget. One third is prescribed to be used for rotation + synthesis. + alg: The logical algorithm costs, from which we extract the gate count. rotation_model: Cost model used to compute the number of T gates needed to approximate rotations. """ - c_min = math.ceil(alg.measurements + alg.rotation_gates + alg.t_gates + 3 * alg.toffoli_gates) + M = alg.n_logical_gates.total_beverland_count() + c_min = M['meas'] + M['R'] + M['T'] + 3 * M['Tof'] eps_syn = error_budget / 3 - if alg.rotation_gates > 0: - rotation_cost = rotation_model.rotation_cost(eps_syn / alg.rotation_gates) - c_min += math.ceil( - alg.rotation_circuit_depth * (rotation_cost.n_t + 4 * rotation_cost.n_ccz) - ) + if M['R'] > 0: + # NOTE! argument to the rotation_cost method is inverted relative to eq. D3. + rotation_cost = rotation_model.rotation_cost(eps_syn / M['R']) + + if alg.n_rotation_layers is not None: + # We don't actually push all the cliffords out and count the number of + # rotation layers, so this is just the number of rotations $M_R$ by default. + # If you are trying to reproduce numbers exactly, you can provide an explicit + # number of rotation layers. + M['D_R'] = alg.n_rotation_layers + c_min += math.ceil(M['D_R'] * (rotation_cost.t + 4 * rotation_cost.toffoli)) return c_min def code_distance( + *, error_budget: float, time_steps: float, - alg: AlgorithmSummary, - qec: QuantumErrorCorrectionSchemeSummary, - physical_error_rate: float, + alg: 'AlgorithmSummary', + qec_scheme: 'QuantumErrorCorrectionSchemeSummary', + physical_error: float, ) -> int: r"""Minimum code distance needed to run the algorithm within the error budget. @@ -74,16 +94,40 @@ def code_distance( Args: error_budget: Error Budget. time_steps: Number of time steps used to run the algorithm. - alg: A summary of an algorithm/circuit. - qec: Quantum Error Correction Scheme. - physical_error_rate: The physical error rate of the device. + alg: The logical algorithm costs, from which we extract the gate count. + qec_scheme: Quantum Error Correction Scheme. + physical_error: The physical error rate of the device. """ - q = FastDataBlock.get_n_tiles(n_algo_qubits=int(alg.algorithm_qubits)) - return qec.code_distance_from_budget(physical_error_rate, error_budget / (3 * q * time_steps)) + from qualtran.surface_code import FastDataBlock + + q = FastDataBlock.get_n_tiles(n_algo_qubits=alg.n_algo_qubits) + return qec_scheme.code_distance_from_budget(physical_error, error_budget / (3 * q * time_steps)) + + +def n_discrete_logical_gates( + *, eps_syn: float, alg: 'AlgorithmSummary', rotation_model: 'RotationCostModel' +) -> GateCounts: + r"""Total number of T and CCZ states after synthesizing rotations. + + Args: + eps_syn: The error budget for synthesizing rotations. + alg: The logical algorithm costs, from which we extract the gate count. + rotation_model: Cost model used to compute the number of T gates + needed to approximate rotations. + """ + n_rotations: int = alg.n_logical_gates.rotation + ret = attrs.evolve(alg.n_logical_gates, rotation=0) + if n_rotations > 0: + ret = ( + ret + + rotation_model.preparation_overhead(eps_syn) + + n_rotations * rotation_model.rotation_cost(eps_syn / n_rotations) + ) + return ret def t_states( - error_budget: float, alg: AlgorithmSummary, rotation_model: RotationCostModel + *, error_budget: float, alg: 'AlgorithmSummary', rotation_model: 'RotationCostModel' ) -> float: r"""Total number of T states consumed by the algorithm. @@ -105,5 +149,6 @@ def t_states( needed to approximate rotations. """ eps_syn = error_budget / 3 - total_magic = alg.to_magic_count(rotation_model=rotation_model, error_budget=eps_syn) - return total_magic.n_t + 4 * total_magic.n_ccz + return n_discrete_logical_gates( + eps_syn=eps_syn, alg=alg, rotation_model=rotation_model + ).total_t_count() diff --git a/qualtran/surface_code/azure_cost_model_test.py b/qualtran/surface_code/azure_cost_model_test.py index 952cf9fd5..36f54f7ed 100644 --- a/qualtran/surface_code/azure_cost_model_test.py +++ b/qualtran/surface_code/azure_cost_model_test.py @@ -16,8 +16,8 @@ from attrs import frozen import qualtran.testing as qlt_testing -from qualtran.surface_code import azure_cost_model -from qualtran.surface_code.algorithm_summary import AlgorithmSummary +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, ) @@ -40,10 +40,9 @@ class Test: _TESTS = [ Test( alg=AlgorithmSummary( - algorithm_qubits=100, - rotation_gates=30100, - measurements=1.4 * 10**6, - rotation_circuit_depth=501, + n_algo_qubits=100, + n_logical_gates=GateCounts(rotation=30_100, measurement=int(1.4e6)), + n_rotation_layers=501, ), error_budget=1e-3, c_min=1.5e6, @@ -53,12 +52,11 @@ class Test: ), Test( alg=AlgorithmSummary( - algorithm_qubits=1318, - t_gates=5.53e7, - rotation_circuit_depth=2.05e8, - rotation_gates=2.06e8, - toffoli_gates=1.35e11, - measurements=1.37e9, + n_algo_qubits=1318, + n_logical_gates=GateCounts( + t=int(5.53e7), rotation=int(2.06e8), toffoli=int(1.35e11), measurement=int(1.37e9) + ), + n_rotation_layers=int(2.05e8), ), error_budget=1e-2, c_min=4.1e11, @@ -68,12 +66,11 @@ class Test: ), Test( alg=AlgorithmSummary( - algorithm_qubits=12581, - t_gates=12, - rotation_circuit_depth=12, - rotation_gates=12, - toffoli_gates=3.73e9, - measurements=1.08e9, + n_algo_qubits=12581, + n_logical_gates=GateCounts( + t=12, rotation=12, toffoli=int(3.73e9), measurement=int(1.08e9) + ), + n_rotation_layers=12, ), error_budget=1 / 3, c_min=1.23e10, @@ -87,7 +84,7 @@ class Test: @pytest.mark.parametrize('test', _TESTS) def test_minimum_time_step(test: Test): got = azure_cost_model.minimum_time_steps( - test.error_budget, test.alg, rotation_model=BeverlandEtAlRotationCost + error_budget=test.error_budget, alg=test.alg, rotation_model=BeverlandEtAlRotationCost ) assert got == pytest.approx(test.c_min, rel=0.1) @@ -95,11 +92,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( - test.error_budget, - test.time_steps, - test.alg, - qec=BeverlandSuperconductingQubits, - physical_error_rate=1e-4, + error_budget=test.error_budget, + time_steps=test.time_steps, + alg=test.alg, + qec_scheme=BeverlandSuperconductingQubits, + physical_error=1e-4, ) assert got == test.code_distance @@ -107,7 +104,7 @@ def test_code_distance(test: Test): @pytest.mark.parametrize('test', _TESTS) def test_t_states(test: Test): got = azure_cost_model.t_states( - test.error_budget, test.alg, rotation_model=BeverlandEtAlRotationCost + error_budget=test.error_budget, alg=test.alg, rotation_model=BeverlandEtAlRotationCost ) assert got == pytest.approx(test.t_states, rel=0.1) diff --git a/qualtran/surface_code/physical_parameters.py b/qualtran/surface_code/physical_parameters.py index e793eb56d..eeb750532 100644 --- a/qualtran/surface_code/physical_parameters.py +++ b/qualtran/surface_code/physical_parameters.py @@ -11,35 +11,66 @@ # 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 typing import Optional from attrs import field, frozen -from qualtran.surface_code.reference import Reference - @frozen class PhysicalParameters: """The physical properties of a quantum computer. Attributes: - t_gate_ns: Clifford gate physical time. - t_meas_ns: Measurement physical time. - physical_error_rate: Physical error rate. - reference: Source of these estimates. + physical_error: The error rate of the underlying physical qubits. + cycle_time_us: The number of microseconds it takes to do one cycle of error correction. + """ - t_gate_ns: float = field(repr=lambda x: f'{x:g}') - t_meas_ns: float = field(repr=lambda x: f'{x:g}') + physical_error: float = field(default=1e-3, repr=lambda x: f'{x:g}') + cycle_time_us: float = 1.0 + + @classmethod + def from_beverland( + cls, qubit_modality: str = 'superconducting', optimistic_err_rate: bool = False + ): + """The physical parameters considered in the reference. + + Args: + qubit_modality: One of "superconducting", "ion", or "majorana". This sets the + cycle time, with ions being considerably slower. + optimistic_err_rate: In the reference, the authors consider two error rates, which + they term "realistic" and "optimistic". Set this to `True` to use optimistic + error rates. - physical_error_rate: float = field(default=1e-3, repr=lambda x: f'{x:g}') + References: + [https://arxiv.org/abs/2211.07629](Assessing requirements to scale to practical quantum advantage). + Beverland et. al. (2022). + """ + if optimistic_err_rate: + phys_err_rate = 1e-4 + else: + phys_err_rate = 1e-3 - reference: Optional[Reference] = None + if qubit_modality == 'ion': + t_gate_ns = 100_000 + t_meas_ns = 100_000 + elif qubit_modality == 'superconducting': + t_gate_ns = 50 + t_meas_ns = 100 + elif qubit_modality == 'majorana': + if optimistic_err_rate: + phys_err_rate = 1e-6 + else: + phys_err_rate = 1e-4 + t_gate_ns = 100 + t_meas_ns = 100 + else: + raise ValueError( + f"Unknown qubit modality {qubit_modality}. Must be one " + f"of 'ion', 'superconducting', or 'majorana'." + ) -BEVERLAND_PARAMS = PhysicalParameters( - t_gate_ns=50, # 50ns - t_meas_ns=100, # 100ns - physical_error_rate=1e-4, - reference=Reference(url='https://arxiv.org/abs/2211.07629'), -) + cycle_time_ns = 4 * t_gate_ns + 2 * t_meas_ns + return PhysicalParameters( + physical_error=phys_err_rate, cycle_time_us=cycle_time_ns / 1000.0 + ) diff --git a/qualtran/surface_code/rotation_cost_model.py b/qualtran/surface_code/rotation_cost_model.py index 890320a79..0944fe4bc 100644 --- a/qualtran/surface_code/rotation_cost_model.py +++ b/qualtran/surface_code/rotation_cost_model.py @@ -18,19 +18,18 @@ from attrs import frozen -from qualtran.surface_code.magic_count import MagicCount -from qualtran.surface_code.reference import Reference +from qualtran.resource_counting import GateCounts -class RotationCostModel(abc.ABC): +class RotationCostModel(metaclass=abc.ABCMeta): """Analytical estimate of the complexity of approximating a rotation given an error budget.""" @abc.abstractmethod - def rotation_cost(self, error_budget: float) -> MagicCount: + def rotation_cost(self, error_budget: float) -> GateCounts: """Cost of a single rotation.""" @abc.abstractmethod - def prepartion_overhead(self, error_budget) -> MagicCount: + def preparation_overhead(self, error_budget) -> GateCounts: """Cost of preparation circuit.""" @@ -46,21 +45,24 @@ class RotationLogarithmicModel(RotationCostModel): slope: The coefficient of $log_2{budget}$. overhead: The overhead. gateset: A human-readable description of the gate set (e.g. 'Clifford+T'). - approximation_protocol: A description or reference to the approximation protocol - reference: A description of the source of the model. + + References: + [https://arxiv.org/abs/2211.07629](Assessing requirements to scale to practical quantum advantage). + Beverland et. al. (2022). + + [https://arxiv.org/abs/2203.10064](Shorter quantum circuits via single-qubit gate approximation). + Kliuchnikov. et. al. (2022). Used for the approximation protocol. """ slope: float overhead: float gateset: Optional[str] = None - approximation_protocol: Optional[Reference] = None - reference: Optional[Reference] = None - def rotation_cost(self, error_budget: float) -> MagicCount: - return MagicCount(n_t=math.ceil(-self.slope * math.log2(error_budget) + self.overhead)) + def rotation_cost(self, error_budget: float) -> GateCounts: + return GateCounts(t=math.ceil(-self.slope * math.log2(error_budget) + self.overhead)) - def prepartion_overhead(self, error_budget) -> MagicCount: - return MagicCount() + def preparation_overhead(self, error_budget) -> GateCounts: + return GateCounts() @frozen @@ -74,39 +76,27 @@ class ConstantWithOverheadRotationCost(RotationCostModel): $$ Where $b$ is the bitsize/number of digits of accuracy. - reference: https://doi.org/10.1103/PRXQuantum.1.020312 - Attributes: bitsize: Number of digits of accuracy for approximating a rotation. overhead_rotation_cost: The cost model of preparing the initial rotation. - reference: A description of the source of the model. + + References: + [https://doi.org/10.1103/PRXQuantum.1.020312](Compilation of Fault-Tolerant Quantum Heuristics for Combinatorial Optimization). + Sanders et. al. (2020). """ bitsize: int overhead_rotation_cost: RotationCostModel - reference: Optional[Reference] = None - def rotation_cost(self, error_budget: float) -> MagicCount: - return MagicCount(n_ccz=max(self.bitsize - 2, 0)) + def rotation_cost(self, error_budget: float) -> GateCounts: + return GateCounts(toffoli=max(self.bitsize - 2, 0)) - def prepartion_overhead(self, error_budget) -> MagicCount: + def preparation_overhead(self, error_budget) -> GateCounts: return self.bitsize * self.overhead_rotation_cost.rotation_cost(error_budget / self.bitsize) -BeverlandEtAlRotationCost = RotationLogarithmicModel( - slope=0.53, - overhead=5.3, - gateset='Clifford+T', - approximation_protocol=Reference( - url='https://arxiv.org/abs/2203.10064', comment='Mixed fallback' - ), - reference=Reference(url='https://arxiv.org/abs/2211.07629:D2'), -) +BeverlandEtAlRotationCost = RotationLogarithmicModel(slope=0.53, overhead=5.3, gateset='Clifford+T') SevenDigitsOfPrecisionConstantCost = ConstantWithOverheadRotationCost( - bitsize=7, - overhead_rotation_cost=BeverlandEtAlRotationCost, - reference=Reference( - url='https://journals.aps.org/prxquantum/abstract/10.1103/PRXQuantum.1.020312' - ), + bitsize=7, overhead_rotation_cost=BeverlandEtAlRotationCost ) diff --git a/qualtran/surface_code/rotation_cost_model_test.py b/qualtran/surface_code/rotation_cost_model_test.py index 81158d01f..2b766a5a3 100644 --- a/qualtran/surface_code/rotation_cost_model_test.py +++ b/qualtran/surface_code/rotation_cost_model_test.py @@ -15,18 +15,18 @@ import pytest import qualtran.surface_code.rotation_cost_model as rcm -from qualtran.surface_code.magic_count import MagicCount +from qualtran.resource_counting import GateCounts @pytest.mark.parametrize( 'model,want', [ - (rcm.BeverlandEtAlRotationCost, MagicCount(n_t=7)), + (rcm.BeverlandEtAlRotationCost, GateCounts(t=7)), ( rcm.ConstantWithOverheadRotationCost( bitsize=13, overhead_rotation_cost=rcm.RotationLogarithmicModel(1, 1) ), - MagicCount(n_ccz=11), + GateCounts(toffoli=11), ), ], ) @@ -37,14 +37,14 @@ def test_rotation_cost(model: rcm.RotationCostModel, want: float): @pytest.mark.parametrize( 'model,want', [ - (rcm.BeverlandEtAlRotationCost, MagicCount()), + (rcm.BeverlandEtAlRotationCost, GateCounts()), ( rcm.ConstantWithOverheadRotationCost( bitsize=13, overhead_rotation_cost=rcm.RotationLogarithmicModel(1, 1) ), - MagicCount(n_t=104), + GateCounts(t=104), ), ], ) def test_preparation_overhead(model: rcm.RotationCostModel, want: float): - assert model.prepartion_overhead(2**-3) == want + assert model.preparation_overhead(2**-3) == want diff --git a/qualtran/surface_code/ui.py b/qualtran/surface_code/ui.py index 2372badc0..0d2db59ea 100644 --- a/qualtran/surface_code/ui.py +++ b/qualtran/surface_code/ui.py @@ -24,6 +24,7 @@ from qualtran.resource_counting import GateCounts from qualtran.surface_code import ( AlgorithmSummary, + azure_cost_model, ccz2t_cost_model, fifteen_to_one, LogicalErrorModel, @@ -31,7 +32,6 @@ ) from qualtran.surface_code import quantum_error_correction_scheme_summary as qecs from qualtran.surface_code import rotation_cost_model -from qualtran.surface_code.azure_cost_model import code_distance, minimum_time_steps from qualtran.surface_code.ccz2t_cost_model import ( get_ccz2t_costs_from_grid_search, iter_ccz2t_factories, @@ -368,7 +368,7 @@ def create_qubit_pie_chart( if estimation_model == _GIDNEY_FOWLER_MODEL: res, factory, _ = get_ccz2t_costs_from_grid_search( n_logical_gates=n_logical_gates, - n_algo_qubits=int(algorithm.algorithm_qubits), + 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()], @@ -395,7 +395,7 @@ def create_qubit_pie_chart( 'Magic State Distillation', ] memory_footprint['qubits'] = [ - FastDataBlock.get_n_tiles(int(algorithm.algorithm_qubits)), + FastDataBlock.get_n_tiles(int(algorithm.n_algo_qubits)), multi_factory.n_physical_qubits(), ] fig = px.pie( @@ -447,7 +447,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 = minimum_time_steps( + c_min = azure_cost_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,12 +464,12 @@ def create_runtime_plot( magic_counts[0] = min_num_factories time_steps[0] = c_min cds = [ - code_distance( + azure_cost_model.code_distance( error_budget=error_budget, time_steps=t, alg=algorithm, - qec=qec, - physical_error_rate=physical_error_rate, + qec_scheme=qec, + physical_error=physical_error_rate, ) for t in time_steps ] @@ -477,7 +477,7 @@ def create_runtime_plot( unit, duration = format_duration(duration) duration_name = f'Duration ({unit})' num_qubits = ( - FastDataBlock.get_n_tiles(int(algorithm.algorithm_qubits)) + FastDataBlock.get_n_tiles(int(algorithm.n_algo_qubits)) + factory.n_physical_qubits() * magic_counts ) df = pd.DataFrame( @@ -507,24 +507,35 @@ def create_runtime_plot( *_ALL_INPUTS, ) def update( - physical_error_rate, - error_budget, - estimation_model, - algorithm_data, - qec_name, - magic_name, - magic_count, - rotaion_model_name, + physical_error_rate: float, + error_budget: float, + estimation_model: str, + algorithm_data: Sequence[Any], + qec_name: str, + magic_name: str, + magic_count: int, + rotaion_model_name: str, ): """Updates the visualization.""" if any(x is None for x in [physical_error_rate, error_budget, *algorithm_data, magic_count]): raise PreventUpdate - algorithm = AlgorithmSummary(*algorithm_data) + + # TODO: We implicitly rely on the order of the input components + qubits, measurements, ts, toffolis, rotations, n_rotation_layers = algorithm_data + algorithm = AlgorithmSummary( + n_algo_qubits=qubits, + n_logical_gates=GateCounts( + measurement=measurements, t=ts, toffoli=toffolis, rotation=rotations + ), + n_rotation_layers=n_rotation_layers, + ) qec = _QEC_SCHEMES[qec_name] magic_factory = _MAGIC_FACTORIES[magic_name] rotation_model = _ROTATION_MODELS[rotaion_model_name] - needed_magic = algorithm.to_magic_count(rotation_model, error_budget / 3) - n_logical_gates = GateCounts(t=int(needed_magic.n_t), toffoli=int(needed_magic.n_ccz)) + n_logical_gates = azure_cost_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)) magic_count = int(magic_count) logical_err_model = LogicalErrorModel(qec_scheme=qec, physical_error=physical_error_rate) return ( @@ -591,7 +602,7 @@ def min_num_factories( ) -> Tuple[Dict[str, Any], int]: if estimation_model == _GIDNEY_FOWLER_MODEL: return {'display': 'none'}, 1 - c_min = minimum_time_steps( + c_min = azure_cost_model.minimum_time_steps( error_budget=error_budget, alg=algorithm, rotation_model=rotation_model ) return {'display': 'block'}, int( @@ -620,7 +631,7 @@ def compute_duration( if estimation_model == _GIDNEY_FOWLER_MODEL: res, _, _ = get_ccz2t_costs_from_grid_search( n_logical_gates=n_logical_gates, - n_algo_qubits=int(algorithm.algorithm_qubits), + 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()], From c9ff4d371d848d21771f25f23689fa561c7a2c95 Mon Sep 17 00:00:00 2001 From: Matthew Harrigan Date: Tue, 23 Jul 2024 15:37:52 -0700 Subject: [PATCH 2/2] Rotation notes --- qualtran/surface_code/azure_cost_model.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/qualtran/surface_code/azure_cost_model.py b/qualtran/surface_code/azure_cost_model.py index c84051387..382a516a3 100644 --- a/qualtran/surface_code/azure_cost_model.py +++ b/qualtran/surface_code/azure_cost_model.py @@ -62,8 +62,13 @@ def minimum_time_steps( c_min = M['meas'] + M['R'] + M['T'] + 3 * M['Tof'] eps_syn = error_budget / 3 if M['R'] > 0: - # NOTE! argument to the rotation_cost method is inverted relative to eq. D3. - rotation_cost = rotation_model.rotation_cost(eps_syn / M['R']) + # Note: The argument to the rotation_cost method is inverted relative to the notation in + # eq. D3. The log rotation model (corresponding to eq. D3) has a negative sign outside the + # log. + rot_err_budget = eps_syn / M['R'] + rotation_cost = rotation_model.rotation_cost( + rot_err_budget + ) + rotation_model.preparation_overhead(rot_err_budget) if alg.n_rotation_layers is not None: # We don't actually push all the cliffords out and count the number of