Skip to content

Commit

Permalink
Merge pull request #370 from QunaSys/circuit-cost-estimator
Browse files Browse the repository at this point in the history
Circuit cost estimator
  • Loading branch information
kwkbtr authored Oct 24, 2024
2 parents b7d154c + 4ca1ee8 commit da930fa
Show file tree
Hide file tree
Showing 17 changed files with 1,395 additions and 18 deletions.
22 changes: 20 additions & 2 deletions packages/circuit/quri_parts/circuit/gate_names.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@
U1: Literal["U1"] = "U1"
U2: Literal["U2"] = "U2"
U3: Literal["U3"] = "U3"
Measurement: Literal["Measurement"] = "Measurement"

SINGLE_QUBIT_GATE_NAMES: set[SingleQubitGateNameType] = {
Identity,
Expand Down Expand Up @@ -130,10 +129,22 @@ def is_multi_qubit_gate_name(gate_name: str) -> TypeGuard[MultiQubitGateNameType

#: A set of strings representing gate names.
NonParametricGateNameType: TypeAlias = Union[
SingleQubitGateNameType, TwoQubitGateNameType, MultiQubitGateNameType
SingleQubitGateNameType,
TwoQubitGateNameType,
ThreeQubitGateNameType,
MultiQubitGateNameType,
]


def is_non_parametric_gate_name(gate_name: str) -> TypeGuard[NonParametricGateNameType]:
return gate_name in (
SINGLE_QUBIT_GATE_NAMES
| TWO_QUBIT_GATE_NAMES
| THREE_QUBIT_GATE_NAMES
| MULTI_QUBIT_GATE_NAMES
)


UnitaryMatrixGateNameType: TypeAlias = Literal["UnitaryMatrix"]

UnitaryMatrix: Literal["UnitaryMatrix"] = "UnitaryMatrix"
Expand Down Expand Up @@ -171,6 +182,11 @@ def is_parametric_gate_name(gate_name: str) -> TypeGuard[ParametricGateNameType]
return gate_name in PARAMETRIC_GATE_NAMES


MeasurementGateNameType: TypeAlias = Literal["Measurement"]
Measurement: Literal["Measurement"] = "Measurement"
MEASUREMENT_GATE_NAMES: set[MeasurementGateNameType] = {Measurement}


#: Valid Pauli gate names.
PauliNameType: TypeAlias = Literal["X", "Y", "Z", "Pauli"]

Expand Down Expand Up @@ -230,6 +246,7 @@ def is_pauli_name(gate_name: str) -> TypeGuard[PauliNameType]:
MultiQubitGateNameType,
UnitaryMatrixGateNameType,
ParametricGateNameType,
MeasurementGateNameType,
]

GATE_NAMES: set[GateNameType] = (
Expand All @@ -239,6 +256,7 @@ def is_pauli_name(gate_name: str) -> TypeGuard[PauliNameType]:
| MULTI_QUBIT_GATE_NAMES
| UNITARY_MATRIX_GATE_NAMES
| PARAMETRIC_GATE_NAMES
| MEASUREMENT_GATE_NAMES
)


Expand Down
15 changes: 14 additions & 1 deletion packages/circuit/quri_parts/circuit/topology/square_lattice.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.

from collections.abc import Mapping, Sequence
from collections.abc import Collection, Mapping, Sequence
from typing import Callable, Optional

from typing_extensions import TypeAlias
Expand Down Expand Up @@ -46,6 +46,19 @@ def __init__(
f"The same coordinates were specified multiple times: {v}."
)
self._coord_to_qubit[v] = k
self._xsize, self._ysize = xsize, ysize

@property
def xsize(self) -> int:
return self._xsize

@property
def ysize(self) -> int:
return self._ysize

@property
def qubits(self) -> Collection[int]:
return self._qubit_to_coord.keys()

def get_qubit(self, coord: Coordinate) -> int:
"""Returns qubit index corresponding to the given coordinate."""
Expand Down
27 changes: 24 additions & 3 deletions packages/circuit/quri_parts/circuit/transpile/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@
from .gateset import (
CliffordConversionTranspiler,
GateSetConversionTranspiler,
ParametricRX2RZHTranspiler,
ParametricRY2RZHTranspiler,
RotationConversionTranspiler,
RX2RYRZTranspiler,
RX2RZHTranspiler,
Expand All @@ -87,6 +89,7 @@
ParallelDecomposer,
ParametricCircuitTranspiler,
ParametricCircuitTranspilerProtocol,
ParametricSequentialTranspiler,
ParametricTranspiler,
SequentialTranspiler,
)
Expand Down Expand Up @@ -195,9 +198,20 @@ def __init__(self, epsilon: float = 1.0e-9):
#: CircuitTranspiler to transpile a QuntumCircuit into another
#: QuantumCircuit containing only basic gates for STAR architecture (H, RZ, and CNOT).
#: (UnitaryMatrix gate for 3 or more qubits are not decomposed.)
STARSetTranspiler: Callable[
[], CircuitTranspiler
] = lambda: GateSetConversionTranspiler([gate_names.H, gate_names.RZ, gate_names.CNOT])
STARSetTranspiler: Callable[[], CircuitTranspiler] = lambda: SequentialTranspiler(
[
RX2RZHTranspiler(),
RY2RZHTranspiler(),
GateSetConversionTranspiler(
[
gate_names.H,
gate_names.S,
gate_names.RZ,
gate_names.CNOT,
]
),
]
)


__all__ = [
Expand All @@ -223,6 +237,13 @@ def __init__(self, epsilon: float = 1.0e-9):
"ParallelDecomposer",
"PauliDecomposeTranspiler",
"PauliRotationDecomposeTranspiler",
"ParametricPauliRotationDecomposeTranspiler",
"ParametricTranspiler",
"ParametricCircuitTranspiler",
"ParametricCircuitTranspilerProtocol",
"ParametricRX2RZHTranspiler",
"ParametricRY2RZHTranspiler",
"ParametricSequentialTranspiler",
"QubitRemappingTranspiler",
"RotationConversionTranspiler",
"RX2RYRZTranspiler",
Expand Down
92 changes: 91 additions & 1 deletion packages/circuit/quri_parts/circuit/transpile/gateset.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,13 @@
from math import pi
from typing import cast

from quri_parts.circuit import ImmutableQuantumCircuit, QuantumCircuit
from quri_parts.circuit import (
ImmutableQuantumCircuit,
LinearMappedParametricQuantumCircuit,
LinearParameterMapping,
ParametricQuantumCircuitProtocol,
QuantumCircuit,
)
from quri_parts.circuit import gates as gf
from quri_parts.circuit.gate import QuantumGate
from quri_parts.circuit.gate_names import (
Expand All @@ -32,6 +38,10 @@
GateNameType,
H,
Identity,
ParametricPauliRotation,
ParametricRX,
ParametricRY,
ParametricRZ,
Pauli,
PauliRotation,
S,
Expand Down Expand Up @@ -86,6 +96,7 @@
CircuitTranspiler,
CircuitTranspilerProtocol,
GateKindDecomposer,
ParametricCircuitTranspilerProtocol,
SequentialTranspiler,
)
from .unitary_matrix_decomposer import (
Expand Down Expand Up @@ -301,6 +312,85 @@ def decompose(self, gate: QuantumGate) -> Sequence[QuantumGate]:
]


class ParametricRX2RZHTranspiler(ParametricCircuitTranspilerProtocol):
def __call__(
self, circuit: ParametricQuantumCircuitProtocol
) -> LinearMappedParametricQuantumCircuit:
ret = LinearMappedParametricQuantumCircuit(
circuit.qubit_count, circuit.cbit_count
)
ret._param_mapping = LinearParameterMapping(circuit.param_mapping.in_params)
pmap = circuit.param_mapping.mapping

for gate, param in circuit.primitive_circuit().gates_and_params:
if isinstance(gate, QuantumGate):
ret.add_gate(gate)
else:
if param is None:
raise ValueError("Parametric gate with no Parameter: {gate}")

qubit = gate.target_indices[0]
if gate.name == ParametricRX:
ret.add_H_gate(qubit)
ret.add_ParametricRZ_gate(qubit, pmap[param])
ret.add_H_gate(qubit)
elif gate.name == ParametricRY:
ret.add_ParametricRY_gate(qubit, pmap[param])
elif gate.name == ParametricRZ:
ret.add_ParametricRZ_gate(qubit, pmap[param])
elif gate.name == ParametricPauliRotation:
ret.add_ParametricPauliRotation_gate(
gate.target_indices,
gate.pauli_ids,
pmap[param],
)
else:
raise ValueError(f"Unsupported parametric gate: {gate.name}")

return ret


class ParametricRY2RZHTranspiler(ParametricCircuitTranspilerProtocol):
def __call__(
self, circuit: ParametricQuantumCircuitProtocol
) -> LinearMappedParametricQuantumCircuit:
ret = LinearMappedParametricQuantumCircuit(
circuit.qubit_count, circuit.cbit_count
)
ret._param_mapping = LinearParameterMapping(circuit.param_mapping.in_params)
pmap = circuit.param_mapping.mapping

for gate, param in circuit.primitive_circuit().gates_and_params:
if isinstance(gate, QuantumGate):
ret.add_gate(gate)
else:
if param is None:
raise ValueError("Parametric gate with no Parameter: {gate}")

qubit = gate.target_indices[0]
if gate.name == ParametricRX:
ret.add_ParametricRX_gate(qubit, pmap[param])
elif gate.name == ParametricRY:
qubit = gate.target_indices[0]
ret.add_RZ_gate(qubit, -pi / 2.0)
ret.add_H_gate(qubit)
ret.add_ParametricRZ_gate(qubit, pmap[param])
ret.add_H_gate(qubit)
ret.add_RZ_gate(qubit, pi / 2.0)
elif gate.name == ParametricRZ:
ret.add_ParametricRZ_gate(qubit, pmap[param])
elif gate.name == ParametricPauliRotation:
ret.add_ParametricPauliRotation_gate(
gate.target_indices,
gate.pauli_ids,
pmap[param],
)
else:
raise ValueError(f"Unsupported parametric gate: {gate.name}")

return ret


class IdentityTranspiler(CircuitTranspilerProtocol):
"""A CircuitTranspiler returns the same circuit as the input."""

Expand Down
12 changes: 12 additions & 0 deletions packages/circuit/quri_parts/circuit/transpile/transpiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -234,3 +234,15 @@ def __call__(
ret.extend(self._transpiler(cc).gates)

return ret


class ParametricSequentialTranspiler(ParametricCircuitTranspilerProtocol):
def __init__(self, transpilers: Sequence[ParametricCircuitTranspiler]):
self._transpilers = transpilers

def __call__(
self, circuit: ParametricQuantumCircuitProtocol
) -> ParametricQuantumCircuitProtocol:
for transpiler in self._transpilers:
circuit = transpiler(circuit)
return circuit
Original file line number Diff line number Diff line change
Expand Up @@ -701,6 +701,11 @@ def test_starset_transpile(self) -> None:
transpiled = STARSetTranspiler()(circuit)

target_set = {gate.name for gate in transpiled.gates}
expect_set = {gate_names.H, gate_names.RZ, gate_names.CNOT}
expect_set = {
gate_names.H,
gate_names.S,
gate_names.RZ,
gate_names.CNOT,
}

assert target_set <= expect_set
67 changes: 57 additions & 10 deletions packages/circuit/tests/circuit/transpile/test_gateset.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,16 @@
# See the License for the specific language governing permissions and
# limitations under the License.

from typing import Union

import numpy as np

from quri_parts.circuit import (
ImmutableQuantumCircuit,
NonParametricQuantumCircuit,
ParametricQuantumCircuit,
ParametricQuantumCircuitProtocol,
ParametricQuantumGate,
QuantumCircuit,
QuantumGate,
gate_names,
Expand All @@ -21,6 +27,8 @@
from quri_parts.circuit.transpile import (
CliffordConversionTranspiler,
GateSetConversionTranspiler,
ParametricRX2RZHTranspiler,
ParametricRY2RZHTranspiler,
RotationConversionTranspiler,
RX2RYRZTranspiler,
RX2RZHTranspiler,
Expand All @@ -30,18 +38,29 @@
)


def _gates_close(x: QuantumGate, y: QuantumGate) -> bool:
return (
x.name == y.name
and x.target_indices == y.target_indices
and x.control_indices == y.control_indices
and np.allclose(x.params, y.params)
and x.pauli_ids == y.pauli_ids
and np.allclose(x.unitary_matrix, y.unitary_matrix)
)
def _gates_close(
x: Union[QuantumGate, ParametricQuantumGate],
y: Union[QuantumGate, ParametricQuantumGate],
) -> bool:
if isinstance(x, ParametricQuantumGate) and isinstance(y, ParametricQuantumGate):
return x == y
elif isinstance(x, QuantumGate) and isinstance(y, QuantumGate):
return (
x.name == y.name
and x.target_indices == y.target_indices
and x.control_indices == y.control_indices
and np.allclose(x.params, y.params)
and x.pauli_ids == y.pauli_ids
and np.allclose(x.unitary_matrix, y.unitary_matrix)
)
else:
return False


def _circuit_close(x: ImmutableQuantumCircuit, y: ImmutableQuantumCircuit) -> bool:
def _circuit_close(
x: Union[NonParametricQuantumCircuit, ParametricQuantumCircuitProtocol],
y: Union[NonParametricQuantumCircuit, ParametricQuantumCircuitProtocol],
) -> bool:
return len(x.gates) == len(y.gates) and all(
_gates_close(a, b) for a, b in zip(x.gates, y.gates)
)
Expand Down Expand Up @@ -294,6 +313,34 @@ def test_rz2rxry_transpile(self) -> None:
assert _circuit_close(transpiled, expect)


class TestParametricRotationKindDecompose:
def test_parametricrx2rzh_transpile(self) -> None:
circuit = ParametricQuantumCircuit(1)
circuit.add_ParametricRX_gate(0)
transpiled = ParametricRX2RZHTranspiler()(circuit)

expect = ParametricQuantumCircuit(1)
expect.add_H_gate(0)
expect.add_ParametricRZ_gate(0)
expect.add_H_gate(0)

assert _circuit_close(transpiled, expect)

def test_parametricry2rzh_transpile(self) -> None:
circuit = ParametricQuantumCircuit(1)
circuit.add_ParametricRY_gate(0)
transpiled = ParametricRY2RZHTranspiler()(circuit)

expect = ParametricQuantumCircuit(1)
expect.add_RZ_gate(0, -np.pi / 2.0)
expect.add_H_gate(0)
expect.add_ParametricRZ_gate(0)
expect.add_H_gate(0)
expect.add_RZ_gate(0, np.pi / 2.0)

assert _circuit_close(transpiled, expect)


class TestRotationConversion:
def test_rxryrz_transpile(self) -> None:
circuit = _rotation_circuit(np.pi / 7.0)
Expand Down
Loading

1 comment on commit da930fa

@github-actions
Copy link

Choose a reason for hiding this comment

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

Please sign in to comment.