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

Cirq decompose to supported gate set #93

Open
ryanhill1 opened this issue May 17, 2024 · 12 comments
Open

Cirq decompose to supported gate set #93

ryanhill1 opened this issue May 17, 2024 · 12 comments
Assignees
Labels
cirq Related to Cirq conversions good first issue Good for newcomers

Comments

@ryanhill1
Copy link
Member

Before passing an input cirq circuit to the QIR converter, we first decompose the circuit to ensure that it only uses gates / operations that are supported by QIR, see PYQIR_OP_MAP. Right now, to accomplish this, we use a preprocess_circuit function which loops through each operation, and uses a try / except to determine if the operation is supported, and if not, applies a naive cirq.decompose_once to the circuit.

try:
# Try converting to PyQIR. If successful, keep the operation.
_ = map_cirq_op_to_pyqir_callable(operation)
return [operation]
except CirqConversionError:
pass
new_ops = cirq.decompose_once(operation, flatten=True, default=[operation])
if len(new_ops) == 1 and new_ops[0] == operation:
raise CirqConversionError("Couldn't convert circuit to QIR gate set.")
return list(itertools.chain.from_iterable(map(_decompose_gate_op, new_ops)))

For the majority of use-cases, this works. However, it is:

  1. Bad style
  2. Inefficient

Instead, we would like to use one of the built-in Cirq transforms such as cirq.optimize_for_target_gateset to automatically ensure that the input circuit conforms to the supported QIR operations. In doing so, hopefully we can eliminate the try except blocks in passes.py, and re-implement preprocess_circuit without the clunky helpers.

@ryanhill1 ryanhill1 added good first issue Good for newcomers cirq Related to Cirq conversions unitary-hack Issues tagged with Unitary Hack '24 labels May 17, 2024
@ryanhill1 ryanhill1 removed the unitary-hack Issues tagged with Unitary Hack '24 label Jun 12, 2024
@arulandu
Copy link

arulandu commented Nov 8, 2024

@ryanhill1 Is this still open? I'd love to work on this!

@ryanhill1
Copy link
Member Author

@arulandu It is, go for it!

@arulandu
Copy link

arulandu commented Nov 11, 2024

I'm thinking of creating a Gateset by extending https://quantumai.google/reference/python/cirq/TwoQubitCompilationTargetGateset. However, I'm unsure how to implement self._decompose_two_qubit_operation. Could we just use https://quantumai.google/reference/python/cirq/CZTargetGateset?

@ryanhill1
Copy link
Member Author

I'll admit, I'm not super familiar with how cirq.CompilationTargetGateset work. It seems like you just start by passing in a list of cirq.Gate, so to start, this would be the set gates supported by map_cirq_op_to_pyqir_callable(), I think? And then, perhaps there's code from qbraid_qir/qasm3/maps.py and/or qbraid_qir/qasm3/linalg.py that we can move to the top-level and generalize to expand that list or assist with some of the decompositions? What gates are included in the cirq.CZTargetGateset? If it maps to the pyqir gateset that we are targeting closely, I guess that could be a reasonable starting point.

@TheGupta2012 @skushnir123 perhaps you guys could take a look here and weigh in?

Or if @vtomole has any insights, as someone who is much closer to the Cirq stack, that could be super helpful as well.

@arulandu
Copy link

arulandu commented Nov 11, 2024

cirq.CZTargetGateset is cirq.CZPowGate, PhasedXZGate, MeasurementGate, GlobalPhaseGate. Yes, it should indeed take the list of gates, but you need to implement the abstract method for decomposing gates. TwoQubitCompilationTargetGateset does some of the work and only requires you to implement a 2q unitary decomposition. Should we be implementing a Clifford-T decomposition like here and then replace iSWAP with SWAP + Xs?

@vtomole
Copy link

vtomole commented Nov 11, 2024

What gateset do ya'll want to decompose to? The list in PYQIR_OP_MAP is kinda big. You can just decompose to Clifford + T if the the input gate is not in that list.

I'm unsure how to implement self._decompose_two_qubit_operation

Why not copy CZTargetGateset's implementation?

@arulandu
Copy link

