Skip to content

Commit

Permalink
Add cirq.merge_k_qubit_unitaries transformer to replace `cirq.Merge…
Browse files Browse the repository at this point in the history
…SingleQubitGates` optimizer (quantumlib#4986)

* Replace cirq.MergeSingleQubitGates optimizer with cirq.merge_single_qubit_gates transformer

* Add merge_k_qubit_unitaries primitive
  • Loading branch information
tanujkhattar authored and 95-martin-orion committed Mar 2, 2022
1 parent 6ac9d2d commit eacf4a3
Show file tree
Hide file tree
Showing 20 changed files with 602 additions and 74 deletions.
4 changes: 4 additions & 0 deletions cirq-core/cirq/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -373,10 +373,14 @@
map_moments,
map_operations,
map_operations_and_unroll,
merge_k_qubit_unitaries,
merge_k_qubit_unitaries_to_circuit_op,
merge_moments,
merge_operations,
merge_operations_to_circuit_op,
merge_single_qubit_gates_to_phased_x_and_z,
merge_single_qubit_gates_to_phxz,
merge_single_qubit_moments_to_phxz,
prepare_two_qubit_state_using_cz,
prepare_two_qubit_state_using_sqrt_iswap,
single_qubit_matrix_to_gates,
Expand Down
2 changes: 1 addition & 1 deletion cirq-core/cirq/contrib/paulistring/convert_gate_set.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ def converted_gate_set(
"""
conv_circuit = circuits.Circuit(circuit)
optimizers.ConvertToCzAndSingleGates().optimize_circuit(conv_circuit)
optimizers.MergeSingleQubitGates().optimize_circuit(conv_circuit)
conv_circuit = transformers.merge_k_qubit_unitaries(conv_circuit, k=1)
ConvertToPauliStringPhasors(
ignore_failures=True,
keep_clifford=not no_clifford_gates,
Expand Down
7 changes: 3 additions & 4 deletions cirq-core/cirq/devices/noise_model_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,6 @@ def test_noise_composition():
a, b, c = cirq.LineQubit.range(3)
noise_z = cirq.ConstantQubitNoiseModel(cirq.Z)
noise_inv_s = cirq.ConstantQubitNoiseModel(cirq.S ** -1)
merge = cirq.optimizers.merge_single_qubit_gates_into_phased_x_z
base_moments = [cirq.Moment([cirq.X(a)]), cirq.Moment([cirq.Y(b)]), cirq.Moment([cirq.H(c)])]
circuit_z = cirq.Circuit(noise_z.noisy_moments(base_moments, [a, b, c]))
circuit_s = cirq.Circuit(noise_inv_s.noisy_moments(base_moments, [a, b, c]))
Expand All @@ -147,9 +146,9 @@ def test_noise_composition():
)

# All of the gates will be the same, just out of order. Merging fixes this.
merge(actual_zs)
merge(actual_sz)
merge(expected_circuit)
actual_zs = cirq.merge_single_qubit_gates_to_phased_x_and_z(actual_zs)
actual_sz = cirq.merge_single_qubit_gates_to_phased_x_and_z(actual_sz)
expected_circuit = cirq.merge_single_qubit_gates_to_phased_x_and_z(expected_circuit)
assert_equivalent_op_tree(actual_zs, actual_sz)
assert_equivalent_op_tree(actual_zs, expected_circuit)

Expand Down
6 changes: 2 additions & 4 deletions cirq-core/cirq/ion/convert_to_ion_gates.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

import numpy as np

from cirq import ops, protocols, optimizers, circuits, transformers
from cirq import ops, protocols, circuits, transformers
from cirq.ion import ms, two_qubit_matrix_to_ion_operations, ion_device


Expand Down Expand Up @@ -86,6 +86,4 @@ def convert_circuit(self, circuit: circuits.Circuit) -> circuits.Circuit:
for moment in circuit:
for op in moment.operations:
new_circuit.append(self.convert_one(op))
optimizers.merge_single_qubit_gates_into_phased_x_z(new_circuit)

return new_circuit
return transformers.merge_single_qubit_gates_to_phased_x_and_z(new_circuit)
4 changes: 2 additions & 2 deletions cirq-core/cirq/ion/ion_decomposition.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@

import numpy as np

from cirq import ops, linalg, protocols, optimizers, circuits, transformers
from cirq import ops, linalg, protocols, circuits, transformers
from cirq.ion import ms

if TYPE_CHECKING:
Expand Down Expand Up @@ -52,7 +52,7 @@ def two_qubit_matrix_to_ion_operations(

def _cleanup_operations(operations: List[ops.Operation]):
circuit = circuits.Circuit(operations)
optimizers.merge_single_qubit_gates.merge_single_qubit_gates_into_phased_x_z(circuit)
circuit = transformers.merge_single_qubit_gates_to_phased_x_and_z(circuit)
circuit = transformers.eject_phased_paulis(circuit)
circuit = transformers.eject_z(circuit)
circuit = circuits.Circuit(circuit.all_operations(), strategy=circuits.InsertStrategy.EARLIEST)
Expand Down
10 changes: 2 additions & 8 deletions cirq-core/cirq/optimizers/merge_interactions_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.

from typing import Callable, List
from typing import List

import pytest
import sympy
Expand All @@ -26,14 +26,8 @@ def assert_optimizes(before: cirq.Circuit, expected: cirq.Circuit):
opt.optimize_circuit(actual)

# Ignore differences that would be caught by follow-up optimizations.
followup_optimizations: List[Callable[[cirq.Circuit], None]] = [
cirq.merge_single_qubit_gates_into_phased_x_z,
]
for post in followup_optimizations:
post(actual)
post(expected)

followup_transformers: List[cirq.TRANSFORMER] = [
cirq.merge_single_qubit_gates_to_phased_x_and_z,
cirq.eject_phased_paulis,
cirq.eject_z,
cirq.drop_negligible_operations,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.

from typing import Callable, List
from typing import List

import pytest

Expand All @@ -37,14 +37,8 @@ def assert_optimizes(before: cirq.Circuit, expected: cirq.Circuit, **kwargs):
opt.optimize_circuit(actual)

# Ignore differences that would be caught by follow-up optimizations.
followup_optimizations: List[Callable[[cirq.Circuit], None]] = [
cirq.merge_single_qubit_gates_into_phased_x_z,
]
for post in followup_optimizations:
post(actual)
post(expected)

followup_transformers: List[cirq.TRANSFORMER] = [
cirq.merge_single_qubit_gates_to_phased_x_and_z,
cirq.eject_phased_paulis,
cirq.eject_z,
cirq.drop_negligible_operations,
Expand Down
24 changes: 10 additions & 14 deletions cirq-core/cirq/optimizers/merge_single_qubit_gates.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@

import numpy as np

from cirq import ops, linalg, protocols, circuits
from cirq.transformers.analytical_decompositions import single_qubit_decompositions
from cirq import ops, linalg, protocols, circuits, _compat, transformers

if TYPE_CHECKING:
import cirq


@_compat.deprecated_class(deadline='v1.0', fix='Use cirq.merge_k_qubit_unitaries instead.')
class MergeSingleQubitGates(circuits.PointOptimizer):
"""Optimizes runs of adjacent unitary 1-qubit operations."""

Expand Down Expand Up @@ -101,6 +101,9 @@ def optimization_at(
)


@_compat.deprecated(
deadline='v1.0', fix='Use cirq.merge_single_qubit_gates_to_phased_x_and_z instead.'
)
def merge_single_qubit_gates_into_phased_x_z(circuit: circuits.Circuit, atol: float = 1e-8) -> None:
"""Canonicalizes runs of single-qubit rotations in a circuit.
Expand All @@ -113,14 +116,12 @@ def merge_single_qubit_gates_into_phased_x_z(circuit: circuits.Circuit, atol: fl
atol: Absolute tolerance to angle error. Larger values allow more
negligible gates to be dropped, smaller values increase accuracy.
"""

def synth(qubit: 'cirq.Qid', matrix: np.ndarray) -> List[ops.Operation]:
out_gates = single_qubit_decompositions.single_qubit_matrix_to_phased_x_z(matrix, atol)
return [gate(qubit) for gate in out_gates]

MergeSingleQubitGates(synthesizer=synth).optimize_circuit(circuit)
circuit._moments = [
*transformers.merge_single_qubit_gates_to_phased_x_and_z(circuit, atol=atol)
]


@_compat.deprecated(deadline='v1.0', fix='Use cirq.merge_single_qubit_gates_to_phxz instead.')
def merge_single_qubit_gates_into_phxz(
circuit: circuits.Circuit,
atol: float = 1e-8,
Expand All @@ -135,9 +136,4 @@ def merge_single_qubit_gates_into_phxz(
atol: Absolute tolerance to angle error. Larger values allow more
negligible gates to be dropped, smaller values increase accuracy.
"""

def synth(qubit: 'cirq.Qid', matrix: np.ndarray) -> List[ops.Operation]:
gate = single_qubit_decompositions.single_qubit_matrix_to_phxz(matrix, atol)
return [gate(qubit)] if gate else []

MergeSingleQubitGates(synthesizer=synth).optimize_circuit(circuit)
circuit._moments = [*transformers.merge_single_qubit_gates_to_phxz(circuit, atol=atol)]
39 changes: 26 additions & 13 deletions cirq-core/cirq/optimizers/merge_single_qubit_gates_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,12 @@ def assert_optimizes(
before: cirq.Circuit,
expected: cirq.Circuit,
optimizer: Optional[Callable[[cirq.Circuit], None]] = None,
deprecated_msg: str = "Use cirq.merge_k_qubit_unitaries",
):
if optimizer is None:
optimizer = cirq.MergeSingleQubitGates().optimize_circuit
optimizer(before)
with cirq.testing.assert_deprecated(deprecated_msg, deadline='v1.0'):
if optimizer is None:
optimizer = cirq.MergeSingleQubitGates().optimize_circuit
optimizer(before)

# Ignore differences that would be caught by follow-up optimizations.
followup_transformers = [cirq.drop_negligible_operations, cirq.drop_empty_moments]
Expand All @@ -38,7 +40,8 @@ def assert_optimizes(


def test_leaves_singleton():
m = cirq.MergeSingleQubitGates()
with cirq.testing.assert_deprecated("Use cirq.merge_k_qubit_unitaries", deadline='v1.0'):
m = cirq.MergeSingleQubitGates()
q = cirq.NamedQubit('q')
c = cirq.Circuit([cirq.Moment([cirq.X(q)])])

Expand All @@ -48,12 +51,16 @@ def test_leaves_singleton():


def test_not_both():
with pytest.raises(ValueError):
_ = cirq.MergeSingleQubitGates(synthesizer=lambda *args: None, rewriter=lambda *args: None)
with cirq.testing.assert_deprecated("Use cirq.merge_k_qubit_unitaries", deadline='v1.0'):
with pytest.raises(ValueError):
_ = cirq.MergeSingleQubitGates(
synthesizer=lambda *args: None, rewriter=lambda *args: None
)


def test_combines_sequence():
m = cirq.MergeSingleQubitGates()
with cirq.testing.assert_deprecated("Use cirq.merge_k_qubit_unitaries", deadline='v1.0'):
m = cirq.MergeSingleQubitGates()
q = cirq.NamedQubit('q')
c = cirq.Circuit(cirq.X(q) ** 0.5, cirq.Z(q) ** 0.5, cirq.X(q) ** -0.5)

Expand Down Expand Up @@ -83,7 +90,8 @@ def test_removes_identity_sequence():


def test_stopped_at_2qubit():
m = cirq.MergeSingleQubitGates()
with cirq.testing.assert_deprecated("Use cirq.merge_k_qubit_unitaries", deadline='v1.0'):
m = cirq.MergeSingleQubitGates()
q = cirq.NamedQubit('q')
q2 = cirq.NamedQubit('q2')
c = cirq.Circuit(
Expand All @@ -109,7 +117,8 @@ def test_stopped_at_2qubit():


def test_ignores_2qubit_target():
m = cirq.MergeSingleQubitGates()
with cirq.testing.assert_deprecated("Use cirq.merge_k_qubit_unitaries", deadline='v1.0'):
m = cirq.MergeSingleQubitGates()
q = cirq.NamedQubit('q')
q2 = cirq.NamedQubit('q2')
c = cirq.Circuit(
Expand All @@ -132,7 +141,8 @@ class UnsupportedDummy(cirq.SingleQubitGate):
UnsupportedDummy()(q0),
)
c_orig = cirq.Circuit(circuit)
cirq.MergeSingleQubitGates().optimize_circuit(circuit)
with cirq.testing.assert_deprecated("Use cirq.merge_k_qubit_unitaries", deadline='v1.0'):
cirq.MergeSingleQubitGates().optimize_circuit(circuit)

assert circuit == c_orig

Expand All @@ -147,9 +157,10 @@ def test_rewrite():
cirq.CZ(q0, q1),
cirq.Y(q1),
)
cirq.MergeSingleQubitGates(rewriter=lambda ops: cirq.H(ops[0].qubits[0])).optimize_circuit(
circuit
)
with cirq.testing.assert_deprecated("Use cirq.merge_k_qubit_unitaries", deadline='v1.0'):
cirq.MergeSingleQubitGates(rewriter=lambda ops: cirq.H(ops[0].qubits[0])).optimize_circuit(
circuit
)
circuit = cirq.drop_empty_moments(circuit)

cirq.testing.assert_same_circuits(
Expand Down Expand Up @@ -180,6 +191,7 @@ def test_merge_single_qubit_gates_into_phased_x_z():
(cirq.PhasedXPowGate(phase_exponent=-0.5)(a)) ** 0.5,
),
optimizer=cirq.merge_single_qubit_gates_into_phased_x_z,
deprecated_msg="Use cirq.merge_single_qubit_gates_to_phased_x_and_z",
)


Expand Down Expand Up @@ -207,4 +219,5 @@ def phxz(a, x, z):
phxz(-0.5, 0.5, 0).on(a),
),
optimizer=cirq.merge_single_qubit_gates_into_phxz,
deprecated_msg="Use cirq.merge_single_qubit_gates_to_phxz",
)
8 changes: 8 additions & 0 deletions cirq-core/cirq/transformers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,14 @@
dephase_measurements,
)

