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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ parameter
- Add `interaction_graph` as a property of the `Circuit`.
- Add `mapping` as an attribute of the `Circuit`, which contains an identity mapping, prior to any
arbitrarily applied mapper pass.
- The measure instruction accepts an axis parameter
- `MeasureDecomposer` to decompose arbitrary measurements to a decomposition of single-qubit gates and a +Z measurement.

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

Expand Down
6 changes: 2 additions & 4 deletions docs/circuit-builder/instructions/control-instructions.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
!!! note "Coming soon"

# Control Instructions

| Name | Operator | Description | Example |
|------------|----------------|--------------------------------------------------|-------------------------------------------------------------------------|
| [Barrier](https://qutech-delft.github.io/cQASM-spec/latest/language_specification/statements/instructions/control_instructions/barrier_instruction.html) | _barrier_ | Barrier gate | `builder.barrier(0)` |
| [Wait](https://qutech-delft.github.io/cQASM-spec/latest/language_specification/statements/instructions/control_instructions/wait_instruction.html) | _wait_ | Wait gate | `builder.wait(0)` |
| [Barrier](https://qutech-delft.github.io/cQASM-spec/latest/language_specification/statements/instructions/control_instructions/barrier_instruction.html) | _barrier_ | Places a barrier on specified qubit(s) | `builder.barrier(0)` |
| [Wait](https://qutech-delft.github.io/cQASM-spec/latest/language_specification/statements/instructions/control_instructions/wait_instruction.html) | _wait_ | Enforces given cycle times between consecutive instructions on specified qubit | `builder.wait(5, 0)` |
53 changes: 27 additions & 26 deletions docs/circuit-builder/instructions/gates.md
Original file line number Diff line number Diff line change
@@ -1,28 +1,29 @@
!!! note "Coming soon"

# [Unitary Instructions](https://qutech-delft.github.io/cQASM-spec/latest/language_specification/statements/instructions/unitary_instructions.html)

| Name | Operator | Description | Example |
|------------|----------------|--------------------------------------------------|----------------------------|
| I | _I_ | Identity gate | `builder.I(0)` |
| H | _H_ | Hadamard gate | `builder.H(0)` |
| X | _X_ | Pauli-X | `builder.X(0)` |
| X90 | _X<sub>90</sub>_| Rotation around the x-axis of $\frac{\pi}{2}$ | `builder.X90(0)` |
| mX90 | _X<sub>-90</sub>_| Rotation around the x-axis of $\frac{-\pi}{2}$ | `builder.mX90(0)` |
| Y | _Y_ | Pauli-Y | `builder.Y(0)` |
| Y90 | _Y<sub>90</sub>_| Rotation around the y-axis of $\frac{\pi}{2}$ | `builder.Y90(0)` |
| mY90 | _Y<sub>-90</sub>_| Rotation around the y-axis of $\frac{-\pi}{2}$ | `builder.mY90(0)` |
| Z | _Z_ | Pauli-Z | `builder.Z(0)` |
| S | _S_ | Phase gate | `builder.S(0)` |
| Sdag | _S<sup>†</sup>_| S dagger gate | `builder.Sdag(0)` |
| T | _T_ | T | `builder.T(0)` |
| Tdag | _T<sup>†</sup>_| T dagger gate | `builder.Tdag(0)` |
| Rx | _R<sub>x</sub>($\theta$)_| Arbitrary rotation around x-axis | `builder.Rx(0, 0.23)` |
| Ry | _R<sub>y</sub>($\theta$)_| Arbitrary rotation around y-axis | `builder.Ry(0, 0.23)` |
| Rz | _R<sub>z</sub>($\theta$)_ | Arbitrary rotation around z-axis | `builder.Rz(0, 2)` |
| Rn | _R<sub>n</sub>(n<sub>x</sub>, n<sub>y</sub>, n<sub>z</sub>, $\theta$, $\phi$<sub>g</sub>)_ | Arbitrary rotation around specified axis | `builder.Rn(0)` |
| CZ | _CZ_ | Controlled-Z, Controlled-Phase | `builder.CZ(1, 2)` |
| CR | _CR(\theta)_ | Controlled phase shift (arbitrary angle) | `builder.CR(0, 1, 3.1415)` |
| CRk | _CR<sub>k</sub>(k)_ | Controlled phase shift ($\frac{\pi}{2^{k-1}}$) | `builder.CRk(1, 0, 2)` |
| SWAP | _SWAP_ | SWAP gate | `builder.SWAP(1, 2)` |
| CNOT | _CNOT_ | Controlled-NOT gate | `builder.CNOT(1, 2)` |
| Name | Operator | Description | Example |
|-------|---------------- |-------------------------------------------------- |----------------------------|
| I | $I$ | Identity gate | `builder.I(0)` |
| H | $H$ | Hadamard gate | `builder.H(0)` |
| X | $X$ | Pauli-X | `builder.X(0)` |
| X90 | $X_{90}$ | Rotation around the x-axis of $\frac{\pi}{2}$ | `builder.X90(0)` |
| mX90 | $X_{-90}$ | Rotation around the x-axis of $-\frac{\pi}{2}$ | `builder.mX90(0)` |
| Y | $Y$ | Pauli-Y | `builder.Y(0)` |
| Y90 | $Y_{90}$ | Rotation around the y-axis of $\frac{\pi}{2}$ | `builder.Y90(0)` |
| mY90 | $Y_{-90}$ | Rotation around the y-axis of $-\frac{\pi}{2}$ | `builder.mY90(0)` |
| Z | $Z$ | Pauli-Z | `builder.Z(0)` |
| Z90 | $Z_{90}$ | Rotation around the z-axis of $\frac{\pi}{2}$ | `builder.Z90(0)` |
| mZ90 | $Z_{-90}$ | Rotation around the z-axis of $-\frac{\pi}{2}$ | `builder.mZ90(0)` |
| S | $S$ | Phase gate | `builder.S(0)` |
| Sdag | $S^\dagger$ | S dagger gate | `builder.Sdag(0)` |
| T | $T$ | T gate | `builder.T(0)` |
| Tdag | $T^\dagger$ | T dagger gate | `builder.Tdag(0)` |
| Rx | $R_x(\theta)$ | Arbitrary rotation around x-axis | `builder.Rx(0, 0.23)` |
| Ry | $R_y(\theta)$ | Arbitrary rotation around y-axis | `builder.Ry(0, 0.23)` |
| Rz | $R_z(\theta)$ | Arbitrary rotation around z-axis | `builder.Rz(0, 2)` |
| Rn | $R_\textbf{n}(n_x, n_y, n_z, \theta, \phi_g)$ | Arbitrary rotation around specified axis | `builder.Rn(0)` |
| U | $U(\theta, \phi,\lambda)$ | Arbitrary unitary gate | `builder.U(0, 1, 2, 3)` |
| CZ | $CZ$ | Controlled-Z, Controlled-Phase | `builder.CZ(1, 2)` |
| CR | $CR(\theta)$ | Controlled phase shift (arbitrary angle) | `builder.CR(0, 1, 3.1415)`|
| CRk | $CR_k(k)$ | Controlled phase shift ($\frac{\pi}{2^{k-1}}$) | `builder.CRk(1, 0, 2)` |
| SWAP | $SWAP$ | SWAP gate | `builder.SWAP(1, 2)` |
| CNOT | $CNOT$ | Controlled-NOT gate | `builder.CNOT(1, 2)` |
8 changes: 3 additions & 5 deletions docs/circuit-builder/instructions/non-unitaries.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
!!! note "Coming soon"

# Non-Unitary Instructions

| Name | Operator | Description | Example |
|------------|----------------|--------------------------------------------------|-------------------------------------------------------------------------|
| [Init](https://qutech-delft.github.io/cQASM-spec/latest/language_specification/statements/instructions/non_unitary_instructions/init_instruction.html) | _init_ | Initialize certain qubits in $\|0>$ | `builder.init(0)` |
| [Measure](https://qutech-delft.github.io/cQASM-spec/latest/language_specification/statements/instructions/non_unitary_instructions/measure_instruction.html) | _measure_ | Measure qubit argument | `builder.measure(0)` |
| [Reset](https://qutech-delft.github.io/cQASM-spec/latest/language_specification/statements/instructions/non_unitary_instructions/reset_instruction.html) | _reset_ | Reset a qubit's state to $\|0>$ | `builder.reset(0)` |
| [Init](https://qutech-delft.github.io/cQASM-spec/latest/language_specification/statements/instructions/non_unitary_instructions/init_instruction.html) | _init_ | Initialize the qubit in $\vert0\rangle$ | `builder.init(0)` |
| [Measure](https://qutech-delft.github.io/cQASM-spec/latest/language_specification/statements/instructions/non_unitary_instructions/measure_instruction.html) | _measure_ | Measures the qubit (by default in the Z-basis) and stores the outcome in the specified bit | `builder.measure(0, 0)` |
| [Reset](https://qutech-delft.github.io/cQASM-spec/latest/language_specification/statements/instructions/non_unitary_instructions/reset_instruction.html) | _reset_ | Resets the state of the qubit to $\vert0\rangle$ | `builder.reset(0)` |
26 changes: 26 additions & 0 deletions docs/compilation-passes/decomposition/measure-decomposer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
The measure decomposer (`MeasureDecomposer`) decomposes measurements along an arbitrary axis into at most 2 single-qubit gates followed by a measurement along the Z-axis.

## Theory

For a measurement axis defined by the unit vector
$$\hat{n} = (\sin\theta\cos\phi, \sin\theta\sin\phi, \cos\theta)$$

where $\theta = \arccos(n_z)$ and $\phi = \arctan2(n_y, n_x)$, we construct the unitary transformation:
$$U = R_z(\phi) R_y(\theta)$$

This unitary maps Z-axis eigenstates to eigenstates along the $\hat{n}$ direction. Measuring in the $\hat{n}$ basis is equivalent to applying the inverse transformation $U^\dagger = R_y(-\theta) R_z(-\phi)$ and then measuring along Z.

## Decomposition steps

1. Apply $R_z(-\phi)$
2. Apply $R_y(-\theta)$
3. Measure in the Z basis

## Common examples

| Measurement axis | Decomposition |
|---|---|
| Z | $I$ |
| X | $R_y(-\pi/2)$ |
| Y | $R_y(-\pi/2) \cdot R_z(\-pi/2)$ |
| H | $R_y(-\pi/4)$ |
8 changes: 4 additions & 4 deletions opensquirrel/circuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,9 +155,9 @@ def decompose(self, decomposer: Decomposer) -> None:
decomposer (Decomposer): The decomposer to apply.

"""
from opensquirrel.passes.decomposer import general_decomposer
from opensquirrel.passes.decomposer.general_decomposer import decompose

general_decomposer.decompose(self.ir, decomposer)
decompose(self.ir, decomposer)

def export(self, exporter: Exporter) -> Any:
"""Exports the circuit using the specified exporter.
Expand Down Expand Up @@ -208,9 +208,9 @@ def replace(self, gate: type[Gate], replacement_gates_function: Callable[..., li
replacement_gates_function (Callable[..., list[Gate]]): function that describes the replacement gates.

"""
from opensquirrel.passes.decomposer import general_decomposer
from opensquirrel.passes.decomposer.gate_replacer import replace

general_decomposer.replace(self.ir, gate, replacement_gates_function)
replace(self.ir, gate, replacement_gates_function)

def validate(self, validator: Validator) -> None:
"""Validates the circuit using the specified validator.
Expand Down
14 changes: 14 additions & 0 deletions opensquirrel/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,20 @@ def __init__(self, gate: Any, *args: Any) -> None:
super().__init__(f"{gate} is not supported", *args)


class UnsupportedMeasureError(Exception):
"""Should be raised when a measure is not supported."""

def __init__(self, measure: Any, *args: Any) -> None:
"""Init of the ``UnsupportedMeasureError``.

Args:
measure: Measure that is not supported.
"""
super().__init__(
f"measurement along {measure.axis!r} is not supported: "
"decompose measurement before exporting", *args)


class ExporterError(Exception):
"""Should be raised when a circuit cannot be exported to the desired output format."""

Expand Down
8 changes: 5 additions & 3 deletions opensquirrel/passes/decomposer/aba_decomposer.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ def Ra(self) -> Callable[..., SingleQubitGate]: ... # noqa: N802
@abstractmethod
def Rb(self) -> Callable[..., SingleQubitGate]: ... # noqa: N802

def decompose(self, gate: Gate) -> list[Gate]:
def decompose(self, instruction: Gate) -> list[Gate]:
"""Decomposes a single-qubit gate into (at most) three single-qubit gates following the
R$a$-R$b$-R$a$ decomposition, where [$ab$] are in $\\{x,y,z\\}$ and $a$ is not equal to $b$.

Expand All @@ -46,8 +46,10 @@ def decompose(self, gate: Gate) -> list[Gate]:
A sequence of (at most) three gates, following the R$a$-R$b$-R$a$ decomposition.

"""
if not isinstance(gate, SingleQubitGate):
return [gate]
if not isinstance(instruction, SingleQubitGate):
return [instruction]

gate = instruction

theta_a1, theta_b, theta_a2 = self._determine_rotation_angles(gate.bsr.axis, gate.bsr.angle)
return filter_out_identities(
Expand Down
14 changes: 6 additions & 8 deletions opensquirrel/passes/decomposer/cnot2cz_decomposer.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
from __future__ import annotations

from math import pi
from typing import TYPE_CHECKING

from opensquirrel import CZ, Ry
from opensquirrel.ir import Gate
from opensquirrel.passes.decomposer.general_decomposer import Decomposer

if TYPE_CHECKING:
from opensquirrel.ir import Gate


class CNOT2CZDecomposer(Decomposer):
def decompose(self, gate: Gate) -> list[Gate]:
def decompose(self, instruction: Gate) -> list[Gate]:
"""Predefined decomposition of CNOT gate into CZ gate with Ry rotations.

![image](../../../_static/cnot2cz.png#only-light)
Expand All @@ -21,15 +18,16 @@ def decompose(self, gate: Gate) -> list[Gate]:
This decomposition preserves the global phase of the CNOT gate.

Args:
gate (Gate): CNOT gate to decompose.
instruction (Instruction): CNOT gate to decompose.

Returns:
A sequence of gates, Ry(-π/2)-CZ-Ry(π/2), that decompose the CNOT gate.

"""
if gate.name != "CNOT":
return [gate]
if not isinstance(instruction, Gate) or instruction.name != "CNOT":
return [instruction]

gate = instruction
control_qubit, target_qubit = gate.qubit_operands
return [
Ry(target_qubit, -pi / 2),
Expand Down
10 changes: 7 additions & 3 deletions opensquirrel/passes/decomposer/cnot_decomposer.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ class CNOTDecomposer(Decomposer):
[Quantum Gates by G.E. Crooks (2024), Section 7.5](https://threeplusone.com/pubs/on_gates.pdf).
"""

def decompose(self, gate: Gate) -> list[Gate]:
def decompose(self, instruction: Gate) -> list[Gate]:
"""Decomposes a controlled two-qubit gate into a sequence of (at most 2) CNOT gates and
single-qubit gates. It decomposes the CR, CRk, and CZ controlled two-qubit gates.

Expand All @@ -35,13 +35,17 @@ def decompose(self, gate: Gate) -> list[Gate]:
or the [SWAP2CZDecomposer][opensquirrel.passes.decomposer.swap2cz_decomposer.SWAP2CZDecomposer].

Args:
gate (Gate): Two-qubit controlled gate to decompose.
instruction (Instruction): Two-qubit controlled gate to decompose.

Returns:
A sequence of (at most 2) CNOT gates and single-qubit gates.

"""
if not isinstance(gate, TwoQubitGate) or not gate.controlled:
if not isinstance(instruction, TwoQubitGate):
return [instruction]
gate = instruction

if not gate.controlled:
return [gate]

control_qubit = gate.qubit0
Expand Down
10 changes: 6 additions & 4 deletions opensquirrel/passes/decomposer/cz_decomposer.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ class CZDecomposer(Decomposer):
Source of the math: https://threeplusone.com/pubs/on_gates.pdf, chapter 7.5 "ABC decomposition"
"""

def decompose(self, gate: Gate) -> list[Gate]:
def decompose(self, instruction: Gate) -> list[Gate]:
"""Decomposes a controlled two-qubit gate into a sequence of (at most 2) CZ gates and
single-qubit gates. It decomposes the CR, CRk, and CNOT controlled two-qubit gates.

Expand All @@ -37,14 +37,16 @@ def decompose(self, gate: Gate) -> list[Gate]:
or the [SWAP2CNOTDecomposer][opensquirrel.passes.decomposer.swap2cnot_decomposer.SWAP2CNOTDecomposer].

Args:
gate (Gate): Two-qubit controlled gate to decompose.
instruction (Instruction): Two-qubit controlled gate to decompose.

Returns:
A sequence of (at most 2) CZ gates and single-qubit gates.

"""
if not isinstance(gate, TwoQubitGate):
return [gate]
if not isinstance(instruction, TwoQubitGate):
return [instruction]

gate = instruction

if not gate.controlled:
# Do nothing:
Expand Down
30 changes: 30 additions & 0 deletions opensquirrel/passes/decomposer/gate_replacer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
from collections.abc import Callable

from opensquirrel.default_instructions import is_anonymous_gate
from opensquirrel.ir import IR, Gate
from opensquirrel.passes.decomposer.general_decomposer import Decomposer, decompose


class _GenericReplacer(Decomposer):
def __init__(self, gate_type: type[Gate], replacement_gates_function: Callable[..., list[Gate]]) -> None:
self.gate_type = gate_type
self.replacement_gates_function = replacement_gates_function

def decompose(self, instruction: Gate) -> list[Gate]:
if is_anonymous_gate(instruction.name) or type(instruction) is not self.gate_type:
return [instruction]
return self.replacement_gates_function(*instruction.qubit_operands, *instruction.arguments)


def replace(ir: IR, gate: type[Gate], replacement_gates_function: Callable[..., list[Gate]]) -> None:
"""Replaces all occurrences of a specific gate in the circuit IR with a given sequence of other
gates.

Args:
ir (IR): The circuit IR to modify.
gate (type[Gate]): Gate to replace.
replacement_gates_function (Callable[..., list[Gate]]): Function that returns a list of replacement gates.

"""
generic_replacer = _GenericReplacer(gate, replacement_gates_function)
decompose(ir, generic_replacer)
Loading
Loading