Skip to content
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
1 change: 1 addition & 0 deletions .github/workflows/typecheck.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ jobs:
- run: |
python -m pip install --upgrade pip
pip install -e .[dev,extra]
pip install --no-deps -e .[no-deps]

- run: mypy

Expand Down
10 changes: 5 additions & 5 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## Unreleased

### Added

- #385
- Introduced `graphix.flow.core.XZCorrections.check_well_formed` which verifies the correctness of an XZ-corrections instance and raises an exception if incorrect.
- Added XZ-correction exceptions to module `graphix.flow.core.exceptions`.
Expand All @@ -18,17 +19,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Introduced new module `graphix.flow.exceptions` grouping flow exceptions.
- Introduced new methods `graphix.flow.core.PauliFlow.get_measurement_label` and `graphix.flow.core.GFlow.get_measurement_label` which return the measurement label of a given node following same criteria employed in the flow-finding algorithms.

- #374:
- #379: Added a new instruction `CZ` which can be added as a circuit gate using `circuit.cz`.
- Introduced new method `graphix.opengraph.OpenGraph.is_equal_structurally` which compares the underlying structure of two open graphs.
- Added new method `isclose` to `graphix.fundamentals.AbstractMeasurement` which defaults to `==` comparison.

- #383: Simulators are now parameterized by `PrepareMethod` (which
defaults to `DefaultPrepareMethod`) to customize how `N` commands are
handled, and the class `BaseN` can be used as a base class for
custom preparation commands.
- #383: Simulators are now parameterized by `PrepareMethod` (which defaults to `DefaultPrepareMethod`) to customize how `N` commands are handled, and the class `BaseN` can be used as a base class for custom preparation commands.

### Fixed

- #379: Removed unnecessary `meas_index` from API for rotation instructions `RZ`, `RY` and `RX`.

- #347: Adapted existing method `graphix.opengraph.OpenGraph.isclose` to the new API introduced in #358.

- #349, #362: Patterns transpiled from circuits always have causal flow.
Expand Down
17 changes: 10 additions & 7 deletions graphix/instruction.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ class InstructionKind(Enum):
RZZ = enum.auto()
CNOT = enum.auto()
SWAP = enum.auto()
CZ = enum.auto()
H = enum.auto()
S = enum.auto()
X = enum.auto()
Expand Down Expand Up @@ -80,9 +81,6 @@ class RZZ(_KindChecker, DataclassReprMixin):
target: int
control: int
angle: ExpressionOrFloat = field(metadata={"repr": repr_angle})
# FIXME: Remove `| None` from `meas_index`
# - `None` makes codes messy/type-unsafe
meas_index: int | None = None
kind: ClassVar[Literal[InstructionKind.RZZ]] = field(default=InstructionKind.RZZ, init=False)


Expand All @@ -95,6 +93,14 @@ class CNOT(_KindChecker, DataclassReprMixin):
kind: ClassVar[Literal[InstructionKind.CNOT]] = field(default=InstructionKind.CNOT, init=False)


@dataclass(repr=False)
class CZ(_KindChecker, DataclassReprMixin):
"""CZ circuit instruction."""

targets: tuple[int, int]
kind: ClassVar[Literal[InstructionKind.CZ]] = field(default=InstructionKind.CZ, init=False)


@dataclass(repr=False)
class SWAP(_KindChecker, DataclassReprMixin):
"""SWAP circuit instruction."""
Expand Down Expand Up @@ -167,7 +173,6 @@ class RX(_KindChecker, DataclassReprMixin):

target: int
angle: ExpressionOrFloat = field(metadata={"repr": repr_angle})
meas_index: int | None = None
kind: ClassVar[Literal[InstructionKind.RX]] = field(default=InstructionKind.RX, init=False)


Expand All @@ -177,7 +182,6 @@ class RY(_KindChecker, DataclassReprMixin):

target: int
angle: ExpressionOrFloat = field(metadata={"repr": repr_angle})
meas_index: int | None = None
kind: ClassVar[Literal[InstructionKind.RY]] = field(default=InstructionKind.RY, init=False)


Expand All @@ -187,7 +191,6 @@ class RZ(_KindChecker, DataclassReprMixin):

target: int
angle: ExpressionOrFloat = field(metadata={"repr": repr_angle})
meas_index: int | None = None
kind: ClassVar[Literal[InstructionKind.RZ]] = field(default=InstructionKind.RZ, init=False)


Expand All @@ -209,4 +212,4 @@ class _ZC(_KindChecker):
kind: ClassVar[Literal[InstructionKind._ZC]] = field(default=InstructionKind._ZC, init=False)


