Skip to content

Commit 2e78e07

Browse files
authored
ArithmeticGate implementation (quantumlib#4702)
Migrate ArithmeticOperation to ArithmeticGate. As implemented, ArithmeticGate.on(qubits) returns a GateOperation. In other words, ArithmeticGate is completely unrelated to ArithmeticOperation. They can be considered two different ways to do the same thing. This was done in order to enable ArithmeticOperation to be deprecated. Additionally this PR implements QuirkArithmeticGate, deprecating QuirkArithmeticOperation, and rewrites ModularExp as a gate without a deprecation cycle since it's in /examples. The code for ArithmeticGate (and subclasses) is *basically* identical with ArithmeticOperation, except instead of `Sequence[Qid]]`, a quantum register is a `Sequence[int]`, where the int represents the dimension. It implements the _qid_shape_ protocol from this data, and the GateOperation constructor already has logic to ensure the appropriate number/dimension of qubits are applied. Tests are added to that effect. Closes quantumlib#4683
1 parent fa19849 commit 2e78e07

File tree

10 files changed

+449
-47
lines changed

10 files changed

+449
-47
lines changed

cirq/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,7 @@
185185
AmplitudeDampingChannel,
186186
AnyIntegerPowerGateFamily,
187187
AnyUnitaryGateFamily,
188+
ArithmeticGate,
188189
ArithmeticOperation,
189190
asymmetric_depolarize,
190191
AsymmetricDepolarizingChannel,

cirq/interop/quirk/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
# Imports from cells are only to ensure operation reprs work correctly.
2222
from cirq.interop.quirk.cells import (
23+
QuirkArithmeticGate,
2324
QuirkArithmeticOperation,
2425
QuirkInputRotationOperation,
2526
QuirkQubitPermutationGate,

cirq/interop/quirk/cells/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121

2222
from cirq.interop.quirk.cells.qubit_permutation_cells import QuirkQubitPermutationGate
2323

24-
from cirq.interop.quirk.cells.arithmetic_cells import QuirkArithmeticOperation
24+
from cirq.interop.quirk.cells.arithmetic_cells import QuirkArithmeticGate, QuirkArithmeticOperation
2525

2626
from cirq.interop.quirk.cells.input_rotation_cells import QuirkInputRotationOperation
2727

cirq/interop/quirk/cells/arithmetic_cells.py

Lines changed: 112 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,14 @@
2828
)
2929

3030
from cirq import ops, value
31+
from cirq._compat import deprecated_class
3132
from cirq.interop.quirk.cells.cell import Cell, CellMaker, CELL_SIZES
3233

3334
if TYPE_CHECKING:
3435
import cirq
3536

3637

38+
@deprecated_class(deadline='v0.15', fix='Use cirq.QuirkArithmeticGate')
3739
@value.value_equality
3840
class QuirkArithmeticOperation(ops.ArithmeticOperation):
3941
"""Applies arithmetic to a target and some inputs.
@@ -148,6 +150,110 @@ def __repr__(self) -> str:
148150
)
149151

150152

153+
@value.value_equality
154+
class QuirkArithmeticGate(ops.ArithmeticGate):
155+
"""Applies arithmetic to a target and some inputs.
156+
157+
Implements Quirk-specific implicit effects like assuming that the presence
158+
of an 'r' input implies modular arithmetic.
159+
160+
In Quirk, modular operations have no effect on values larger than the
161+
modulus. This convention is used because unitarity forces *some* convention
162+
on out-of-range values (they cannot simply disappear or raise exceptions),
163+
and the simplest is to do nothing. This call handles ensuring that happens,
164+
and ensuring the new target register value is normalized modulo the modulus.
165+
"""
166+
167+
def __init__(
168+
self, identifier: str, target: Sequence[int], inputs: Sequence[Union[Sequence[int], int]]
169+
):
170+
"""Inits QuirkArithmeticGate.
171+
172+
Args:
173+
identifier: The quirk identifier string for this operation.
174+
target: The target qubit register.
175+
inputs: Qubit registers, which correspond to the qid shape of the
176+
qubits from which the input will be read, or classical
177+
constants, that determine what happens to the target.
178+
179+
Raises:
180+
ValueError: If the target is too small for a modular operation with
181+
too small modulus.
182+
"""
183+
self.identifier = identifier
184+
self.target: Tuple[int, ...] = tuple(target)
185+
self.inputs: Tuple[Union[Sequence[int], int], ...] = tuple(
186+
e if isinstance(e, int) else tuple(e) for e in inputs
187+
)
188+
189+
if self.operation.is_modular:
190+
r = inputs[-1]
191+
if isinstance(r, int):
192+
over = r > 1 << len(target)
193+
else:
194+
over = len(cast(Sequence, r)) > len(target)
195+
if over:
196+
raise ValueError(f'Target too small for modulus.\nTarget: {target}\nModulus: {r}')
197+
198+
@property
199+
def operation(self) -> '_QuirkArithmeticCallable':
200+
return ARITHMETIC_OP_TABLE[self.identifier]
201+
202+
def _value_equality_values_(self) -> Any:
203+
return self.identifier, self.target, self.inputs
204+
205+
def registers(self) -> Sequence[Union[int, Sequence[int]]]:
206+
return [self.target, *self.inputs]
207+
208+
def with_registers(self, *new_registers: Union[int, Sequence[int]]) -> 'QuirkArithmeticGate':
209+
if len(new_registers) != len(self.inputs) + 1:
210+
raise ValueError(
211+
'Wrong number of registers.\n'
212+
f'New registers: {repr(new_registers)}\n'
213+
f'Operation: {repr(self)}'
214+
)
215+
216+
if isinstance(new_registers[0], int):
217+
raise ValueError(
218+
'The first register is the mutable target. '
219+
'It must be a list of qubits, not the constant '
220+
f'{new_registers[0]}.'
221+
)
222+
223+
return QuirkArithmeticGate(self.identifier, new_registers[0], new_registers[1:])
224+
225+
def apply(self, *registers: int) -> Union[int, Iterable[int]]:
226+
return self.operation(*registers)
227+
228+
def _circuit_diagram_info_(self, args: 'cirq.CircuitDiagramInfoArgs') -> List[str]:
229+
lettered_args = list(zip(self.operation.letters, self.inputs))
230+
231+
result: List[str] = []
232+
233+
# Target register labels.
234+
consts = ''.join(
235+
f',{letter}={reg}' for letter, reg in lettered_args if isinstance(reg, int)
236+
)
237+
result.append(f'Quirk({self.identifier}{consts})')
238+
result.extend(f'#{i}' for i in range(2, len(self.target) + 1))
239+
240+
# Input register labels.
241+
for letter, reg in lettered_args:
242+
if not isinstance(reg, int):
243+
result.extend(f'{letter.upper()}{i}' for i in range(len(cast(Sequence, reg))))
244+
245+
return result
246+
247+
def __repr__(self) -> str:
248+
return (
249+
'cirq.interop.quirk.QuirkArithmeticGate(\n'
250+
f' {repr(self.identifier)},\n'
251+
f' target={repr(self.target)},\n'
252+
f' inputs={_indented_list_lines_repr(self.inputs)},\n'
253+
')'
254+
)
255+
256+
151257
_IntsToIntCallable = Union[
152258
Callable[[int], int],
153259
Callable[[int, int], int],
@@ -244,11 +350,13 @@ def operations(self) -> 'cirq.OP_TREE':
244350
if missing_inputs:
245351
raise ValueError(f'Missing input: {sorted(missing_inputs)}')
246352

247-
return QuirkArithmeticOperation(
353+
inputs = cast(Sequence[Union[Sequence['cirq.Qid'], int]], self.inputs)
354+
qubits = self.target + tuple(q for i in self.inputs if isinstance(i, Sequence) for q in i)
355+
return QuirkArithmeticGate(
248356
self.identifier,
249-
self.target,
250-
cast(Sequence[Union[Sequence['cirq.Qid'], int]], self.inputs),
251-
)
357+
[q.dimension for q in self.target],
358+
[i if isinstance(i, int) else [q.dimension for q in i] for i in inputs],
359+
).on(*qubits)
252360

253361

254362
def _indented_list_lines_repr(items: Sequence[Any]) -> str:

cirq/interop/quirk/cells/arithmetic_cells_test.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -344,7 +344,7 @@ def test_with_registers():
344344
'["+=AB3",1,1,"inputB2"]'
345345
']}'
346346
)
347-
op = cast(cirq.ArithmeticOperation, circuit[0].operations[0])
347+
op = cast(cirq.ArithmeticGate, circuit[0].operations[0].gate)
348348

349349
with pytest.raises(ValueError, match='number of registers'):
350350
_ = op.with_registers()
@@ -353,11 +353,11 @@ def test_with_registers():
353353
_ = op.with_registers(1, 2, 3)
354354

355355
op2 = op.with_registers([], 5, 5)
356-
np.testing.assert_allclose(cirq.unitary(cirq.Circuit(op2)), np.array([[1]]), atol=1e-8)
356+
np.testing.assert_allclose(cirq.unitary(cirq.Circuit(op2())), np.array([[1]]), atol=1e-8)
357357

358-
op2 = op.with_registers([*cirq.LineQubit.range(3)], 5, 5)
358+
op2 = op.with_registers([2, 2, 2], 5, 5)
359359
np.testing.assert_allclose(
360-
cirq.final_state_vector(cirq.Circuit(op2), initial_state=0),
360+
cirq.final_state_vector(cirq.Circuit(op2(*cirq.LineQubit.range(3))), initial_state=0),
361361
cirq.one_hot(index=25 % 8, shape=8, dtype=np.complex64),
362362
atol=1e-8,
363363
)

cirq/interop/quirk/cells/composite_cell_test.py

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -92,13 +92,17 @@ def test_custom_circuit_gate():
9292
# With internal input.
9393
assert_url_to_circuit_returns(
9494
'{"cols":[["~a5ls"]],"gates":[{"id":"~a5ls","circuit":{"cols":[["inputA1","+=A1"]]}}]}',
95-
cirq.Circuit(cirq.interop.quirk.QuirkArithmeticOperation('+=A1', target=[b], inputs=[[a]])),
95+
cirq.Circuit(
96+
cirq.interop.quirk.QuirkArithmeticGate('+=A1', target=[2], inputs=[[2]]).on(b, a)
97+
),
9698
)
9799

98100
# With external input.
99101
assert_url_to_circuit_returns(
100102
'{"cols":[["inputA1","~r79k"]],"gates":[{"id":"~r79k","circuit":{"cols":[["+=A1"]]}}]}',
101-
cirq.Circuit(cirq.interop.quirk.QuirkArithmeticOperation('+=A1', target=[b], inputs=[[a]])),
103+
cirq.Circuit(
104+
cirq.interop.quirk.QuirkArithmeticGate('+=A1', target=[2], inputs=[[2]]).on(b, a)
105+
),
102106
)
103107

104108
# With external control.
@@ -127,9 +131,15 @@ def test_custom_circuit_gate():
127131
'{"cols":[["~q1fh",1,1,"inputA2"]],"gates":[{"id":"~q1fh",'
128132
'"circuit":{"cols":[["+=A2"],[1,"+=A2"],[1,"+=A2"]]}}]}',
129133
cirq.Circuit(
130-
cirq.interop.quirk.QuirkArithmeticOperation('+=A2', target=[a, b], inputs=[[d, e]]),
131-
cirq.interop.quirk.QuirkArithmeticOperation('+=A2', target=[b, c], inputs=[[d, e]]),
132-
cirq.interop.quirk.QuirkArithmeticOperation('+=A2', target=[b, c], inputs=[[d, e]]),
134+
cirq.interop.quirk.QuirkArithmeticGate('+=A2', target=[2, 2], inputs=[[2, 2]]).on(
135+
a, b, d, e
136+
),
137+
cirq.interop.quirk.QuirkArithmeticGate('+=A2', target=[2, 2], inputs=[[2, 2]]).on(
138+
b, c, d, e
139+
),
140+
cirq.interop.quirk.QuirkArithmeticGate('+=A2', target=[2, 2], inputs=[[2, 2]]).on(
141+
b, c, d, e
142+
),
133143
),
134144
)
135145

cirq/interop/quirk/cells/input_cells_test.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ def test_input_cell():
3535
)
3636

3737
# Overlaps with effect.
38-
with pytest.raises(ValueError, match='Overlapping registers'):
38+
with pytest.raises(ValueError, match='Duplicate qids'):
3939
_ = quirk_url_to_circuit(
4040
'https://algassert.com/quirk#circuit={"cols":[["+=A3","inputA3"]]}'
4141
)
@@ -53,7 +53,7 @@ def test_reversed_input_cell():
5353
)
5454

5555
# Overlaps with effect.
56-
with pytest.raises(ValueError, match='Overlapping registers'):
56+
with pytest.raises(ValueError, match='Duplicate qids'):
5757
_ = quirk_url_to_circuit(
5858
'https://algassert.com/quirk#circuit={"cols":[["+=A3","revinputA3"]]}'
5959
)

cirq/ops/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
"""Gates (unitary and non-unitary), operations, base types, and gate sets.
1515
"""
1616

17-
from cirq.ops.arithmetic_operation import ArithmeticOperation
17+
from cirq.ops.arithmetic_operation import ArithmeticGate, ArithmeticOperation
1818

1919
from cirq.ops.clifford_gate import CliffordGate, PauliTransform, SingleQubitCliffordGate
2020

0 commit comments

Comments
 (0)