Skip to content
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

ArithmeticGate implementation #4702

Merged
merged 25 commits into from
May 5, 2022
Merged
Show file tree
Hide file tree
Changes from 14 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
1 change: 1 addition & 0 deletions cirq-core/cirq/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@
AmplitudeDampingChannel,
AnyIntegerPowerGateFamily,
AnyUnitaryGateFamily,
ArithmeticGate,
ArithmeticOperation,
asymmetric_depolarize,
AsymmetricDepolarizingChannel,
Expand Down
1 change: 1 addition & 0 deletions cirq-core/cirq/interop/quirk/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

# Imports from cells are only to ensure operation reprs work correctly.
from cirq.interop.quirk.cells import (
QuirkArithmeticGate,
QuirkArithmeticOperation,
QuirkInputRotationOperation,
QuirkQubitPermutationGate,
Expand Down
1 change: 1 addition & 0 deletions cirq-core/cirq/interop/quirk/cells/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
)

from cirq.interop.quirk.cells.arithmetic_cells import (
QuirkArithmeticGate,
QuirkArithmeticOperation,
)

Expand Down
118 changes: 114 additions & 4 deletions cirq-core/cirq/interop/quirk/cells/arithmetic_cells.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,14 @@
)

from cirq import ops, value
from cirq._compat import deprecated_class
from cirq.interop.quirk.cells.cell import Cell, CellMaker, CELL_SIZES

if TYPE_CHECKING:
import cirq


@deprecated_class(deadline='v0.15', fix='Use cirq.QuirkArithmeticGate')
@value.value_equality
class QuirkArithmeticOperation(ops.ArithmeticOperation):
"""Applies arithmetic to a target and some inputs.
Expand Down Expand Up @@ -148,6 +150,112 @@ def __repr__(self) -> str:
)


@value.value_equality
class QuirkArithmeticGate(ops.ArithmeticGate):
"""Applies arithmetic to a target and some inputs.

Implements Quirk-specific implicit effects like assuming that the presence
of an 'r' input implies modular arithmetic.

In Quirk, modular operations have no effect on values larger than the
modulus. This convention is used because unitarity forces *some* convention
on out-of-range values (they cannot simply disappear or raise exceptions),
and the simplest is to do nothing. This call handles ensuring that happens,
and ensuring the new target register value is normalized modulo the modulus.
"""

def __init__(
self,
identifier: str,
target: Sequence[int],
inputs: Sequence[Union[Sequence[int], int]],
):
"""Inits QuirkArithmeticGate.

Args:
identifier: The quirk identifier string for this operation.
target: The target qubit register.
inputs: Qubit registers (or classical constants) that
determine what happens to the target.
daxfohl marked this conversation as resolved.
Show resolved Hide resolved