Instruction = CCX | RZZ | CNOT | SWAP | H | S | X | Y | Z | I | M | RX | RY | RZ | _XC | _ZC
Instruction = CCX | RZZ | CNOT | SWAP | CZ | H | S | X | Y | Z | I | M | RX | RY | RZ | _XC | _ZC
2 changes: 2 additions & 0 deletions graphix/qasm3_exporter.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@ def instruction_to_qasm3(instruction: Instruction) -> str:
return qasm3_gate_call("cx", [qasm3_qubit(instruction.control), qasm3_qubit(instruction.target)])
if instruction.kind == InstructionKind.SWAP:
return qasm3_gate_call("swap", [qasm3_qubit(instruction.targets[i]) for i in (0, 1)])
if instruction.kind == InstructionKind.CZ:
return qasm3_gate_call("cz", [qasm3_qubit(instruction.targets[i]) for i in (0, 1)])
if instruction.kind == InstructionKind.RZZ:
angle = angle_to_qasm3(instruction.angle)
return qasm3_gate_call(
Expand Down
10 changes: 8 additions & 2 deletions graphix/random_objects.py
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,7 @@ def rand_circuit(
depth: int,
rng: Generator | None = None,
*,
use_cz: bool = True,
use_rzz: bool = False,
use_ccx: bool = False,
parameters: Iterable[Parameter] | None = None,
Expand All @@ -381,10 +382,12 @@ def rand_circuit(
Number of alternating entangling and single-qubit layers.
rng : numpy.random.Generator, optional
Random number generator. A default generator is created if ``None``.
use_cz : bool, optional
If ``True`` add CZ gates in each layer (default: ``True``).
use_rzz : bool, optional
If ``True`` add :math:`R_{ZZ}` gates in each layer.
If ``True`` add :math:`R_{ZZ}` gates in each layer (default: ``False``).
use_ccx : bool, optional
If ``True`` add CCX gates in each layer.
If ``True`` add CCX gates in each layer (default: ``False``).
parameters : Iterable[Parameter], optional
Parameters used for randomly chosen rotation gates.

Expand Down Expand Up @@ -414,6 +417,9 @@ def rand_circuit(
for _ in range(depth):
for j, k in _genpair(nqubits, 2, rng):
circuit.cnot(j, k)
if use_cz:
for j, k in _genpair(nqubits, 2, rng):
circuit.cz(j, k)
if use_rzz:
for j, k in _genpair(nqubits, 2, rng):
circuit.rzz(j, k, np.pi / 4)
Expand Down
44 changes: 43 additions & 1 deletion graphix/transpiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,8 @@ def add(self, instr: Instruction) -> None:
self.cnot(instr.control, instr.target)
elif instr.kind == InstructionKind.SWAP:
self.swap(instr.targets[0], instr.targets[1])
elif instr.kind == InstructionKind.CZ:
self.cz(instr.targets[0], instr.targets[1])
elif instr.kind == InstructionKind.H:
self.h(instr.target)
elif instr.kind == InstructionKind.S:
Expand Down Expand Up @@ -174,6 +176,21 @@ def swap(self, qubit1: int, qubit2: int) -> None:
assert qubit1 != qubit2
self.instruction.append(instruction.SWAP(targets=(qubit1, qubit2)))

def cz(self, qubit1: int, qubit2: int) -> None:
"""Apply a CNOT gate.

Parameters
----------
qubit1 : int
control qubit
qubit2 : int
target qubit
"""
assert qubit1 in self.active_qubits
assert qubit2 in self.active_qubits
assert qubit1 != qubit2
self.instruction.append(instruction.CZ(targets=(qubit1, qubit2)))

def h(self, qubit: int) -> None:
"""Apply a Hadamard gate.

Expand Down Expand Up @@ -351,7 +368,12 @@ def transpile(self) -> TranspileResult:
pattern = Pattern(input_nodes=list(range(self.width)))
classical_outputs = []
for instr in _transpile_rzz(self.instruction):
if instr.kind == instruction.InstructionKind.CNOT:
if instr.kind == instruction.InstructionKind.CZ:
target0 = _check_target(out, instr.targets[0])
target1 = _check_target(out, instr.targets[1])
seq = self._cz_command(target0, target1)
pattern.extend(seq)
elif instr.kind == instruction.InstructionKind.CNOT:
ancilla = [n_node, n_node + 1]
control = _check_target(out, instr.control)
target = _check_target(out, instr.target)
Expand Down Expand Up @@ -485,6 +507,24 @@ def _cnot_command(
)
return control_node, ancilla[1], seq

@classmethod
def _cz_command(cls, target_1: int, target_2: int) -> list[Command]:
"""MBQC commands for CZ gate.

Parameters
----------
target_1 : int
target node on graph
target_2 : int
other target node on graph

Returns
-------
commands : list
list of MBQC commands
"""
return [E(nodes=(target_1, target_2))]

@classmethod
def _m_command(cls, input_node: int, plane: Plane, angle: Angle) -> list[Command]:
"""MBQC commands for measuring qubit.
Expand Down Expand Up @@ -901,6 +941,8 @@ def simulate_statevector(
state.cnot((instr.control, instr.target))
elif instr.kind == instruction.InstructionKind.SWAP:
state.swap(instr.targets)
elif instr.kind == instruction.InstructionKind.CZ:
state.entangle(instr.targets)
elif instr.kind == instruction.InstructionKind.I:
pass
elif instr.kind == instruction.InstructionKind.S:
Expand Down
1 change: 1 addition & 0 deletions requirements-dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,5 @@ pytest-mpl
qiskit>=1.0
qiskit-aer

openqasm-parser>=3.1.0
graphix-qasm-parser
7 changes: 5 additions & 2 deletions tests/test_qasm3_exporter.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
from graphix.instruction import Instruction

try:
from graphix_qasm_parser import OpenQASMParser
from graphix_qasm_parser import OpenQASMParser # type: ignore[import-not-found, unused-ignore]
except ImportError:
pytestmark = pytest.mark.skip(reason="graphix-qasm-parser not installed")

Expand All @@ -42,7 +42,8 @@ def test_circuit_to_qasm3(fx_bg: PCG64, jumps: int) -> None:
rng = Generator(fx_bg.jumped(jumps))
nqubits = 5
depth = 4
check_round_trip(rand_circuit(nqubits, depth, rng))
# See https://github.com/TeamGraphix/graphix-qasm-parser/pull/5
check_round_trip(rand_circuit(nqubits, depth, rng, use_cz=False))


@pytest.mark.parametrize(
Expand All @@ -52,6 +53,8 @@ def test_circuit_to_qasm3(fx_bg: PCG64, jumps: int) -> None:
instruction.RZZ(target=0, control=1, angle=pi / 4),
instruction.CNOT(target=0, control=1),
instruction.SWAP(targets=(0, 1)),
# See https://github.com/TeamGraphix/graphix-qasm-parser/pull/5
# instruction.CZ(targets=(0, 1)),
instruction.H(target=0),
instruction.S(target=0),
instruction.X(target=0),
Expand Down
8 changes: 4 additions & 4 deletions tests/test_statevec_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ def test_clifford(self) -> None:
backend.apply_clifford(node=0, clifford=clifford)
np.testing.assert_allclose(vec.psi, backend.state.psi)

def test_deterministic_measure_one(self, fx_rng: Generator):
def test_deterministic_measure_one(self, fx_rng: Generator) -> None:
# plus state & zero state (default), but with tossed coins
for _ in range(10):
backend = StatevectorBackend()
Expand All @@ -112,7 +112,7 @@ def test_deterministic_measure_one(self, fx_rng: Generator):
result = backend.measure(node=node_to_measure, measurement=measurement)
assert result == expected_result

def test_deterministic_measure(self):
def test_deterministic_measure(self) -> None:
"""Entangle |+> state with N |0> states, the (XY,0) measurement yields the outcome 0 with probability 1."""
for _ in range(10):
# plus state (default)
Expand All @@ -130,7 +130,7 @@ def test_deterministic_measure(self):
assert result == 0
assert list(backend.node_index) == list(range(1, n_neighbors + 1))

def test_deterministic_measure_many(self):
def test_deterministic_measure_many(self) -> None:
"""Entangle |+> state with N |0> states, the (XY,0) measurement yields the outcome 0 with probability 1."""
for _ in range(10):
# plus state (default)
Expand Down Expand Up @@ -161,7 +161,7 @@ def test_deterministic_measure_many(self):

assert list(backend.node_index) == list(range(n_traps, n_neighbors + n_traps + n_whatever))

def test_deterministic_measure_with_coin(self, fx_rng: Generator):
def test_deterministic_measure_with_coin(self, fx_rng: Generator) -> None:
"""Entangle |+> state with N |0> states, the (XY,0) measurement yields the outcome 0 with probability 1.

We add coin toss to that.
Expand Down
12 changes: 12 additions & 0 deletions tests/test_tnsim.py
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,18 @@ def test_i(self, fx_rng: Generator) -> None:
value2 = tn_mbqc.expectation_value(random_op1, [0])
assert value1 == pytest.approx(value2)

def test_cz(self, fx_rng: Generator) -> None:
circuit = Circuit(2)
circuit.cz(0, 1)
pattern = circuit.transpile().pattern
pattern.standardize()
state = circuit.simulate_statevector().statevec
tn_mbqc = pattern.simulate_pattern(backend="tensornetwork", rng=fx_rng)
random_op2 = random_op(2, np.complex128, fx_rng)
value1 = state.expectation_value(random_op2, [0, 1])
value2 = tn_mbqc.expectation_value(random_op2, [0, 1])
assert value1 == pytest.approx(value2)

def test_cnot(self, fx_rng: Generator) -> None:
circuit = Circuit(2)
circuit.cnot(0, 1)
Expand Down
Loading