qutip/qutip-qip

View on GitHub
tests/test_device.py

Summary

Maintainability
C
7 hrs
Test Coverage
from itertools import product
from functools import reduce
from operator import mul

import warnings
import numpy as np
import pytest

import qutip
from qutip_qip.circuit import QubitCircuit
from qutip_qip.operations import Gate, gate_sequence_product, RZX
from qutip_qip.device import (DispersiveCavityQED, LinearSpinChain,
                                CircularSpinChain, SCQubits)

from packaging.version import parse as parse_version
from qutip import Options

_tol = 3.e-2

_x = Gate("X", targets=[0])
_z = Gate("Z", targets=[0])
_y = Gate("Y", targets=[0])
_snot = Gate("SNOT", targets=[0])
_rz = Gate("RZ", targets=[0], arg_value=np.pi/2, arg_label=r"\pi/2")
_rx = Gate("RX", targets=[0], arg_value=np.pi/2, arg_label=r"\pi/2")
_ry = Gate("RY", targets=[0], arg_value=np.pi/2, arg_label=r"\pi/2")
_iswap = Gate("ISWAP", targets=[0, 1])
_cnot = Gate("CNOT", targets=[0], controls=[1])
_sqrt_iswap = Gate("SQRTISWAP", targets=[0, 1])


single_gate_tests = [
    pytest.param(2, [_z], id="Z"),
    pytest.param(2, [_x], id="X"),
    pytest.param(2, [_y], id="Y"),
    pytest.param(2, [_snot], id="SNOT"),
    pytest.param(2, [_rz], id="RZ"),
    pytest.param(2, [_rx], id="RX"),
    pytest.param(2, [_ry], id="RY"),
    pytest.param(2, [_iswap], id="ISWAP"),
    pytest.param(2, [_sqrt_iswap], id="SQRTISWAP", marks=pytest.mark.skip),
    pytest.param(2, [_cnot], id="CNOT"),
]


def _ket_expaned_dims(qubit_state, expanded_dims):
    all_qubit_basis = list(product([0, 1], repeat=len(expanded_dims)))
    old_dims = qubit_state.dims[0]
    expanded_qubit_state = np.zeros(
        reduce(mul, expanded_dims, 1), dtype=np.complex128)
    for basis_state in all_qubit_basis:
        old_ind = np.ravel_multi_index(basis_state, old_dims)
        new_ind = np.ravel_multi_index(basis_state, expanded_dims)
        expanded_qubit_state[new_ind] = qubit_state[old_ind, 0]
    expanded_qubit_state.reshape((reduce(mul, expanded_dims, 1), 1))
    return qutip.Qobj(
        expanded_qubit_state, dims=[expanded_dims, [1]*len(expanded_dims)])


device_lists_analytic = [
    pytest.param(DispersiveCavityQED, {"g":0.1}, id = "DispersiveCavityQED"),
    pytest.param(LinearSpinChain, {}, id = "LinearSpinChain"),
    pytest.param(CircularSpinChain, {}, id = "CircularSpinChain"),
]

device_lists_numeric = device_lists_analytic + [
    # Does not support global phase
    pytest.param(SCQubits, {}, id = "SCQubits"),
]


@pytest.mark.parametrize(("num_qubits", "gates"), single_gate_tests)
@pytest.mark.parametrize(("device_class", "kwargs"), device_lists_analytic)
def test_device_against_gate_sequence(
    num_qubits, gates, device_class, kwargs):
    circuit = QubitCircuit(num_qubits)
    for gate in gates:
        circuit.add_gate(gate)
    U_ideal = circuit.compute_unitary()

    device = device_class(num_qubits)
    U_physical = gate_sequence_product(device.run(circuit))
    assert (U_ideal - U_physical).norm() < _tol