from cirq.transformers.merge_k_qubit_gates import merge_k_qubit_unitaries

from cirq.transformers.merge_single_qubit_gates import (
merge_single_qubit_gates_to_phased_x_and_z,
merge_single_qubit_gates_to_phxz,
merge_single_qubit_moments_to_phxz,
)

from cirq.transformers.synchronize_terminal_measurements import synchronize_terminal_measurements

from cirq.transformers.transformer_api import (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@

from cirq import ops, linalg, protocols, circuits
from cirq.transformers.analytical_decompositions import single_qubit_decompositions
from cirq.transformers.merge_single_qubit_gates import merge_single_qubit_gates_to_phased_x_and_z
from cirq.transformers.eject_z import eject_z
from cirq.transformers.eject_phased_paulis import eject_phased_paulis
from cirq.optimizers import merge_single_qubit_gates

if TYPE_CHECKING:
import cirq
Expand Down Expand Up @@ -161,7 +161,7 @@ def _xx_yy_zz_interaction_via_full_czs(

def _cleanup_operations(operations: Sequence[ops.Operation]):
circuit = circuits.Circuit(operations)
merge_single_qubit_gates.merge_single_qubit_gates_into_phased_x_z(circuit)
circuit = merge_single_qubit_gates_to_phased_x_and_z(circuit)
circuit = eject_phased_paulis(circuit)
circuit = eject_z(circuit)
circuit = circuits.Circuit(circuit.all_operations(), strategy=circuits.InsertStrategy.EARLIEST)
Expand Down
Loading

0 comments on commit eacf4a3

Please sign in to comment.