Skip to content
This repository has been archived by the owner on Nov 16, 2023. It is now read-only.

Commit

Permalink
Add additional function and method docstrings
Browse files Browse the repository at this point in the history
Partially addresses #16

Currently, the only files that are lacking such docstrings
are those dealing with magic rounding.  I did not want to create
conflicts with #31.)
  • Loading branch information
garrison committed Jun 2, 2022
1 parent 43c64ee commit bfb8f68
Show file tree
Hide file tree
Showing 10 changed files with 57 additions and 5 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
- [Tutorials](docs/tutorials/)
- [How-Tos](docs/how_tos/)
- [Background](docs/background/README.md)
- [API Reference](https://qiskit-community.github.io/prototype-qrao/apidocs/) (work in progress)
- [API Reference](https://qiskit-community.github.io/prototype-qrao/apidocs/)
* [Important Usage Notes](#important-usage-notes)
* [How to Give Feedback](#how-to-give-feedback)
* [Contribution Guidelines](#contribution-guidelines)
Expand Down Expand Up @@ -86,7 +86,7 @@ Documentation is stored in the [`docs/`](docs/) directory. It is organized acco
- [Tutorials](docs/tutorials/): longer examples of end-to-end usage
- [How-to guides](docs/how_tos/): targeted answers to common questions
- [Background material](docs/background/README.md): in-depth exploration of theoretical concepts
- [API Reference](https://qiskit-community.github.io/prototype-qrao/apidocs/): documentation of each individual class and function (work in progress)
- [API Reference](https://qiskit-community.github.io/prototype-qrao/apidocs/): documentation of each individual class and function

----------------------------------------------------------------------------------------------------

Expand Down
2 changes: 1 addition & 1 deletion docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ The documentation in this project is organized into four types according to the

- [Tutorials](tutorials/)
- [How-to guides](how_tos/)
- [API Reference](https://qiskit-community.github.io/prototype-qrao/apidocs/) (work in progress)
- [API Reference](https://qiskit-community.github.io/prototype-qrao/apidocs/)
- [Explanation](background/) (background material on using quantum random access codes (QRACs) to solve optimization problems)

[Diátaxis framework]: https://diataxis.fr/
Expand Down
25 changes: 25 additions & 0 deletions qrao/encoding.py
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,9 @@ def qrac_state_prep_multiqubit(
) -> CircuitStateFn:
"""
Prepare a multiqubit QRAC state.
Args:
dvars: state of each decision variable (0 or 1)
"""
remaining_dvars = set(dvars if isinstance(dvars, dict) else range(len(dvars)))
ordered_bits = []
Expand Down Expand Up @@ -285,35 +288,50 @@ def __init__(self, max_vars_per_qubit: int = 3):

@property
def num_qubits(self) -> int:
"""Number of qubits"""
return len(self._q2vars)

@property
def num_vars(self) -> int:
"""Number of decision variables"""
return len(self._var2op)

@property
def max_vars_per_qubit(self) -> int:
"""Maximum number of variables per qubit
This is set in the constructor and controls the maximum compression ratio
"""

return len(self._ops)

@property
def var2op(self) -> Dict[int, Tuple[int, PrimitiveOp]]:
"""Maps each decision variable to ``(qubit_index, operator)``"""
return self._var2op

@property
def q2vars(self) -> List[List[int]]:
"""Each element contains the list of decision variable indice(s) encoded on that qubit"""
return self._q2vars

@property
def compression_ratio(self) -> float:
"""Compression ratio
Number of decision variables divided by number of qubits
"""
return self.num_vars / self.num_qubits

@property
def minimum_recovery_probability(self) -> float:
"""Minimum recovery probability, as set by ``max_vars_per_qubit``"""
n = self.max_vars_per_qubit
return (1 + 1 / np.sqrt(n)) / 2

@property
def qubit_op(self) -> Union[PauliOp, PauliSumOp]:
"""Relaxed Hamiltonian operator"""
if self._qubit_op is None:
raise AttributeError(
"No objective function has been provided from which a "
Expand All @@ -325,6 +343,7 @@ def qubit_op(self) -> Union[PauliOp, PauliSumOp]:

@property
def offset(self) -> float:
"""Relaxed Hamiltonian offset"""
if self._offset is None:
raise AttributeError(
"No objective function has been provided from which a "
Expand All @@ -336,6 +355,7 @@ def offset(self) -> float:

@property
def problem(self) -> QuadraticProgram:
"""The ``QuadraticProgram`` used as basis for the encoding"""
if self._problem is None:
raise AttributeError(
"No quadratic program has been associated with this object. "
Expand Down Expand Up @@ -392,6 +412,10 @@ def _add_term(self, w: float, *variables: int) -> None:
self._qubit_op += op

def term2op(self, *variables: int) -> PauliOp:
"""Construct a ``PauliOp`` that is a product of encoded decision ``variable``\\(s).
The decision variables provided must all be encoded on different qubits.
"""
ops = [I] * self.num_qubits
done = set()
for x in variables:
Expand Down Expand Up @@ -564,6 +588,7 @@ def ensure_thawed(self) -> None:
raise RuntimeError("Cannot modify an encoding that has been frozen")

def state_prep(self, dvars: Union[Dict[int, int], List[int]]) -> CircuitStateFn:
"""Prepare a multiqubit QRAC state."""
return qrac_state_prep_multiqubit(dvars, self.q2vars, self.max_vars_per_qubit)


Expand Down
1 change: 1 addition & 0 deletions qrao/rounding_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ def __init__(self, samples: List[RoundingSolutionSample], *, time_taken=None):

@property
def samples(self) -> List[RoundingSolutionSample]:
"""List of samples"""
return self._samples


Expand Down
1 change: 1 addition & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
# https://docs.pytest.org/en/latest/example/simple.html#control-skipping-of-tests-according-to-command-line-option


# pylint: disable=missing-function-docstring
def pytest_addoption(parser):
parser.addoption(
"--run-backend-tests",
Expand Down
4 changes: 4 additions & 0 deletions tests/test_backends.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@

@pytest.fixture(scope="module")
def my_encoding():
"""Fixture to construct ``my_encoding`` for use in this file"""
# Load small reference problem
elist = [(0, 1), (0, 4), (0, 3), (1, 2), (1, 5), (2, 3), (2, 4), (4, 5), (5, 3)]
num_nodes = 6
Expand All @@ -70,6 +71,7 @@ def my_encoding():

@pytest.fixture(scope="module")
def my_ansatz(my_encoding):
"""Fixture to construct ``my_ansatz`` for use in this file"""
return RealAmplitudes(my_encoding.num_qubits)


Expand All @@ -81,6 +83,8 @@ def my_ansatz(my_encoding):
) # ignore magic rounding's UserWarning when using statevector_simulator
@pytest.mark.backend
def test_backend(relaxed_backend, rounding_backend, my_encoding, my_ansatz, shots=3):
"""Smoke test of each backend combination"""

def cb(f, *args):
"Construct backend"
return f(*args)
Expand Down
15 changes: 15 additions & 0 deletions tests/test_encoding.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,15 @@


def test_qrac_unsupported_encoding():
"""Test that exception is raised if ``max_vars_per_qubit`` is invalid"""
with pytest.raises(ValueError):
QuantumRandomAccessEncoding(4)
with pytest.raises(TypeError):
QuantumRandomAccessEncoding(1.0)


def test_31p_qrac_encoding(): # pylint: disable=too-many-statements
"""Test (3,1,p) QRAC"""
encoding = QuantumRandomAccessEncoding(3)
assert encoding.num_qubits == 0
assert not encoding.frozen
Expand Down Expand Up @@ -103,6 +107,7 @@ def test_31p_qrac_encoding(): # pylint: disable=too-many-statements


def test_21p_qrac_encoding():
"""Test (2,1,p) QRAC"""
encoding = QuantumRandomAccessEncoding(2)
encoding._add_variables([7, 11, 13])
assert encoding.num_qubits == 2
Expand All @@ -116,6 +121,7 @@ def test_21p_qrac_encoding():


def test_111_qrac_encoding():
"""Test (1,1,1) QRAC"""
encoding = QuantumRandomAccessEncoding(1)
encoding._add_variables([7, 11, 13])
assert encoding.num_qubits == 3
Expand All @@ -129,6 +135,7 @@ def test_111_qrac_encoding():


def test_qrac_encoding_from_model():
"""Test QRAC encoding from DOcplex model"""
model = Model("docplex model")
x = model.binary_var("x")
y = model.binary_var("y")
Expand Down Expand Up @@ -159,6 +166,7 @@ def test_qrac_encoding_from_model():


def test_qrac_encoding_from_invalid_model2():
"""Test QRAC encoding from DOcplex model that is not a QUBO"""
model = Model("docplex model")
x = model.integer_var(-5, 5, "x")
model.minimize(x)
Expand All @@ -171,13 +179,16 @@ def test_qrac_encoding_from_invalid_model2():


def test_qrac_recovery_probability():
"""Test that each QRAC returns the correct recovery probability"""
e = {i: QuantumRandomAccessEncoding(i) for i in (1, 2, 3)}
assert e[1].minimum_recovery_probability == 1.0
assert e[2].minimum_recovery_probability == pytest.approx(0.854, 0.001)
assert e[3].minimum_recovery_probability == pytest.approx(0.789, 0.001)


def test_sense():
"""Test that minimization and maximization problems relate to each other as expected"""

def get_problem(maximize=True):
# Load small reference problem (includes a self-loop)
elist = [
Expand Down Expand Up @@ -220,6 +231,7 @@ def get_problem(maximize=True):


def test_qrac_state_prep_1q():
"""Test each possble QRAC state preparation on a single qubit"""
with pytest.raises(TypeError):
qrac_state_prep_1q(1, 0, 1, 0)

Expand All @@ -236,18 +248,21 @@ def test_qrac_state_prep_1q():


def test_undefined_basis_rotations():
"""Test that undefined basis rotations raise ``ValueError``"""
with pytest.raises(ValueError):
z_to_31p_qrac_basis_circuit([4]) # each element should be 0, 1, 2, or 3
with pytest.raises(ValueError):
z_to_21p_qrac_basis_circuit([2]) # each element should be 0 or 1


def test_unassigned_qubit():
"""Test that qubit with no decision variables assigned to it raises error"""
with pytest.raises(ValueError):
qrac_state_prep_multiqubit({}, [[]], 3)


def test_encoding_verifier_indexerror():
"""Test that ``EncodingCommutationVerifier`` raises ``IndexError`` if indexed out of range"""
model = Model("docplex model")
x = model.binary_var("x")
y = model.binary_var("y")
Expand Down
6 changes: 5 additions & 1 deletion tests/test_encoding_commutation.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@


def check_problem_commutation(problem: QuadraticProgram, max_vars_per_qubit: int):
"""Utility function to check that the problem commutes with its encoding"""
encoding = QuantumRandomAccessEncoding(max_vars_per_qubit=max_vars_per_qubit)
encoding.encode(problem)
verifier = EncodingCommutationVerifier(encoding)
Expand All @@ -38,7 +39,7 @@ def check_problem_commutation(problem: QuadraticProgram, max_vars_per_qubit: int
@pytest.mark.parametrize("max_vars_per_qubit", [1, 2, 3])
@pytest.mark.parametrize("task", ["minimize", "maximize"])
def test_one_qubit_qrac(max_vars_per_qubit, task):
"""Non-uniform weights, degree 1 terms"""
"""Test commutation of single qubit QRAC with non-uniform weights, degree 1 terms"""
mod = Model("maxcut")
num_nodes = max_vars_per_qubit
nodes = list(range(num_nodes))
Expand All @@ -54,6 +55,7 @@ def test_one_qubit_qrac(max_vars_per_qubit, task):
@pytest.mark.parametrize("max_vars_per_qubit", [1, 2, 3])
@pytest.mark.parametrize("task", ["minimize", "maximize"])
def test_uniform_weights_degree_2(max_vars_per_qubit, task):
"""Test problem commutation with degree 2 terms"""
# Note that the variable embedding has some qubits with 1, 2, and 3 qubits
num_nodes = 6
elist = [(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 0), (0, 3), (1, 4), (2, 4)]
Expand All @@ -78,13 +80,15 @@ def test_uniform_weights_degree_2(max_vars_per_qubit, task):

@pytest.mark.parametrize("max_vars_per_qubit", [1, 2, 3])
def test_random_unweighted_maxcut_problem(max_vars_per_qubit):
"""Test problem commutation on random max-cut problem"""
problem = get_random_maxcut_qp(degree=3, num_nodes=8)
check_problem_commutation(problem, max_vars_per_qubit=max_vars_per_qubit)


@pytest.mark.parametrize("max_vars_per_qubit", [1, 2, 3])
@pytest.mark.parametrize("task", ["minimize", "maximize"])
def test_nonuniform_weights_degree_1_and_2_terms(max_vars_per_qubit, task):
"""Test problem commutation with non-uniform weights"""
num_nodes = 6
elist = [(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 0), (0, 3), (1, 4), (2, 5)]
edges = np.zeros((num_nodes, num_nodes))
Expand Down
1 change: 1 addition & 0 deletions tests/test_semideterministic_rounding.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@


def test_semideterministic_rounding():
"""Test that semideterministic rounding works as expected"""
encoding = QuantumRandomAccessEncoding()
encoding.encode(get_random_maxcut_qp(degree=3, num_nodes=6))

Expand Down
3 changes: 2 additions & 1 deletion tests/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
from qrao.utils import get_random_maxcut_qp


def test_get_random_maxcut_qp():
def test_get_random_maxcut_qp_weight():
"""Test ``get_random_maxcut_qp()`` with each "edge case" (if statement branch) of ``weight``"""
get_random_maxcut_qp(weight=1)
get_random_maxcut_qp(weight=-1)
get_random_maxcut_qp(weight=2)
Expand Down

0 comments on commit bfb8f68

Please sign in to comment.