@pytest.mark.parametrize(("num_qubits", "gates"), single_gate_tests)
@pytest.mark.parametrize(("device_class", "kwargs"), device_lists_analytic)
def test_analytical_evolution(num_qubits, gates, device_class, kwargs):
    circuit = QubitCircuit(num_qubits)
    for gate in gates:
        circuit.add_gate(gate)
    state = qutip.rand_ket(2**num_qubits)
    state.dims = [[2]*num_qubits, [1]*num_qubits]
    ideal = circuit.run(state)
    device = device_class(num_qubits)
    operators = device.run_state(init_state=state, qc=circuit, analytical=True)
    result = gate_sequence_product(operators)
    assert abs(qutip.metrics.fidelity(result, ideal) - 1) < _tol


@pytest.mark.filterwarnings("ignore:Not in the dispersive regime")
@pytest.mark.parametrize(("num_qubits", "gates"), single_gate_tests)
@pytest.mark.parametrize(("device_class", "kwargs"), device_lists_numeric)
def test_numerical_evolution(num_qubits, gates, device_class, kwargs):
    _test_numerical_evolution_helper(num_qubits, gates, device_class, kwargs)


# Test for RZX gate, only available on SCQubits.
_rzx = RZX([0, 1], arg_value=np.pi/2)
@pytest.mark.parametrize(
    ("num_qubits", "gates", "device_class", "kwargs"), 
    [pytest.param(2, [_rzx], SCQubits, {}, id="RZX-SCQubits")]
    )
def test_numerical_evolution_zx(num_qubits, gates, device_class, kwargs):
    _test_numerical_evolution_helper(num_qubits, gates, device_class, kwargs)


def _test_numerical_evolution_helper(num_qubits, gates, device_class, kwargs):
    num_qubits = 2
    circuit = QubitCircuit(num_qubits)
    for gate in gates:
        circuit.add_gate(gate)
    device = device_class(num_qubits, **kwargs)
    device.load_circuit(circuit)

    state = qutip.rand_ket(2**num_qubits)
    state.dims = [[2]*num_qubits, [1]*num_qubits]
    target = circuit.run(state)
    if isinstance(device, DispersiveCavityQED):
        num_ancilla = len(device.dims)-num_qubits
        ancilla_indices = slice(0, num_ancilla)
        extra = qutip.basis(device.dims[ancilla_indices], [0]*num_ancilla)
        init_state = qutip.tensor(extra, state)
    elif isinstance(device, SCQubits):
        # expand to 3-level represetnation
        init_state = _ket_expaned_dims(state, device.dims)
    else:
        init_state = state
    options = Options(store_final_state=True, nsteps=50_000)
    result = device.run_state(init_state=init_state,
                              analytical=False,
                              options=options)
    numerical_result = result.final_state
    if isinstance(device, DispersiveCavityQED):
        target = qutip.tensor(extra, target)
    elif isinstance(device, SCQubits):
        target = _ket_expaned_dims(target, device.dims)
    assert _tol > abs(1 - qutip.metrics.fidelity(numerical_result, target))


circuit = QubitCircuit(3)
circuit.add_gate("RX", targets=[0], arg_value=np.pi/2)
circuit.add_gate("RZ", targets=[2], arg_value=np.pi)
circuit.add_gate("CNOT", targets=[0], controls=[1])
circuit.add_gate("ISWAP", targets=[2, 1])
circuit.add_gate("Y", targets=[2])
circuit.add_gate("Z", targets=[0])
circuit.add_gate("IDLE", targets=[1], arg_value=1.)
circuit.add_gate("CNOT", targets=[0], controls=[2])
circuit.add_gate("Z", targets=[1])
circuit.add_gate("X", targets=[1])

from copy import deepcopy
circuit2 = deepcopy(circuit)
circuit2.add_gate("SQRTISWAP", targets=[0, 2])  # supported only by SpinChain


