```from hypothesis import given
import hypothesis.strategies as st
import hypothesis.extra.numpy as hnp
import numpy as np
import pytest

import qsim.gate

def unitary(n):
valid_complex = st.complex_numbers(allow_infinity=False, allow_nan=False)
return (
hnp.arrays(complex, (n, n), valid_complex)
.map(lambda a: np.linalg.qr(a)[0])
.filter(lambda u: np.all(np.isfinite(u)))
)

n_qubits = st.shared(st.integers(min_value=1, max_value=6))
phases = st.floats(
min_value=0, max_value=2 * np.pi, allow_nan=False, allow_infinity=False
)
single_qubit_gates = unitary(2)
two_qubit_gates = unitary(4)
n_qubit_gates = n_qubits.map(lambda n: 2 ** n).flatmap(unitary)

@given(n_qubits, n_qubit_gates)
def test_n_qubits(n, gate):
assert qsim.gate.n_qubits(gate) == n

@given(n_qubit_gates)
def test_n_qubits_invalid(gate):
# Not a numpy array
with pytest.raises(ValueError):
qsim.gate.n_qubits(list(map(list, gate)))
# Not complex
with pytest.raises(ValueError):
qsim.gate.n_qubits(gate.real)
# Not square
with pytest.raises(ValueError):
qsim.gate.n_qubits(gate[:-2])
# Not size 2**n, n > 0
with pytest.raises(ValueError):
qsim.gate.n_qubits(gate[:-1, :-1])
# Not unitary
nonunitary_part = np.zeros_like(gate)
nonunitary_part[0, -1] = 1j
with pytest.raises(ValueError):
qsim.gate.n_qubits(gate + nonunitary_part)

@given(n_qubits, n_qubit_gates)
def test_controlled(n, gate):
nq = 1 << n
controlled_gate = qsim.gate.controlled(gate)
assert controlled_gate.shape[0] == 2 * nq
assert np.all(controlled_gate[:nq, :nq] == np.identity(nq))
assert np.all(controlled_gate[nq:, nq:] == gate)

@given(phases)
def test_phase_gate_inverse(phi):
assert np.allclose(
qsim.gate.phase_shift(phi) @ qsim.gate.phase_shift(-phi), np.identity(2)
)

@given(phases, st.integers())
def test_phase_gate_periodic(phi, n):
atol = np.finfo(complex).resolution * abs(n)
assert np.allclose(
qsim.gate.phase_shift(phi),
qsim.gate.phase_shift(phi + 2 * np.pi * n),
atol=atol,
)

@given(single_qubit_gates)
def test_id(gate):
assert np.all(qsim.gate.id @ gate == gate)
assert np.all(gate @ qsim.gate.id == gate)

def test_pauli_gates_are_involutary():
pauli_gates = [qsim.gate.x, qsim.gate.y, qsim.gate.z]
assert np.all(qsim.gate.x == qsim.gate.not_)
for gate in pauli_gates:
assert np.all(gate @ gate == qsim.gate.id)
assert np.all(-1j * qsim.gate.x @ qsim.gate.y @ qsim.gate.z == qsim.gate.id)

def test_sqrt_not():
assert np.all(qsim.gate.sqrt_not @ qsim.gate.sqrt_not == qsim.gate.not_)

def test_deutch():
assert np.allclose(qsim.gate.deutsch(np.pi / 2), qsim.gate.toffoli)

def test_swap():
assert np.all(qsim.gate.swap @ qsim.gate.swap == np.identity(4))
```