diff --git a/qualtran/surface_code/algorithm_summary.py b/qualtran/surface_code/algorithm_summary.py index fc3f76587..ec40160ac 100644 --- a/qualtran/surface_code/algorithm_summary.py +++ b/qualtran/surface_code/algorithm_summary.py @@ -22,7 +22,7 @@ class AlgorithmSummary: """Properties of a quantum algorithm that impact its physical cost - Counts of different properities that affect the physical cost of + Counts of different properties that affect the physical cost of running an algorithm (e.g. number of T gates). All counts default to zero. @@ -41,3 +41,49 @@ class AlgorithmSummary: 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, + ) diff --git a/qualtran/surface_code/algorithm_summary_test.py b/qualtran/surface_code/algorithm_summary_test.py new file mode 100644 index 000000000..619b4d601 --- /dev/null +++ b/qualtran/surface_code/algorithm_summary_test.py @@ -0,0 +1,84 @@ +# 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 pytest + +from qualtran.surface_code.algorithm_summary import AlgorithmSummary + + +def test_mul(): + assert AlgorithmSummary(t_gates=9) == 3 * AlgorithmSummary(t_gates=3) + + with pytest.raises(TypeError): + _ = complex(1, 0) * AlgorithmSummary(rotation_gates=1) + + +def test_addition(): + with pytest.raises(TypeError): + _ = AlgorithmSummary() + 5 + + 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 + + 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, + ) diff --git a/qualtran/surface_code/rotation_cost_model.py b/qualtran/surface_code/rotation_cost_model.py new file mode 100644 index 000000000..04e594efb --- /dev/null +++ b/qualtran/surface_code/rotation_cost_model.py @@ -0,0 +1,104 @@ +# 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 attrs import frozen + +from qualtran.surface_code.algorithm_summary import AlgorithmSummary + + +class RotationCostModel(abc.ABC): + """Analytical estimate of the complexity of approximating a rotation given an error budget.""" + + @abc.abstractmethod + def rotation_cost(self, error_budget: float) -> AlgorithmSummary: + """Cost of a single rotation.""" + + @abc.abstractmethod + def prepartion_overhead(self, error_budget) -> AlgorithmSummary: + """Cost of preparation circuit.""" + + +@frozen +class RotationLogarithmicModel(RotationCostModel): + r"""A linear model in the log of the error budget with no preparation cost. + + $$ + \#T = -\textrm{slope} \log_2{\textrm{budget}} + \textrm{overhead} + $$ + + Attributes: + 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 + (e.g. `Diagonal: https://arxiv.org/abs/1403.2975`) + reference: A human-readable description of the source of the model + (e.g. 'https://arxiv.org/abs/1404.5320'). + """ + slope: float + overhead: float + gateset: str | None = None + approximation_protocol: str | None = None + reference: str | None = None + + def rotation_cost(self, error_budget: float) -> AlgorithmSummary: + return AlgorithmSummary( + t_gates=math.ceil(-self.slope * math.log2(error_budget) + self.overhead) + ) + + def prepartion_overhead(self, error_budget) -> AlgorithmSummary: + return AlgorithmSummary() + + +@frozen +class ConstantWithOverheadRotationCost(RotationCostModel): + r"""A rotation cost of bitsize - 2 toffoli per rotation independent of the error budget. + + This model assumes a state $\ket{\phi}$ has been prepared using a standard technique, then + each rotation is applied with bitsize digits of accuracy using bitsize - 2 Toffoli gates. + $$ + \ket{\phi} = \frac{1}{\sqrt{2^{b}}} \sum_{k=0}^{2^b-1} e^{-2\pi i k/2^b} \ket{k} + $$ + 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 human-readable description of the source of the model + (e.g. 'https://arxiv.org/abs/1404.5320'). + """ + + bitsize: int + overhead_rotation_cost: RotationCostModel + reference: str | None = None + + def rotation_cost(self, error_budget: float) -> AlgorithmSummary: + return AlgorithmSummary(toffoli_gates=max(self.bitsize - 2, 0)) + + def prepartion_overhead(self, error_budget) -> AlgorithmSummary: + 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='Mixed fallback:https://arxiv.org/abs/2203.10064', + reference='https://arxiv.org/abs/2211.07629:D2', +) diff --git a/qualtran/surface_code/rotation_cost_model_test.py b/qualtran/surface_code/rotation_cost_model_test.py new file mode 100644 index 000000000..94a193c84 --- /dev/null +++ b/qualtran/surface_code/rotation_cost_model_test.py @@ -0,0 +1,50 @@ +# 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 pytest + +import qualtran.surface_code.rotation_cost_model as rcm +from qualtran.surface_code.algorithm_summary import AlgorithmSummary + + +@pytest.mark.parametrize( + 'model,want', + [ + (rcm.BeverlandEtAlRotationCost, AlgorithmSummary(t_gates=7)), + ( + rcm.ConstantWithOverheadRotationCost( + bitsize=13, overhead_rotation_cost=rcm.RotationLogarithmicModel(1, 1) + ), + AlgorithmSummary(toffoli_gates=11), + ), + ], +) +def test_rotation_cost(model: rcm.RotationCostModel, want: float): + assert model.rotation_cost(2**-3) == want + + +@pytest.mark.parametrize( + 'model,want', + [ + (rcm.BeverlandEtAlRotationCost, AlgorithmSummary()), + ( + rcm.ConstantWithOverheadRotationCost( + bitsize=13, overhead_rotation_cost=rcm.RotationLogarithmicModel(1, 1) + ), + AlgorithmSummary(t_gates=104), + ), + ], +) +def test_preparation_overhead(model: rcm.RotationCostModel, want: float): + assert model.prepartion_overhead(2**-3) == want