@pytest.mark.filterwarnings("ignore:Not in the dispersive regime")
@pytest.mark.parametrize(("circuit", "device_class", "kwargs"), [
    pytest.param(circuit, DispersiveCavityQED, {"g":0.1}, id = "DispersiveCavityQED"),
    pytest.param(circuit2, LinearSpinChain, {}, id = "LinearSpinChain"),
    pytest.param(circuit2, CircularSpinChain, {}, id = "CircularSpinChain"),
    # The length of circuit is limited for SCQubits due to leakage
    pytest.param(circuit, SCQubits, {"omega_single":[0.02]*3}, id = "SCQubits"),
])
@pytest.mark.parametrize(("schedule_mode"), ["ASAP", "ALAP", None])
def test_numerical_circuit(circuit, device_class, kwargs, schedule_mode):
    num_qubits = circuit.N
    with warnings.catch_warnings(record=True):
        device = device_class(circuit.N, **kwargs)
    device.load_circuit(circuit, schedule_mode=schedule_mode)

    state = qutip.rand_ket(2**num_qubits)
    state.dims = [[2]*num_qubits, [1]*num_qubits]
    target = circuit.run(state)
    if isinstance(device, DispersiveCavityQED):
        num_ancilla = len(device.dims)-num_qubits
        ancilla_indices = slice(0, num_ancilla)
        extra = qutip.basis(device.dims[ancilla_indices], [0]*num_ancilla)
        init_state = qutip.tensor(extra, state)
    elif isinstance(device, SCQubits):
        # expand to 3-level represetnation
        init_state = _ket_expaned_dims(state, device.dims)
    else:
        init_state = state
    options = Options(store_final_state=True, nsteps=50_000)
    result = device.run_state(init_state=init_state,
                              analytical=False,
                              options=options)
    if isinstance(device, DispersiveCavityQED):
        target = qutip.tensor(extra, target)
    elif isinstance(device, SCQubits):
        target = _ket_expaned_dims(target, device.dims)
    assert _tol > abs(1 - qutip.metrics.fidelity(result.final_state, target))


@pytest.mark.parametrize(
    "processor_class",
    [DispersiveCavityQED, LinearSpinChain, CircularSpinChain, SCQubits])
def test_pulse_plotting(processor_class):
    plt = pytest.importorskip("matplotlib.pyplot")
    qc = QubitCircuit(3)
    qc.add_gate("CNOT", 1, 0)
    qc.add_gate("X", 1)

    processor = processor_class(3)
    processor.load_circuit(qc)
    fig, ax = processor.plot_pulses()
    plt.close(fig)


def _compute_propagator(processor, circuit):
    qevo, _ = processor.get_qobjevo(noisy=True)
    if parse_version(qutip.__version__) < parse_version("5.dev"):
        qevo = qevo.to_list()
        result = qutip.propagator(qevo, t=processor.get_full_tlist())[-1]
    else:
        result = qutip.propagator(
            qevo,
            t=processor.get_full_tlist(),
            parallel=False
            )[-1]
    return result


def test_scqubits_single_qubit_gate():
    # Check the accuracy of the single-qubit gate for SCQubits.
    circuit = QubitCircuit(1)
    circuit.add_gate("X", targets=[0])
    processor = SCQubits(1, omega_single=0.04)
    processor.load_circuit(circuit)
    U = _compute_propagator(processor, circuit)
    fid = qutip.average_gate_fidelity(
        qutip.Qobj(U.full()[:2, :2]), qutip.sigmax()
    )
    assert pytest.approx(fid, rel=1.0e-6) == 1


def test_idling_accuracy():
    """
    Check if the switch-on and off of the pulse is implemented correctly.
    More sampling points may be needed to suppress the interpolated pulse
    during the idling period.
    """
    processor = SCQubits(2, omega_single=0.04)
    circuit = QubitCircuit(1)
    circuit.add_gate("X", targets=[0])
    processor.load_circuit(circuit)
    U = _compute_propagator(processor, circuit)
    error_1_gate = 1 - qutip.average_gate_fidelity(
        qutip.Qobj(U.full()[:2, :2]), qutip.sigmax()
    )

    circuit = QubitCircuit(2)
    circuit.add_gate("X", targets=[0])
    circuit.add_gate("X", targets=[1])
    # Turning off scheduling to keep the idling.
    processor.load_circuit(circuit, schedule_mode=False)
    U = _compute_propagator(processor, circuit)
    error_2_gate = 1 - qutip.average_gate_fidelity(
        qutip.Qobj(U.full()[:2, :2]), qutip.sigmax()
    )

    assert error_2_gate < 2 * error_1_gate