I would copy their implementation and I could decompose PhasedXZ into {Rx,Rz}, but I'm unsure what to do about the
CZPowGate and GlobalPhaseGate

@vtomole
Copy link

vtomole commented Nov 11, 2024

CZ is in PYQIR_OP_MAP. Why not call two_qubit_matrix_to_cz_operations like CZTargetGateset does?

@ryanhill1
Copy link
Member Author

ryanhill1 commented Nov 11, 2024

You can leave out GlobalPhaseGate as there's not a way to represent that in pyqir at the moment (to my knowledge).

And yes, like Victory said, the "CZ" gate is already accounted for in our current implementation, so if the CZPowGate exponent matches to a regular Pauli Z, then we can just map that directly to pyqir._native.cz. Otherwise, you can look at it like ControlledGate(ZPowGate) and follow the different ZPowGate exponent cases that are given in opsets.py:

elif isinstance(gate, (cirq.ops.XPowGate, cirq.ops.YPowGate, cirq.ops.ZPowGate)):
if gate.exponent == 1 or (
isinstance(gate, cirq.ZPowGate) and gate.exponent in [0.25, -0.25, 0.5, -0.5]
):
op_name = str(gate) # X, Y, Z, S, T, S**-1, T**-1
else:
op_name = f"R{gate.__class__.__name__[0].lower()}" # Rotations

@arulandu
Copy link

arulandu commented Nov 11, 2024

I already call two_qubit_matrix_to_cz_operations at the moment. I'll use ^ for CZPowGate and will update afterwards. Thank you!

@arulandu
Copy link

arulandu commented Nov 12, 2024

Tried this in 530a0ca. Running into a max-recursion depth issue with decompose. Will debug through Cirq implementation tomorrow

@vtomole
Copy link

vtomole commented Nov 12, 2024

The recursion issue is cause your decomposer is not getting to your target gateset. Here is a decomposer that will

# Copyright 2018 The Cirq Developers
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from typing import Sequence, Union, Type, List

import cirq
from cirq.protocols.decompose_protocol import DecomposeResult


class QirTargetGateSet(cirq.TwoQubitCompilationTargetGateset):
    def __init__(
        self,
        *,
        atol: float = 1e-8,
        allow_partial_czs: bool = False,
        additional_gates: Sequence[
            Union[Type["cirq.Gate"], "cirq.Gate", "cirq.GateFamily"]
        ] = (),
        preserve_moment_structure: bool = True,
    ) -> None:
        super().__init__(
            cirq.IdentityGate,
            cirq.HPowGate,
            cirq.XPowGate,
            cirq.YPowGate,
            cirq.ZPowGate,
            cirq.SWAP,
            cirq.CNOT,
            cirq.CZ,
            cirq.TOFFOLI,
            cirq.ResetChannel,
            *additional_gates,
            name="QirTargetGateset",
            preserve_moment_structure=preserve_moment_structure,
        )
        self.allow_partial_czs = allow_partial_czs
        self.atol = atol

    @property
    def postprocess_transformers(self) -> List["cirq.TRANSFORMER"]:
        return []

    def _decompose_single_qubit_operation(
        self, op: "cirq.Operation", moment_idx: int
    ) -> DecomposeResult:
        qubit = op.qubits[0]
        mat = cirq.unitary(op)
        for gate in cirq.single_qubit_matrix_to_gates(mat, self.atol):
            yield gate(qubit)

    def _decompose_two_qubit_operation(self, op: "cirq.Operation", _) -> "cirq.OP_TREE":
        if not cirq.has_unitary(op):
            return NotImplemented
        return cirq.two_qubit_matrix_to_cz_operations(
            op.qubits[0],
            op.qubits[1],
            cirq.unitary(op),
            allow_partial_czs=self.allow_partial_czs,
            atol=self.atol,
        )


circuit = cirq.testing.random_circuit(qubits=4, n_moments=6, op_density=0.8)
# Compile the circuit for CZ+ QIR Target Gateset
gateset = QirTargetGateSet()
qir_circuit = cirq.optimize_for_target_gateset(circuit, gateset=gateset)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
cirq Related to Cirq conversions good first issue Good for newcomers
Projects
None yet
Development

No branches or pull requests

3 participants