Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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: 0 additions & 1 deletion cirq-core/cirq/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,6 @@
AnyIntegerPowerGateFamily,
AnyUnitaryGateFamily,
ArithmeticGate,
ArithmeticOperation,
asymmetric_depolarize,
AsymmetricDepolarizingChannel,
BaseDensePauliString,
Expand Down
1 change: 0 additions & 1 deletion cirq-core/cirq/interop/quirk/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
# Imports from cells are only to ensure operation reprs work correctly.
from cirq.interop.quirk.cells import (
QuirkArithmeticGate,
QuirkArithmeticOperation,
QuirkInputRotationOperation,
QuirkQubitPermutationGate,
)
Expand Down
2 changes: 1 addition & 1 deletion cirq-core/cirq/interop/quirk/cells/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@

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

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

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

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

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.

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['cirq.Qid'],
inputs: Sequence[Union[Sequence['cirq.Qid'], int]],
):
"""Inits QuirkArithmeticOperation.

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.

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

for input_register in self.inputs:
if isinstance(input_register, int):
continue
if set(self.target) & set(input_register):
raise ValueError(f'Overlapping registers: {self.target} {self.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['cirq.Qid']]]:
return [self.target, *self.inputs]

def with_registers(
self, *new_registers: Union[int, Sequence['cirq.Qid']]
) -> 'QuirkArithmeticOperation':
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 QuirkArithmeticOperation(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.QuirkArithmeticOperation(\n'
f' {repr(self.identifier)},\n'
f' target={repr(self.target)},\n'
f' inputs={_indented_list_lines_repr(self.inputs)},\n'
')'
)


@value.value_equality
class QuirkArithmeticGate(ops.ArithmeticGate):
"""Applies arithmetic to a target and some inputs.
Expand Down
2 changes: 1 addition & 1 deletion cirq-core/cirq/ops/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"""Gates (unitary and non-unitary), operations, base types, and gate sets.
"""

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

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

Expand Down
220 changes: 1 addition & 219 deletions cirq-core/cirq/ops/arithmetic_operation.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,230 +19,12 @@

import numpy as np

from cirq._compat import deprecated_class
from cirq.ops.raw_types import Operation, Gate
from cirq.ops.raw_types import Gate

if TYPE_CHECKING:
import cirq


TSelf = TypeVar('TSelf', bound='ArithmeticOperation')


@deprecated_class(deadline='v0.15', fix='Use cirq.ArithmeticGate')
class ArithmeticOperation(Operation, metaclass=abc.ABCMeta):
"""A helper class for implementing reversible classical arithmetic.

Child classes must override the `registers`, `with_registers`, and `apply`
methods.

This class handles the details of ensuring that the scaling of implementing
the operation is O(2^n) instead of O(4^n) where n is the number of qubits
being acted on, by implementing an `_apply_unitary_` function in terms of
the registers and the apply function of the child class. It also handles the
boilerplate of implementing the `qubits` and `with_qubits` methods.

Examples:

>>> class Add(cirq.ArithmeticOperation):
... def __init__(self, target_register, input_register):
... self.target_register = target_register
... self.input_register = input_register
...
... def registers(self):
... return self.target_register, self.input_register
...
... def with_registers(self, *new_registers):
... return Add(*new_registers)
...
... def apply(self, target_value, input_value):
... return target_value + input_value

>>> cirq.unitary(
... Add(target_register=cirq.LineQubit.range(2), input_register=1)
... ).astype(np.int32)
array([[0, 0, 0, 1],
[1, 0, 0, 0],
[0, 1, 0, 0],
[0, 0, 1, 0]], dtype=int32)

>>> c = cirq.Circuit(
... cirq.X(cirq.LineQubit(3)),
... cirq.X(cirq.LineQubit(2)),
... cirq.X(cirq.LineQubit(6)),
... cirq.measure(*cirq.LineQubit.range(4, 8), key='before:in'),
... cirq.measure(*cirq.LineQubit.range(4), key='before:out'),
...
... Add(target_register=cirq.LineQubit.range(4),
... input_register=cirq.LineQubit.range(4, 8)),
...
... cirq.measure(*cirq.LineQubit.range(4, 8), key='after:in'),
... cirq.measure(*cirq.LineQubit.range(4), key='after:out'),
... )

>>> cirq.sample(c).data
before:in before:out after:in after:out
0 2 3 2 5

"""