Raises:
ValueError: If the target is too small for a modular operation with
too small modulus.
"""
self.identifier = identifier
self.target: Tuple[int, ...] = tuple(target)
self.inputs: Tuple[Union[Sequence[int], int], ...] = tuple(
e if isinstance(e, int) else tuple(e) for e in inputs
)

if self.operation.is_modular:
r = inputs[-1]
if isinstance(r, int):
over = r > 1 << len(target)
else:
over = len(cast(Sequence, r)) > len(target)
if over:
raise ValueError(f'Target too small for modulus.\nTarget: {target}\nModulus: {r}')

@property
def operation(self) -> '_QuirkArithmeticCallable':
return ARITHMETIC_OP_TABLE[self.identifier]

def _value_equality_values_(self) -> Any:
return self.identifier, self.target, self.inputs

def registers(self) -> Sequence[Union[int, Sequence[int]]]:
return [self.target, *self.inputs]

def with_registers(self, *new_registers: Union[int, Sequence[int]]) -> 'QuirkArithmeticGate':
if len(new_registers) != len(self.inputs) + 1:
raise ValueError(
'Wrong number of registers.\n'
f'New registers: {repr(new_registers)}\n'
f'Operation: {repr(self)}'
)

if isinstance(new_registers[0], int):
raise ValueError(
'The first register is the mutable target. '
'It must be a list of qubits, not the constant '
f'{new_registers[0]}.'
)

return QuirkArithmeticGate(self.identifier, new_registers[0], new_registers[1:])

def apply(self, *registers: int) -> Union[int, Iterable[int]]:
return self.operation(*registers)

def _circuit_diagram_info_(self, args: 'cirq.CircuitDiagramInfoArgs') -> List[str]:
lettered_args = list(zip(self.operation.letters, self.inputs))

result: List[str] = []

# Target register labels.
consts = ''.join(
f',{letter}={reg}' for letter, reg in lettered_args if isinstance(reg, int)
)
result.append(f'Quirk({self.identifier}{consts})')
result.extend(f'#{i}' for i in range(2, len(self.target) + 1))

# Input register labels.
for letter, reg in lettered_args:
if not isinstance(reg, int):
result.extend(f'{letter.upper()}{i}' for i in range(len(cast(Sequence, reg))))

return result

def __repr__(self) -> str:
return (
'cirq.interop.quirk.QuirkArithmeticGate(\n'
f' {repr(self.identifier)},\n'
f' target={repr(self.target)},\n'
f' inputs={_indented_list_lines_repr(self.inputs)},\n'
')'
)


_IntsToIntCallable = Union[
Callable[[int], int],
Callable[[int, int], int],
Expand Down Expand Up @@ -244,11 +352,13 @@ def operations(self) -> 'cirq.OP_TREE':
if missing_inputs:
raise ValueError(f'Missing input: {sorted(missing_inputs)}')

return QuirkArithmeticOperation(
inputs = cast(Sequence[Union[Sequence['cirq.Qid'], int]], self.inputs)
qubits = self.target + tuple(q for i in self.inputs if isinstance(i, Sequence) for q in i)
return QuirkArithmeticGate(
self.identifier,
self.target,
cast(Sequence[Union[Sequence['cirq.Qid'], int]], self.inputs),
)
[q.dimension for q in self.target],
[i if isinstance(i, int) else [q.dimension for q in i] for i in inputs],
).on(*qubits)


def _indented_list_lines_repr(items: Sequence[Any]) -> str:
Expand Down
8 changes: 4 additions & 4 deletions cirq-core/cirq/interop/quirk/cells/arithmetic_cells_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -545,7 +545,7 @@ def test_with_registers():
'["+=AB3",1,1,"inputB2"]'
']}'
)
op = cast(cirq.ArithmeticOperation, circuit[0].operations[0])
op = cast(cirq.ArithmeticGate, circuit[0].operations[0].gate)

with pytest.raises(ValueError, match='number of registers'):
_ = op.with_registers()
Expand All @@ -554,11 +554,11 @@ def test_with_registers():
_ = op.with_registers(1, 2, 3)

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

op2 = op.with_registers([*cirq.LineQubit.range(3)], 5, 5)
op2 = op.with_registers([2, 2, 2], 5, 5)
np.testing.assert_allclose(
cirq.final_state_vector(cirq.Circuit(op2), initial_state=0),
cirq.final_state_vector(cirq.Circuit(op2(*cirq.LineQubit.range(3))), initial_state=0),
cirq.one_hot(index=25 % 8, shape=8, dtype=np.complex64),
atol=1e-8,
)
Expand Down
20 changes: 15 additions & 5 deletions cirq-core/cirq/interop/quirk/cells/composite_cell_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,13 +100,17 @@ def test_custom_circuit_gate():
# With internal input.
assert_url_to_circuit_returns(
'{"cols":[["~a5ls"]],"gates":[{"id":"~a5ls","circuit":{"cols":[["inputA1","+=A1"]]}}]}',
cirq.Circuit(cirq.interop.quirk.QuirkArithmeticOperation('+=A1', target=[b], inputs=[[a]])),
cirq.Circuit(
cirq.interop.quirk.QuirkArithmeticGate('+=A1', target=[2], inputs=[[2]]).on(b, a)
),
)

# With external input.
assert_url_to_circuit_returns(
'{"cols":[["inputA1","~r79k"]],"gates":[{"id":"~r79k","circuit":{"cols":[["+=A1"]]}}]}',
cirq.Circuit(cirq.interop.quirk.QuirkArithmeticOperation('+=A1', target=[b], inputs=[[a]])),
cirq.Circuit(
cirq.interop.quirk.QuirkArithmeticGate('+=A1', target=[2], inputs=[[2]]).on(b, a)
),
)

# With external control.
Expand Down Expand Up @@ -135,9 +139,15 @@ def test_custom_circuit_gate():
'{"cols":[["~q1fh",1,1,"inputA2"]],"gates":[{"id":"~q1fh",'
'"circuit":{"cols":[["+=A2"],[1,"+=A2"],[1,"+=A2"]]}}]}',
cirq.Circuit(
cirq.interop.quirk.QuirkArithmeticOperation('+=A2', target=[a, b], inputs=[[d, e]]),
cirq.interop.quirk.QuirkArithmeticOperation('+=A2', target=[b, c], inputs=[[d, e]]),
cirq.interop.quirk.QuirkArithmeticOperation('+=A2', target=[b, c], inputs=[[d, e]]),
cirq.interop.quirk.QuirkArithmeticGate('+=A2', target=[2, 2], inputs=[[2, 2]]).on(
a, b, d, e
),
cirq.interop.quirk.QuirkArithmeticGate('+=A2', target=[2, 2], inputs=[[2, 2]]).on(
b, c, d, e
),
cirq.interop.quirk.QuirkArithmeticGate('+=A2', target=[2, 2], inputs=[[2, 2]]).on(
b, c, d, e
),
),
)

Expand Down
4 changes: 2 additions & 2 deletions cirq-core/cirq/interop/quirk/cells/input_cells_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ def test_input_cell():
)

# Overlaps with effect.
with pytest.raises(ValueError, match='Overlapping registers'):
with pytest.raises(ValueError, match='Duplicate qids'):
_ = quirk_url_to_circuit(
'https://algassert.com/quirk#circuit={"cols":[["+=A3","inputA3"]]}'
)
Expand All @@ -71,7 +71,7 @@ def test_reversed_input_cell():
)

# Overlaps with effect.
with pytest.raises(ValueError, match='Overlapping registers'):
with pytest.raises(ValueError, match='Duplicate qids'):
_ = quirk_url_to_circuit(
'https://algassert.com/quirk#circuit={"cols":[["+=A3","revinputA3"]]}'
)
Expand Down
1 change: 1 addition & 0 deletions cirq-core/cirq/ops/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"""

from cirq.ops.arithmetic_operation import (
ArithmeticGate,
ArithmeticOperation,
)

Expand Down
Loading