Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bring coverage to 100% #47

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
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: 1 addition & 1 deletion qiskit_addon_aqc_tensor/simulation/explicit_gradient.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ def preprocess_circuit_for_backtracking(
except KeyError as ex:
raise ValueError(
f"Expects a gate from the list of basis ones: "
f"'{_basis_gates()}', got '{operation.name}' instead."
f"{sorted(_basis_gates())}, got '{operation.name}' instead."
) from ex
action = action_generator(pname2index, operation, qubit_indices, settings)
if action is not None:
Expand Down
29 changes: 20 additions & 9 deletions qiskit_addon_aqc_tensor/simulation/quimb/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,14 +90,18 @@ class QuimbSimulator(TensorNetworkSimulationSettings):
#: Whether to display a progress bar while applying gates.
progbar: bool = False

def _construct_circuit(self, qc: QuantumCircuit, /, **kwargs):
def _construct_circuit(
self, qc: QuantumCircuit, /, *, out_state: np.ndarray | None = None, **kwargs
):
import qiskit_quimb

qc = qc.decompose(AnsatzBlock)
quimb_circuit_factory = self.quimb_circuit_factory
circ = quimb_circuit_factory(N=qc.num_qubits, **kwargs)
gates = qiskit_quimb.quimb_gates(qc)
circ.apply_gates(gates, progbar=self.progbar)
if out_state is not None:
out_state[:] = np.squeeze(circ.psi.to_dense())
return circ


Expand All @@ -111,8 +115,10 @@ def tensornetwork_from_circuit(
qc: QuantumCircuit,
settings: QuimbSimulator,
/,
*,
out_state: Optional[np.ndarray] = None,
) -> quimb.tensor.Circuit:
return settings._construct_circuit(qc)
return settings._construct_circuit(qc, out_state=out_state)


@dispatch
Expand Down Expand Up @@ -171,10 +177,7 @@ def apply_circuit_to_state(
Returns:
The new state.
"""
circuit = settings._construct_circuit(qc, psi0=circ0.psi)
if out_state is not None:
out_state[:] = circuit.psi.to_dense()
return circuit
return settings._construct_circuit(qc, out_state=out_state, psi0=circ0.psi)


class QiskitQuimbConversionContext:
Expand Down Expand Up @@ -257,10 +260,18 @@ def qiskit_ansatz_to_quimb(
quimb_gate_ = quimb_gate(op, qubits)
if quimb_gate_ is not None:
circ.apply_gate(quimb_gate_)
else:
else: # pragma: no cover
raise ValueError("A parameter in the circuit has an unexpected type.")
for j, _, _ in mapping:
if j == -1:
if j == -1: # pragma: no cover
# NOTE: There seems to be no obvious way to trigger this error.
# Even the following snippet results in the parameter being removed
# from the circuit.
#
# qc = QuantumCircuit(1)
# x = Parameter("x")
# qc.rx(x, 0)
# qc.data[0] = CircuitInstruction(RXGate(np.pi / 3), qubits=[0])
raise ValueError(
"Some parameter(s) in the given Qiskit circuit remain unused. "
"This use case is not currently supported by the Quimb conversion code."
Expand All @@ -278,7 +289,7 @@ def recover_parameters_from_quimb(
raise ValueError(
"The length of the mapping in the provided QiskitQuimbConversionContext "
"does not match the number of parametrized gates in the circuit "
f"({len(mapping)}) vs. ({len(quimb_parametrized_gates)})."
f"({len(mapping)} vs. {len(quimb_parametrized_gates)})."
)
# `(y - b) / m` is the inversion of the parameter expression, which we
# assumed above to be in the form mx + b.
Expand Down
18 changes: 0 additions & 18 deletions test/simulation/aer/test_aer_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@

import numpy as np
import pytest
from qiskit import QuantumCircuit
from qiskit.circuit.library import CXGate, XGate
from qiskit.providers.basic_provider import BasicSimulator

Expand All @@ -32,23 +31,6 @@
pytestmark = pytest.mark.skipif(skip_aer_tests, reason="qiskit-aer is not installed")


@pytest.fixture
def bell_qc():
qc = QuantumCircuit(2)
qc.h(0)
qc.cx(0, 1)
return qc


@pytest.fixture
def ghz_qc():
qc = QuantumCircuit(3)
qc.h(0)
qc.cx(0, 1)
qc.cx(1, 2)
return qc


class TestQiskitAerBackend:
def test_bell_circuit(self, bell_qc, AerSimulator):
simulator = AerSimulator(method="matrix_product_state")
Expand Down
30 changes: 30 additions & 0 deletions test/simulation/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# This code is a Qiskit project.
#
# (C) Copyright IBM 2024.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.
import pytest
from qiskit import QuantumCircuit


@pytest.fixture
def bell_qc():
qc = QuantumCircuit(2)
qc.h(0)
qc.cx(0, 1)
return qc


@pytest.fixture
def ghz_qc():
qc = QuantumCircuit(3)
qc.h(0)
qc.cx(0, 1)
qc.cx(1, 2)
return qc
39 changes: 39 additions & 0 deletions test/simulation/quimb/test_quimb_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from qiskit.circuit import Parameter, QuantumCircuit
from qiskit.circuit.library import CXGate, RXGate, XXPlusYYGate

from qiskit_addon_aqc_tensor.objective import OneMinusFidelity
from qiskit_addon_aqc_tensor.simulation import (
compute_overlap,
tensornetwork_from_circuit,
Expand Down Expand Up @@ -115,3 +116,41 @@ def test_repeated_parameter(self):
e_info.value.args[0]
== "Parameter cannot be repeated in circuit, else quimb will attempt to optimize each instance separately."
)

def test_unspecified_gradient_method(self, quimb):
settings = QuimbSimulator(quimb.tensor.CircuitMPS)
qc = QuantumCircuit(1)
x = Parameter("x")
qc.rx(x, 0)
with pytest.raises(ValueError) as e_info:
OneMinusFidelity(None, qc, settings)
assert (
e_info.value.args[0]
== "Gradient method unspecified. Please specify an autodiff_backend for the QuimbSimulator object."
)

# FIXME: also do this with a random circuit
# FIXME: consolidate these tests across the backends
@pytest.mark.parametrize("circuit_factory", ["Circuit", "CircuitMPS"])
def test_bell_circuit_statevector(self, bell_qc, quimb, circuit_factory):
simulator = QuimbSimulator(getattr(quimb.tensor, circuit_factory))
out_state = np.zeros([4], dtype=complex)
tensornetwork_from_circuit(bell_qc, simulator, out_state=out_state)
assert out_state == pytest.approx(np.array([1, 0, 0, 1]) / np.sqrt(2))

def test_recovery_num_parameters_mismatch_error(self):
x = Parameter("x")
y = Parameter("y")
qc1 = QuantumCircuit(1)
qc1.rx(1 - x, 0)
qc2 = QuantumCircuit(1)
qc2.rx(1 - x, 0)
qc2.ry(1 - 0.5 * y, 0)
circ1, _ = qiskit_ansatz_to_quimb(qc1, [0.5])
_, ctx2 = qiskit_ansatz_to_quimb(qc2, [0.5, 0.3])
with pytest.raises(ValueError) as e_info:
recover_parameters_from_quimb(circ1, ctx2)
assert (
e_info.value.args[0]
== "The length of the mapping in the provided QiskitQuimbConversionContext does not match the number of parametrized gates in the circuit (2 vs. 1)."
)
54 changes: 53 additions & 1 deletion test/simulation/test_gradient.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
_compute_objective_and_gradient,
_preprocess_for_gradient,
)
from qiskit_addon_aqc_tensor.simulation.aer import is_aer_available
from qiskit_addon_aqc_tensor.simulation.explicit_gradient import _basis_gates


Expand Down Expand Up @@ -70,7 +71,7 @@ def _generate_random_ansatz(num_qubits: int):
su2_gates=su2_gates,
reps=random.randint(1, 2),
parameter_prefix="c",
insert_barriers=_get_random_bool(),
insert_barriers=True,
),
inplace=True,
copy=False,
Expand Down Expand Up @@ -129,3 +130,54 @@ def vdagger_rhs(thetas):
numerical_grad = -2 * np.real(np.conj(f_0) * numerical_grad)

assert grad == pytest.approx(numerical_grad, abs=1e-4)


@pytest.mark.skipif(not is_aer_available(), reason="qiskit-aer is not installed")
class TestExplicitGradient:
def test_no_parameters_throws_error(self, AerSimulator):
settings = AerSimulator(method="matrix_product_state")
tn = tensornetwork_from_circuit(QuantumCircuit(1), settings)
qc = QuantumCircuit(1)
with pytest.raises(ValueError) as e_info:
OneMinusFidelity(tn, qc, settings)
assert (
e_info.value.args[0]
== "Expects parametric circuit using ParameterVector object.\nThat is, placeholders are expected rather than concrete\nvalues for the variable parameters. The circuit specified\nhas no variable parameters. Check that the function\nassign_parameters() has not been applied to this circuit."
)

def test_non_basis_gate(self, AerSimulator):
settings = AerSimulator(method="matrix_product_state")
tn = tensornetwork_from_circuit(QuantumCircuit(2), settings)
qc = QuantumCircuit(2)
x = Parameter("x")
qc.rx(x, 0)
qc.cp(np.pi / 5, 0, 1)
with pytest.raises(ValueError) as e_info:
OneMinusFidelity(tn, qc, settings)
assert (
e_info.value.args[0]
== "Expects a gate from the list of basis ones: ['barrier', 'cx', 'cz', 'ecr', 'h', 'rx', 'ry', 'rz', 's', 'sdg', 'x', 'y', 'z'], got 'cp' instead."
)

def test_one_qubit_parametrized_pauli_error_messages(self, subtests, AerSimulator):
settings = AerSimulator(method="matrix_product_state")
tn = tensornetwork_from_circuit(QuantumCircuit(1), settings)
x = Parameter("x")
y = Parameter("y")
with subtests.test("Expression with multiple parameters"):
qc = QuantumCircuit(1)
qc.rx(x + y, 0)
with pytest.raises(ValueError) as e_info:
OneMinusFidelity(tn, qc, settings)
assert e_info.value.args[0] == "Expression cannot contain more than one Parameter"
with subtests.test("Nonlinear expression"):
qc = QuantumCircuit(1)
qc.rx(x**3, 0)
with pytest.raises(ValueError) as e_info:
OneMinusFidelity(tn, qc, settings)
assert (
e_info.value.args[0]
== "ParameterExpression's derivative must be a floating-point number, i.e., the expression must be in the form ax + b."
)

# FIXME test target and ansatz have different numbers of qubits
2 changes: 1 addition & 1 deletion tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ commands =
coverage3 run --source qiskit_addon_aqc_tensor --parallel-mode -m pytest test/ {posargs}
coverage3 combine
coverage3 html
coverage3 report --fail-under=98 --show-missing
coverage3 report --fail-under=100 --show-missing

[testenv:docs]
extras =
Expand Down
Loading