@abc.abstractmethod
def registers(self) -> Sequence[Union[int, Sequence['cirq.Qid']]]:
"""The data acted upon by the arithmetic operation.

Each register in the list can either be a classical constant (an `int`),
or else a list of qubits/qudits (a `List[cirq.Qid]`). Registers that
are set to a classical constant must not be mutated by the arithmetic
operation (their value must remain fixed when passed to `apply`).

Registers are big endian. The first qubit is the most significant, the
last qubit is the 1s qubit, the before last qubit is the 2s qubit, etc.

Returns:
A list of constants and qubit groups that the operation will act
upon.
"""
raise NotImplementedError()

@abc.abstractmethod
def with_registers(self: TSelf, *new_registers: Union[int, Sequence['cirq.Qid']]) -> TSelf:
"""Returns the same operation targeting different registers.

Args:
*new_registers: The new values that should be returned by the
`registers` method.

Returns:
An instance of the same kind of operation, but acting on different
registers.
"""
raise NotImplementedError()

@abc.abstractmethod
def apply(self, *register_values: int) -> Union[int, Iterable[int]]:
"""Returns the result of the operation operating on classical values.

For example, an addition takes two values (the target and the source),
adds the source into the target, then returns the target and source
as the new register values.

The `apply` method is permitted to be sloppy in three ways:

1. The `apply` method is permitted to return values that have more bits
than the registers they will be stored into. The extra bits are
simply dropped. For example, if the value 5 is returned for a 2
qubit register then 5 % 2**2 = 1 will be used instead. Negative
values are also permitted. For example, for a 3 qubit register the
value -2 becomes -2 % 2**3 = 6.
2. When the value of the last `k` registers is not changed by the
operation, the `apply` method is permitted to omit these values
from the result. That is to say, when the length of the output is
less than the length of the input, it is padded up to the intended
length by copying from the same position in the input.
3. When only the first register's value changes, the `apply` method is
permitted to return an `int` instead of a sequence of ints.

The `apply` method *must* be reversible. Otherwise the operation will
not be unitary, and incorrect behavior will result.

Examples:

A fully detailed adder:

```
def apply(self, target, offset):
return (target + offset) % 2**len(self.target_register), offset
```

The same adder, with less boilerplate due to the details being
handled by the `ArithmeticOperation` class:

```
def apply(self, target, offset):
return target + offset
```
"""
raise NotImplementedError()

@property
def qubits(self):
return tuple(
qubit
for register in self.registers()
if not isinstance(register, int)
for qubit in register
)

def with_qubits(self: TSelf, *new_qubits: 'cirq.Qid') -> TSelf:
new_registers: List[Union[int, Sequence['cirq.Qid']]] = []
qs = iter(new_qubits)
for register in self.registers():
if isinstance(register, int):
new_registers.append(register)
else:
new_registers.append([next(qs) for _ in register])
return self.with_registers(*new_registers)

def _apply_unitary_(self, args: 'cirq.ApplyUnitaryArgs'):
registers = self.registers()
input_ranges: List[Sequence[int]] = []
shape = []
overflow_sizes = []
for register in registers:
if isinstance(register, int):
input_ranges.append([register])
shape.append(1)
overflow_sizes.append(register + 1)
else:
size = int(np.prod([q.dimension for q in register], dtype=np.int64).item())
shape.append(size)
input_ranges.append(range(size))
overflow_sizes.append(size)

leftover = args.target_tensor.size // np.prod(shape, dtype=np.int64).item()
new_shape = (*shape, leftover)

transposed_args = args.with_axes_transposed_to_start()
src = transposed_args.target_tensor.reshape(new_shape)
dst = transposed_args.available_buffer.reshape(new_shape)
for input_seq in itertools.product(*input_ranges):
output = self.apply(*input_seq)

# Wrap into list.
inputs: List[int] = list(input_seq)
outputs: List[int] = [output] if isinstance(output, int) else list(output)

# Omitted tail values default to the corresponding input value.
if len(outputs) < len(inputs):
outputs += inputs[len(outputs) - len(inputs) :]
# Get indices into range.
for i in range(len(outputs)):
if isinstance(registers[i], int):
if outputs[i] != registers[i]:
raise ValueError(
_describe_bad_arithmetic_changed_const(
self.registers(), inputs, outputs
)
)
# Classical constants go to zero on a unit axe.
outputs[i] = 0
inputs[i] = 0
else:
# Quantum values get wrapped into range.
outputs[i] %= overflow_sizes[i]

# Copy amplitude to new location.
cast(List[Union[int, slice]], outputs).append(slice(None))
cast(List[Union[int, slice]], inputs).append(slice(None))
dst[tuple(outputs)] = src[tuple(inputs)]

# In case the reshaped arrays were copies instead of views.
dst.shape = transposed_args.available_buffer.shape
transposed_args.target_tensor[...] = dst

return args.target_tensor


TSelfGate = TypeVar('TSelfGate', bound='ArithmeticGate')


Expand Down
Loading