Skip to content
Open
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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ arbitrarily applied mapper pass.
- The following 2-qubit gates: `CV`, `CY`, `DCNOT`, `ECR`, `ISWAP`, `InvSqrtSWAP`, `M`, `MS`, `SqrtISWAP`, and `SqrtSWAP`
- Add `add_instruction` method to the `CircuitBuilder`
- libQASM parser accepts measure instruction aliases: `measureX`, `measureY`, and `measureZ`
- The following 3-qubit gates: `CCX` (alias `CCNOT`) and `CSWAP`
- `ThreeQubitGateDecomposer` to decompose the 3-qubit gates into CZ gates and single-qubit gates
- The `CqasmV1Exporter` exports the Toffoli gate

## [ 0.9.0 ] - [ 2025-12-19 ]

Expand Down
4 changes: 4 additions & 0 deletions opensquirrel/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@
Wait,
)
from opensquirrel.ir.default_gates import (
CCX,
CNOT,
CR,
CSWAP,
CV,
CY,
CZ,
Expand Down Expand Up @@ -47,8 +49,10 @@
from opensquirrel.register_manager import BitRegister, QubitRegister

__all__ = [
"CCX",
"CNOT",
"CR",
"CSWAP",
"CV",
"CY",
"CZ",
Expand Down
12 changes: 12 additions & 0 deletions opensquirrel/default_instructions.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@
Wait,
)
from opensquirrel.ir.default_gates import (
CCX,
CNOT,
CR,
CSWAP,
CV,
CY,
CZ,
Expand Down Expand Up @@ -50,6 +52,7 @@
if TYPE_CHECKING:
from opensquirrel.ir import ControlInstruction, Gate, Instruction, NonUnitary
from opensquirrel.ir.single_qubit_gate import SingleQubitGate
from opensquirrel.ir.three_qubit_gate import ThreeQubitGate
from opensquirrel.ir.two_qubit_gate import TwoQubitGate

default_bsr_without_params_set: dict[str, type[SingleQubitGate]] = {
Expand Down Expand Up @@ -101,13 +104,22 @@
"SWAP": SWAP,
}

default_three_qubit_gate_set: dict[str, type[ThreeQubitGate]] = {
"CCX": CCX,
"CSWAP": CSWAP,
}

default_gate_alias_set = {
"CCNOT": CCX,
"Fredkin": CSWAP,
"Hadamard": H,
"Identity": I,
"Toffoli": CCX,
}
default_gate_set: dict[str, type[Gate]] = {
**default_single_qubit_gate_set,
**default_two_qubit_gate_set,
**default_three_qubit_gate_set,
**default_gate_alias_set,
}

Expand Down
3 changes: 3 additions & 0 deletions opensquirrel/ir/default_gates/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
Y,
Z,
)
from opensquirrel.ir.default_gates.three_qubit_gates import CCX, CSWAP
from opensquirrel.ir.default_gates.two_qubit_gates import (
CNOT,
CR,
Expand All @@ -39,8 +40,10 @@
)

__all__ = [
"CCX",
"CNOT",
"CR",
"CSWAP",
"CV",
"CY",
"CZ",
Expand Down
59 changes: 59 additions & 0 deletions opensquirrel/ir/default_gates/three_qubit_gates.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import numpy as np

from opensquirrel.ir.expression import Qubit, QubitLike
from opensquirrel.ir.semantics import MatrixGateSemantic
from opensquirrel.ir.three_qubit_gate import ThreeQubitGate


class CCX(ThreeQubitGate):
def __init__(self, control_qubit_0: QubitLike, control_qubit_1: QubitLike, target_qubit: QubitLike) -> None:
super().__init__(
qubit0=control_qubit_0,
qubit1=control_qubit_1,
qubit2=target_qubit,
gate_semantic=MatrixGateSemantic(
matrix=np.array(
[
[1, 0, 0, 0, 0, 0, 0, 0],
[0, 1, 0, 0, 0, 0, 0, 0],
[0, 0, 1, 0, 0, 0, 0, 0],
[0, 0, 0, 1, 0, 0, 0, 0],
[0, 0, 0, 0, 1, 0, 0, 0],
[0, 0, 0, 0, 0, 1, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 1],
[0, 0, 0, 0, 0, 0, 1, 0],
],
),
),
name="CCX",
)
self.control_qubit_0 = Qubit(control_qubit_0)
self.control_qubit_1 = Qubit(control_qubit_1)
self.target_qubit = Qubit(target_qubit)


class CSWAP(ThreeQubitGate):
def __init__(self, control_qubit: QubitLike, qubit_0: QubitLike, qubit_1: QubitLike) -> None:
super().__init__(
qubit0=control_qubit,
qubit1=qubit_0,
qubit2=qubit_1,
gate_semantic=MatrixGateSemantic(
matrix=np.array(
[
[1, 0, 0, 0, 0, 0, 0, 0],
[0, 1, 0, 0, 0, 0, 0, 0],
[0, 0, 1, 0, 0, 0, 0, 0],
[0, 0, 0, 1, 0, 0, 0, 0],
[0, 0, 0, 0, 1, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 1, 0],
[0, 0, 0, 0, 0, 1, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 1],
],
),
),
name="CSWAP",
)
self.control_qubit = Qubit(control_qubit)
self.qubit_0 = Qubit(qubit_0)
self.qubit_1 = Qubit(qubit_1)
3 changes: 3 additions & 0 deletions opensquirrel/ir/ir.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
from opensquirrel.ir.semantics.canonical_gate import CanonicalAxis
from opensquirrel.ir.single_qubit_gate import SingleQubitGate
from opensquirrel.ir.statement import Instruction, Statement
from opensquirrel.ir.three_qubit_gate import ThreeQubitGate
from opensquirrel.ir.two_qubit_gate import TwoQubitGate


Expand Down Expand Up @@ -67,6 +68,8 @@ def visit_single_qubit_gate(self, gate: SingleQubitGate) -> Any: ...

def visit_two_qubit_gate(self, gate: TwoQubitGate) -> Any: ...

def visit_three_qubit_gate(self, gate: ThreeQubitGate) -> Any: ...

def visit_bloch_sphere_rotation(self, bloch_sphere_rotation: BlochSphereRotation) -> Any: ...

def visit_bsr_no_params(self, gate: BsrNoParams) -> Any: ...
Expand Down
60 changes: 60 additions & 0 deletions opensquirrel/ir/three_qubit_gate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
from functools import cached_property
from typing import Any

from opensquirrel.ir import Gate, IRVisitor, Qubit, QubitLike
from opensquirrel.ir.semantics import MatrixGateSemantic
from opensquirrel.ir.semantics.gate_semantic import GateSemantic


class ThreeQubitGate(Gate):
def __init__(
self,
qubit0: QubitLike,
qubit1: QubitLike,
qubit2: QubitLike,
gate_semantic: GateSemantic,
name: str = "ThreeQubitGate",
) -> None:
Gate.__init__(self, name)
self.qubit0 = Qubit(qubit0)
self.qubit1 = Qubit(qubit1)
self.qubit2 = Qubit(qubit2)

# A three-qubit gate can only be described by a matrix. ControlledGateSemantic describes a
# single control qubit acting on a Bloch sphere rotation, and CanonicalGateSemantic
# describes the canonical decomposition of a two-qubit gate.
self._matrix = gate_semantic if isinstance(gate_semantic, MatrixGateSemantic) else None
self.gate_semantic = gate_semantic

if self._check_repeated_qubit_operands(self.qubit_operands):
msg = "qubit operands cannot be the same qubit"
raise ValueError(msg)

@cached_property
def matrix(self) -> MatrixGateSemantic:
if self._matrix:
return self._matrix

msg = f"invalid gate semantic: {self.gate_semantic}"
raise ValueError(msg)

@property
def qubit_operands(self) -> tuple[Qubit, ...]:
return (self.qubit0, self.qubit1, self.qubit2)

def accept(self, visitor: IRVisitor) -> Any:
"""Accepts visitor and processes this IR node."""
visit_parent = super().accept(visitor)
return visit_parent if visit_parent is not None else visitor.visit_three_qubit_gate(self)

def is_identity(self) -> bool:
"""Checks if the three-qubit gate is an identity gate.

Returns:
True if the three-qubit gate is an identity gate, False otherwise.

"""
return self.matrix.is_identity()

def __repr__(self) -> str:
return f"ThreeQubitGate(qubits=[{self.qubit0, self.qubit1, self.qubit2}], gate_semantic={self.gate_semantic})"
2 changes: 2 additions & 0 deletions opensquirrel/passes/decomposer/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from opensquirrel.passes.decomposer.mckay_decomposer import McKayDecomposer
from opensquirrel.passes.decomposer.swap2cnot_decomposer import SWAP2CNOTDecomposer
from opensquirrel.passes.decomposer.swap2cz_decomposer import SWAP2CZDecomposer
from opensquirrel.passes.decomposer.three_qubit_gate_decomposer import ThreeQubitGateDecomposer

__all__ = [
"CNOT2CZDecomposer",
Expand All @@ -22,6 +23,7 @@
"McKayDecomposer",
"SWAP2CNOTDecomposer",
"SWAP2CZDecomposer",
"ThreeQubitGateDecomposer",
"XYXDecomposer",
"XZXDecomposer",
"YXYDecomposer",
Expand Down
81 changes: 81 additions & 0 deletions opensquirrel/passes/decomposer/three_qubit_gate_decomposer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
from __future__ import annotations

from math import pi
from typing import TYPE_CHECKING

from opensquirrel import CZ, Ry, T, TDagger
from opensquirrel.ir.three_qubit_gate import ThreeQubitGate
from opensquirrel.passes.decomposer.general_decomposer import Decomposer

if TYPE_CHECKING:
from opensquirrel.ir import Gate, Qubit


class ThreeQubitGateDecomposer(Decomposer):
def decompose(self, instruction: Gate) -> list[Gate]:
"""Predefined decomposition of the three-qubit gates into CZ gates and single-qubit gates.

The Toffoli gate (CCX) is decomposed into 6 CZ gates and single-qubit rotations, which is
the minimum possible according to
[Shende and Markov (2008)](https://arxiv.org/abs/0803.2316). The Fredkin gate (CSWAP) is
decomposed as a Toffoli gate conjugated by two CNOT gates, giving 8 CZ gates.

Note:
This decomposition preserves the global phase of the three-qubit gate.
Gates other than CCX and CSWAP are returned unchanged.

Args:
instruction: three-qubit gate to decompose.

Returns:
A sequence of CZ gates and single-qubit gates that decompose the three-qubit gate.

"""
if not isinstance(instruction, ThreeQubitGate) or instruction.name not in ("CCX", "CSWAP"):
return [instruction]

gate = instruction

if gate.name == "CCX":
control_qubit_0, control_qubit_1, target_qubit = gate.qubit_operands
return self._get_toffoli_gates(control_qubit_0, control_qubit_1, target_qubit)

control_qubit, qubit_0, qubit_1 = gate.qubit_operands
return [
*self._get_cnot_gates(qubit_1, qubit_0),
*self._get_toffoli_gates(control_qubit, qubit_0, qubit_1),
*self._get_cnot_gates(qubit_1, qubit_0),
]

def _get_cnot_gates(self, control_qubit: Qubit, target_qubit: Qubit) -> list[Gate]:
"""CNOT gate expressed as a CZ gate conjugated by Ry rotations, as in the CNOT2CZDecomposer."""
return [
Ry(target_qubit, -pi / 2),
CZ(control_qubit, target_qubit),
Ry(target_qubit, pi / 2),
]

def _get_toffoli_gates(self, control_qubit_0: Qubit, control_qubit_1: Qubit, target_qubit: Qubit) -> list[Gate]:
"""Toffoli gate as 6 CNOT gates and T rotations, with every CNOT gate rewritten in terms of CZ.

The Hadamard gates that conjugate the target qubit in the textbook circuit are replaced by
the same Ry rotations used above, which likewise map Z onto X under conjugation.
"""
a, b, c = control_qubit_0, control_qubit_1, target_qubit
return [
Ry(c, -pi / 2),
*self._get_cnot_gates(b, c),
TDagger(c),
*self._get_cnot_gates(a, c),
T(c),
*self._get_cnot_gates(b, c),
TDagger(c),
*self._get_cnot_gates(a, c),
T(b),
T(c),
Ry(c, pi / 2),
*self._get_cnot_gates(a, b),
T(a),
TDagger(b),
*self._get_cnot_gates(a, b),
]
13 changes: 13 additions & 0 deletions opensquirrel/passes/exporter/cqasmv1_exporter.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
from opensquirrel.circuit import Circuit
from opensquirrel.ir.expression import Axis
from opensquirrel.ir.single_qubit_gate import SingleQubitGate
from opensquirrel.ir.three_qubit_gate import ThreeQubitGate
from opensquirrel.ir.two_qubit_gate import TwoQubitGate
from opensquirrel.register_manager import RegisterManager

Expand All @@ -57,6 +58,11 @@ def export(self, circuit: Circuit) -> str:
return _post_process(cqasmv1_creator.output).rstrip() + "\n"


# cQASM v1 names for the three-qubit gates it supports. The Fredkin gate (CSWAP) is not part of the
# cQASM v1 default instruction set, and is therefore absent.
CQASM_V1_THREE_QUBIT_GATE_NAMES = {"CCX": "toffoli"}


class CqasmV1ExporterParseError(Exception):
pass

Expand Down Expand Up @@ -128,6 +134,13 @@ def visit_two_qubit_gate(self, gate: TwoQubitGate) -> Any:
else:
self.output += f"{gate.name.lower()} {qubit_operand_0}, {qubit_operand_1}\n"

def visit_three_qubit_gate(self, gate: ThreeQubitGate) -> Any:
if gate.name not in CQASM_V1_THREE_QUBIT_GATE_NAMES:
raise UnsupportedGateError(gate)

qubit_operands = ", ".join(qubit.accept(self) for qubit in gate.qubit_operands)
self.output += f"{CQASM_V1_THREE_QUBIT_GATE_NAMES[gate.name]} {qubit_operands}\n"

def visit_measure(self, measure: Measure) -> None:
qubit_argument = measure.qubit_operands[0].accept(self)
axis = measure.axis
Expand Down
11 changes: 10 additions & 1 deletion opensquirrel/reindexer/qubit_reindexer.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
Wait,
)
from opensquirrel.ir.single_qubit_gate import SingleQubitGate
from opensquirrel.ir.three_qubit_gate import ThreeQubitGate
from opensquirrel.ir.two_qubit_gate import TwoQubitGate
from opensquirrel.register_manager import (
DEFAULT_BIT_REGISTER_NAME,
Expand Down Expand Up @@ -68,7 +69,15 @@ def visit_single_qubit_gate(self, gate: SingleQubitGate) -> SingleQubitGate:
def visit_two_qubit_gate(self, gate: TwoQubitGate) -> TwoQubitGate:
qubit0 = self.qubit_indices.index(gate.qubit0.index)
qubit1 = self.qubit_indices.index(gate.qubit1.index)
return TwoQubitGate(qubit0=qubit0, qubit1=qubit1, gate_semantic=gate.gate_semantic)
return TwoQubitGate(qubit0=qubit0, qubit1=qubit1, gate_semantic=gate.gate_semantic, name=gate.name)

def visit_three_qubit_gate(self, gate: ThreeQubitGate) -> ThreeQubitGate:
qubit0 = self.qubit_indices.index(gate.qubit0.index)
qubit1 = self.qubit_indices.index(gate.qubit1.index)
qubit2 = self.qubit_indices.index(gate.qubit2.index)
return ThreeQubitGate(
qubit0=qubit0, qubit1=qubit1, qubit2=qubit2, gate_semantic=gate.gate_semantic, name=gate.name
)


def get_reindexed_circuit(
Expand Down
Loading
Loading