Skip to content

Add rotation cost model #461

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Nov 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 47 additions & 1 deletion qualtran/surface_code/algorithm_summary.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand All @@ -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,
)
84 changes: 84 additions & 0 deletions qualtran/surface_code/algorithm_summary_test.py
Original file line number Diff line number Diff line change
@@ -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,
)
104 changes: 104 additions & 0 deletions qualtran/surface_code/rotation_cost_model.py
Original file line number Diff line number Diff line change
@@ -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',
)
50 changes: 50 additions & 0 deletions qualtran/surface_code/rotation_cost_model_test.py
Original file line number Diff line number Diff line change
@@ -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