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

Add target gate set for cirq benchmarking #224

Merged
merged 2 commits into from
Feb 14, 2025
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
98 changes: 96 additions & 2 deletions benchmarks/scripts/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
import pandas as pd
import matplotlib
from datetime import datetime
from cirq import CZTargetGateset, optimize_for_target_gateset
from typing import List
from cirq import optimize_for_target_gateset
import cirq
from pytket.circuit import OpType
from pytket.passes import (
DecomposeBoxes,
Expand Down Expand Up @@ -108,9 +110,101 @@ def qiskit_compile(qiskit_circuit):
)


class BenchmarkTargetGateset(cirq.TwoQubitCompilationTargetGateset):
"""Target gateset for compiling circuits for benchmarking.

This is modeled off cirq's `CZCompilationTargetGateset`_, but instead:
* Decomposes non target gateset single-qubit gates into Rz, Ry gates versus XZPowGate.
* Decomposes two-qubit gates into CNOT gates versus CZPowGate.
* Overrides the base classes postprocess_transformers to eliminate the
merge_single_qubit_moments_to_phxz pass to avoid re-introducing XZPowGates.

The gate families accepted by this gateset are:
* Single-Qubit Gates: `cirq.H`, `cirq.Rx`, `cirq.Ry`, `cirq.Rz`.
* Two-Qubit Gates: `cirq.CNOT`
* `cirq.MeasurementGate`

.. _CZCompilationTargetGateset: https://github.com/quantumlib/Cirq/blob/dd3df78c045a03b2de70b2d54d8582abbfc1f6c2/cirq-core/cirq/transformers/target_gatesets/cz_gateset.py#L27
"""

def __init__(self):
"""Initializes BenchmarkTargetGateset"""
super().__init__(
cirq.H,
cirq.CNOT,
cirq.Rx,
cirq.Ry,
cirq.Rz,
cirq.MeasurementGate,
name="BenchmarkTargetGateset",
)

def _decompose_single_qubit_operation(
self, op: cirq.Operation, moment_idx: int
) -> cirq.OP_TREE:
if not cirq.protocols.has_unitary(op):
return NotImplemented

mat = cirq.unitary(op)

pre_phase, rotation, post_phase = (
cirq.linalg.deconstruct_single_qubit_matrix_into_angles(mat)
)
return [
cirq.rz(pre_phase).on(op.qubits[0]),
cirq.ry(rotation).on(op.qubits[0]),
cirq.rz(post_phase).on(op.qubits[0]),
]

def _decompose_two_qubit_operation(
self, op: cirq.Operation, _
) -> cirq.OP_TREE:
if not cirq.has_unitary(op):
return NotImplemented
mat = cirq.unitary(op)
q0, q1 = op.qubits
naive = cirq.two_qubit_matrix_to_cz_operations(
q0, q1, mat, allow_partial_czs=False
)
temp = cirq.map_operations_and_unroll(
cirq.Circuit(naive),
lambda op, _: (
[
cirq.H(op.qubits[1]),
cirq.CNOT(*op.qubits),
cirq.H(op.qubits[1]),
]
if op.gate == cirq.CZ
else op
),
)
return cirq.merge_k_qubit_unitaries(
temp,
k=1,
rewriter=lambda op: self._decompose_single_qubit_operation(op, -1),
).all_operations()

@property
def postprocess_transformers(self) -> List["cirq.TRANSFORMER"]:
"""List of transformers which should be run after decomposing individual operations."""
processors: List["cirq.TRANSFORMER"] = [
cirq.transformers.drop_negligible_operations,
cirq.transformers.drop_empty_moments,
]
if not self._preserve_moment_structure:
processors.append(cirq.transformers.stratified_circuit)
return processors

def __repr__(self) -> str:
return "BenchmarkTargetGateset()"


# Cirq compilation
def cirq_compile(cirq_circuit):
return optimize_for_target_gateset(cirq_circuit, gateset=CZTargetGateset())
res = optimize_for_target_gateset(
cirq_circuit, gateset=BenchmarkTargetGateset()
)
return res


def get_n_qubit_gateset(
Expand Down
Empty file added benchmarks/tests/__init__.py
Empty file.
55 changes: 55 additions & 0 deletions benchmarks/tests/test_common.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import cirq
from benchmarks.scripts.common import BenchmarkTargetGateset
from benchmarks.scripts import random_clifford_circuit
from qbraid.transpiler import transpile


def test_benchmark_target_gateset_simple_circuit():
"""
Tests that a simple circuit compiles to the desired target
gateset and has equivalent functionality
"""
q = cirq.LineQubit.range(2)
c_orig = cirq.Circuit(
cirq.T(q[0]),
cirq.SWAP(*q),
cirq.T(q[0]),
cirq.SWAP(*q),
cirq.SWAP(*q),
cirq.T.on_each(*q),
)

c_new = cirq.optimize_for_target_gateset(
c_orig, gateset=BenchmarkTargetGateset()
)

cirq.testing.assert_circuits_with_terminal_measurements_are_equivalent(
c_orig, c_new, atol=1e-6
)

expected_gates = cirq.Gateset(cirq.CNOT, cirq.Rx, cirq.Ry, cirq.Rz, cirq.H)
assert expected_gates.validate(c_new), (
"Cirq compilation had unsupported gatges"
)
Comment on lines +30 to +33
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm learning about so many cirq functions through this PR! Good stuff.



def test_benchmark_target_gateset_random_clifford():
"""
Tests that a random clifford circuit compiles to the desired target
gateset and has equivalent functionality
"""
c_orig = transpile(random_clifford_circuit(6, 727), target="cirq")

c_new = cirq.optimize_for_target_gateset(
c_orig, gateset=BenchmarkTargetGateset()
)

# Check that circuits are equivalent
cirq.testing.assert_circuits_with_terminal_measurements_are_equivalent(
c_orig, c_new, atol=1e-6
)
# Check if the compiled circuit uses only the expected gates
expected_gates = cirq.Gateset(cirq.CNOT, cirq.Rx, cirq.Ry, cirq.Rz, cirq.H)
assert expected_gates.validate(c_new), (
"Cirq compilation had unsupported gatges"
)