Skip to content

Commit

Permalink
Completely migrate to pytest (TeamGraphix#134)
Browse files Browse the repository at this point in the history
* 🚨 Remove self.assertRaises

* 🚨 Remove self.assertTrue

* 🎨 Resolve redundant assert

* 🚨 Remove self.assertEqual

* 🐛 Change class name

Make pytest aware of them

* 🚧 Partially remove unittest

* 🎨 Update test_linalg.py

* 🎨 Update test_tnsim.py

* ➕ Add pytest-mock

* 🎨 Use pytest-mock

* 🎨 Update test_gflow.py

* ♻️ Refactor rndom_circuit.py

Now it's stateless

* ✅ Add rng fixture

* 🐛 Fix random_circuit.py

* 🎨 Resolve rng global state problem

* 🚧 Update test_pattern.py

Not passing tests

* ♻️ Refactor test_clifford.py

* ♻️ Refactor test_density_matrix.py

* ♻️ Refactor test_generator.py

* ♻️ Refactor test_extraction.py

* ♻️ Refactor test_gflow.py

* ♻️ Refactor test_graphsim.py

* ♻️ Refactor test_kraus.py

* 🐛 Add __future__

* 🚨 Move to TYPE_CHECKING block

* ♻️ Refactor test_linalg.py

* ♻️ Refactor test_noisy_density_matrix.py

* ♻️ Refactor test_pattern.py

* ♻️ Refactor test_pauli.py

* ♻️ Refactor test_random_utilities.py

* ♻️ Refactor test_runner.py

* ♻️ Refactor test_statevec_backend.py

* ♻️ Refactor test_tnsim.py

* ♻️ Refactor test_transpiler.py

* 💡 Resolve too long line

* ➖ Remove parameterized

* ♻️ Remove np.testing.assert_equal

* ♻️ Remove np.testing.assert_almost_equal

* ♻️ Remove np.testing.assert_allclose

* 🚨 Apply isort

* 💩 Skip broken tests

See issue TeamGraphix#130

* 🐛 Add version switcher

* 🚨 Apply isort

* 🩹 Specify RNG type

* 🎨 Collapse for

* ✨ Add fx_bg

* 🎨 Collapse random repeat

* 🎨 Drop unused imports
  • Loading branch information
EarlMilktea authored May 8, 2024
1 parent 1a7c64a commit e9ccc15
Show file tree
Hide file tree
Showing 19 changed files with 1,972 additions and 1,833 deletions.
2 changes: 1 addition & 1 deletion requirements-dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ isort==5.13.2

# Tests
pytest
parameterized
pytest-mock
tox

# Optional dependencies
Expand Down
14 changes: 14 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import pytest
from numpy.random import PCG64, Generator

SEED = 42


@pytest.fixture()
def fx_rng() -> Generator:
return Generator(PCG64(SEED))


@pytest.fixture()
def fx_bg() -> PCG64:
return PCG64(SEED)
136 changes: 57 additions & 79 deletions tests/random_circuit.py
Original file line number Diff line number Diff line change
@@ -1,58 +1,56 @@
from copy import deepcopy
from __future__ import annotations

import functools
from typing import TYPE_CHECKING

import numpy as np

from graphix.transpiler import Circuit

GLOBAL_SEED = None


def set_seed(seed):
global GLOBAL_SEED
GLOBAL_SEED = seed

if TYPE_CHECKING:
from collections.abc import Iterable, Iterator

def get_rng(seed=None):
if seed is not None:
return np.random.default_rng(seed)
elif seed is None and GLOBAL_SEED is not None:
return np.random.default_rng(GLOBAL_SEED)
else:
return np.random.default_rng()
from numpy.random import Generator


def first_rotation(circuit, nqubits, rng):
def first_rotation(circuit: Circuit, nqubits: int, rng: Generator) -> None:
for qubit in range(nqubits):
circuit.rx(qubit, rng.random())


def mid_rotation(circuit, nqubits, rng):
def mid_rotation(circuit: Circuit, nqubits: int, rng: Generator) -> None:
for qubit in range(nqubits):
circuit.rx(qubit, rng.random())
circuit.rz(qubit, rng.random())


def last_rotation(circuit, nqubits, rng):
def last_rotation(circuit: Circuit, nqubits: int, rng: Generator) -> None:
for qubit in range(nqubits):
circuit.rz(qubit, rng.random())


def entangler(circuit, pairs):
def entangler(circuit: Circuit, pairs: Iterable[tuple[int, int]]) -> None:
for a, b in pairs:
circuit.cnot(a, b)


def entangler_rzz(circuit, pairs, rng):
def entangler_rzz(circuit: Circuit, pairs: Iterable[tuple[int, int]], rng: Generator) -> None:
for a, b in pairs:
circuit.rzz(a, b, rng.random())


def generate_gate(nqubits, depth, pairs, use_rzz=False, seed=None):
rng = get_rng(seed)
def generate_gate(
nqubits: int,
depth: int,
pairs: Iterable[tuple[int, int]],
rng: Generator,
*,
use_rzz: bool = False,
) -> Circuit:
circuit = Circuit(nqubits)
first_rotation(circuit, nqubits, rng)
entangler(circuit, pairs)
for k in range(depth - 1):
for _ in range(depth - 1):
mid_rotation(circuit, nqubits, rng)
if use_rzz:
entangler_rzz(circuit, pairs, rng)
Expand All @@ -62,36 +60,42 @@ def generate_gate(nqubits, depth, pairs, use_rzz=False, seed=None):
return circuit


def genpair(n_qubits, count, rng):
pairs = []
for i in range(count):
choice = [j for j in range(n_qubits)]
x = rng.choice(choice)
choice.pop(x)
y = rng.choice(choice)
pairs.append((x, y))
return pairs


def gentriplet(n_qubits, count, rng):
triplets = []
for i in range(count):
choice = [j for j in range(n_qubits)]
x = rng.choice(choice)
choice.pop(x)
y = rng.choice(choice)
locy = np.where(y == np.array(deepcopy(choice)))[0][0]
choice.pop(locy)
z = rng.choice(choice)
triplets.append((x, y, z))
return triplets


def get_rand_circuit(nqubits, depth, use_rzz=False, use_ccx=False, seed=None):
rng = get_rng(seed)
def genpair(n_qubits: int, count: int, rng: Generator) -> Iterator[tuple[int, int]]:
choice = list(range(n_qubits))
for _ in range(count):
rng.shuffle(choice)
x, y = choice[:2]
yield (x, y)


def gentriplet(n_qubits: int, count: int, rng: Generator) -> Iterator[tuple[int, int, int]]:
choice = list(range(n_qubits))
for _ in range(count):
rng.shuffle(choice)
x, y, z = choice[:3]
yield (x, y, z)


def get_rand_circuit(
nqubits: int,
depth: int,
rng: Generator,
*,
use_rzz: bool = False,
use_ccx: bool = False,
) -> Circuit:
circuit = Circuit(nqubits)
gate_choice = [0, 1, 2, 3, 4, 5, 6, 7]
for i in range(depth):
gate_choice = (
functools.partial(circuit.ry, angle=np.pi / 4),
functools.partial(circuit.rz, angle=-np.pi / 4),
functools.partial(circuit.rx, angle=-np.pi / 4),
circuit.h,
circuit.s,
circuit.x,
circuit.z,
circuit.y,
)
for _ in range(depth):
for j, k in genpair(nqubits, 2, rng):
circuit.cnot(j, k)
if use_rzz:
Expand All @@ -103,31 +107,5 @@ def get_rand_circuit(nqubits, depth, use_rzz=False, use_ccx=False, seed=None):
for j, k in genpair(nqubits, 4, rng):
circuit.swap(j, k)
for j in range(nqubits):
k = rng.choice(gate_choice)
if k == 0:
circuit.ry(j, np.pi / 4)
pass
elif k == 1:
circuit.rz(j, -np.pi / 4)
pass
elif k == 2:
circuit.rx(j, -np.pi / 4)
pass
elif k == 3: # H
circuit.h(j)
pass
elif k == 4: # S
circuit.s(j)
pass
elif k == 5: # X
circuit.x(j)
pass
elif k == 6: # Z
circuit.z(j)
pass
elif k == 7: # Y
circuit.y(j)
pass
else:
pass
rng.choice(gate_choice)(j)
return circuit
93 changes: 45 additions & 48 deletions tests/test_clifford.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
import unittest
from __future__ import annotations

import itertools

import numpy as np
import numpy.typing as npt
import pytest

from graphix.clifford import CLIFFORD, CLIFFORD_CONJ, CLIFFORD_HSZ_DECOMPOSITION, CLIFFORD_MEASURE, CLIFFORD_MUL


class TestClifford(unittest.TestCase):
class TestClifford:
@staticmethod
def classify_pauli(arr):
def classify_pauli(arr: npt.NDArray) -> tuple[int, int]:
"""returns the index of Pauli gate with sign for a given 2x2 matrix.
Compare the gate arr with Pauli gates
Expand All @@ -25,21 +29,21 @@ def classify_pauli(arr):

if np.allclose(CLIFFORD[1], arr):
return (0, 0)
elif np.allclose(-1 * CLIFFORD[1], arr):
if np.allclose(-1 * CLIFFORD[1], arr):
return (0, 1)
elif np.allclose(CLIFFORD[2], arr):
if np.allclose(CLIFFORD[2], arr):
return (1, 0)
elif np.allclose(-1 * CLIFFORD[2], arr):
if np.allclose(-1 * CLIFFORD[2], arr):
return (1, 1)
elif np.allclose(CLIFFORD[3], arr):
if np.allclose(CLIFFORD[3], arr):
return (2, 0)
elif np.allclose(-1 * CLIFFORD[3], arr):
if np.allclose(-1 * CLIFFORD[3], arr):
return (2, 1)
else:
raise ValueError("No Pauli found")
msg = "No Pauli found"
raise ValueError(msg)

@staticmethod
def clifford_index(g):
def clifford_index(g: npt.NDArray) -> int:
"""returns the index of Clifford for a given 2x2 matrix.
Compare the gate g with all Clifford gates (up to global phase)
Expand All @@ -55,43 +59,36 @@ def clifford_index(g):
"""

for i in range(24):
ci = CLIFFORD[i]
# normalise global phase
if CLIFFORD[i][0, 0] == 0:
norm = g[0, 1] / CLIFFORD[i][0, 1]
else:
norm = g[0, 0] / CLIFFORD[i][0, 0]
norm = g[0, 1] / ci[0, 1] if ci[0, 0] == 0 else g[0, 0] / ci[0, 0]
# compare
if np.allclose(CLIFFORD[i] * norm, g):
if np.allclose(ci * norm, g):
return i
raise ValueError("No Clifford found")

def test_measure(self):
for i in range(24):
for j in range(3):
conj = CLIFFORD[i].conjugate().T
pauli = CLIFFORD[j + 1]
arr = np.matmul(np.matmul(conj, pauli), CLIFFORD[i])
res = self.classify_pauli(arr)
assert res == CLIFFORD_MEASURE[i][j]

def test_multiplication(self):
for i in range(24):
for j in range(24):
arr = np.matmul(CLIFFORD[i], CLIFFORD[j])
assert CLIFFORD_MUL[i, j] == self.clifford_index(arr)

def test_conjugation(self):
for i in range(24):
arr = CLIFFORD[i].conjugate().T
assert CLIFFORD_CONJ[i] == self.clifford_index(arr)

def test_decomposition(self):
for i in range(1, 24):
op = np.eye(2)
for j in CLIFFORD_HSZ_DECOMPOSITION[i]:
op = op @ CLIFFORD[j]
assert i == self.clifford_index(op)


if __name__ == "__main__":
unittest.main()
msg = "No Clifford found"
raise ValueError(msg)

@pytest.mark.parametrize(("i", "j"), itertools.product(range(24), range(3)))
def test_measure(self, i: int, j: int) -> None:
conj = CLIFFORD[i].conjugate().T
pauli = CLIFFORD[j + 1]
arr = np.matmul(np.matmul(conj, pauli), CLIFFORD[i])
res = self.classify_pauli(arr)
assert res == CLIFFORD_MEASURE[i][j]

@pytest.mark.parametrize(("i", "j"), itertools.product(range(24), range(24)))
def test_multiplication(self, i: int, j: int) -> None:
arr = np.matmul(CLIFFORD[i], CLIFFORD[j])
assert CLIFFORD_MUL[i, j] == self.clifford_index(arr)

@pytest.mark.parametrize("i", range(24))
def test_conjugation(self, i: int) -> None:
arr = CLIFFORD[i].conjugate().T
assert CLIFFORD_CONJ[i] == self.clifford_index(arr)

@pytest.mark.parametrize("i", range(1, 24))
def test_decomposition(self, i: int) -> None:
op = np.eye(2)
for j in CLIFFORD_HSZ_DECOMPOSITION[i]:
op = op @ CLIFFORD[j]
assert i == self.clifford_index(op)
Loading

0 comments on commit e9ccc15

Please sign in to comment.