Skip to content

Commit

Permalink
tests
Browse files Browse the repository at this point in the history
  • Loading branch information
austingmhuang committed Apr 25, 2024
1 parent 1bdd336 commit 2ffb59a
Show file tree
Hide file tree
Showing 3 changed files with 121 additions and 20 deletions.
76 changes: 56 additions & 20 deletions pennylane_qiskit/converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@

import numpy as np
from qiskit import QuantumCircuit, ClassicalRegister, QuantumRegister
from pennylane_qiskit.qiskit_device import QISKIT_OPERATION_MAP
from qiskit.converters import circuit_to_dag, dag_to_circuit
from qiskit.circuit import Parameter, ParameterExpression, ParameterVector
from qiskit.circuit import Measure, Barrier, ControlFlowOp, Clbit
Expand Down Expand Up @@ -67,7 +66,9 @@ def _check_parameter_bound(
"""
if isinstance(param, ParameterVectorElement):
if param.vector not in unbound_params:
raise ValueError(f"The vector of parameter {param} was not bound correctly.")
raise ValueError(
f"The vector of parameter {param} was not bound correctly."
)

elif isinstance(param, Parameter):
if param not in unbound_params:
Expand Down Expand Up @@ -166,8 +167,12 @@ def _format_params_dict(quantum_circuit, params, *args, **kwargs):
params[expected_params[k]] = v

# get any parameters not defined in kwargs (may be all of them) and match to args in order
expected_arg_params = [param for name, param in expected_params.items() if name not in kwargs]
has_param_vectors = np.any([isinstance(p, ParameterVector) for p in expected_arg_params])
expected_arg_params = [
param for name, param in expected_params.items() if name not in kwargs
]
has_param_vectors = np.any(
[isinstance(p, ParameterVector) for p in expected_arg_params]
)

# if too many args were passed to the function call, raise an error
# all other checks regarding correct arguments will be processed in _check_circuit_and_assign_parameters
Expand Down Expand Up @@ -226,7 +231,9 @@ def _check_circuit_and_assign_parameters(
QuantumCircuit: quantum circuit with bound parameters
"""
if not isinstance(quantum_circuit, QuantumCircuit):
raise ValueError(f"The circuit {quantum_circuit} is not a valid Qiskit QuantumCircuit.")
raise ValueError(
f"The circuit {quantum_circuit} is not a valid Qiskit QuantumCircuit."
)

# confirm parameter names are valid for conversion to PennyLane
for name in ["wires", "params"]:
Expand All @@ -247,7 +254,9 @@ def _check_circuit_and_assign_parameters(
return quantum_circuit

# if any parameters are missing a value, raise an error
undefined_params = [name for name, param in expected_params.items() if param not in params]
undefined_params = [
name for name, param in expected_params.items() if param not in params
]
if undefined_params:
s = "s" if len(undefined_params) > 1 else ""
param_names = ", ".join(undefined_params)
Expand Down Expand Up @@ -433,7 +442,9 @@ def _function(*args, params: dict = None, wires: list = None, **kwargs):
# and then bind the parameters to the circuit
params = _format_params_dict(quantum_circuit, params, *args, **kwargs)
unbound_params = _extract_variable_refs(params)
qc = _check_circuit_and_assign_parameters(quantum_circuit, params, unbound_params)
qc = _check_circuit_and_assign_parameters(
quantum_circuit, params, unbound_params
)

# Wires from a qiskit circuit have unique IDs, so their hashes are unique too
qc_wires = [hash(q) for q in qc.qubits]
Expand All @@ -451,11 +462,15 @@ def _function(*args, params: dict = None, wires: list = None, **kwargs):
(instruction, qargs, cargs) = circuit_instruction
# the new Singleton classes have different names than the objects they represent,
# but base_class.__name__ still matches
instruction_name = getattr(instruction, "base_class", instruction.__class__).__name__
instruction_name = getattr(
instruction, "base_class", instruction.__class__
).__name__
# New Qiskit gates that are not natively supported by PL (identical
# gates exist with a different name)
# TODO: remove the following when gates have been renamed in PennyLane
instruction_name = "U3Gate" if instruction_name == "UGate" else instruction_name
instruction_name = (
"U3Gate" if instruction_name == "UGate" else instruction_name
)

# Define operator builders and helpers
# operation_class -> PennyLane operation class object mapped from the Qiskit operation
Expand Down Expand Up @@ -505,7 +520,9 @@ def _function(*args, params: dict = None, wires: list = None, **kwargs):
break
# Check if the subsequent next_op is measurement interfering
if not isinstance(next_op, (Barrier, GlobalPhaseGate)):
next_op_wires = set(wire_map[hash(qubit)] for qubit in next_qargs)
next_op_wires = set(
wire_map[hash(qubit)] for qubit in next_qargs
)
# Check if there's any overlapping wires
if next_op_wires & op_wires:
meas_terminal = False
Expand Down Expand Up @@ -562,11 +579,17 @@ def _function(*args, params: dict = None, wires: list = None, **kwargs):
# Process qiskit condition to PL mid-circ meas conditions
# pl_meas_conds -> PL's conditional expression with mid-circuit meas.
# length(pl_meas_conds) == len(true_fns) ==> True
pl_meas_conds = _process_condition(inst_cond, mid_circ_regs, instruction_name)
pl_meas_conds = _process_condition(
inst_cond, mid_circ_regs, instruction_name
)

# Iterate over each of the conditional triplet and apply the condition via qml.cond
for pl_meas_cond, true_fn, false_fn in zip(pl_meas_conds, true_fns, false_fns):
qml.cond(pl_meas_cond, true_fn, false_fn)(*operation_args, **operation_kwargs)
for pl_meas_cond, true_fn, false_fn in zip(
pl_meas_conds, true_fns, false_fns
):
qml.cond(pl_meas_cond, true_fn, false_fn)(
*operation_args, **operation_kwargs
)

# Check if it is not a mid-circuit measurement
elif operation_class and not isinstance(instruction, Measure):
Expand Down Expand Up @@ -821,7 +844,9 @@ def load_pauli_op(
Y(5) @ Z(3) @ X(7)
"""
if not isinstance(pauli_op, SparsePauliOp):
raise ValueError(f"The operator {pauli_op} is not a valid Qiskit SparsePauliOp.")
raise ValueError(
f"The operator {pauli_op} is not a valid Qiskit SparsePauliOp."
)

if wires is not None and len(wires) != pauli_op.num_qubits:
raise RuntimeError(
Expand All @@ -838,7 +863,9 @@ def load_pauli_op(

coeffs = pauli_op.coeffs
if ParameterExpression in [type(c) for c in coeffs]:
raise RuntimeError(f"Not all parameter expressions are assigned in coeffs {coeffs}")
raise RuntimeError(
f"Not all parameter expressions are assigned in coeffs {coeffs}"
)

qiskit_terms = pauli_op.paulis
pl_terms = []
Expand Down Expand Up @@ -937,7 +964,9 @@ def _process_condition(cond_op, mid_circ_regs, instruction_name):

# Check if the condition is as a tuple -> (Clbit/Clreg, Val)
if isinstance(condition, tuple):
clbits = [condition[0]] if isinstance(condition[0], Clbit) else list(condition[0])
clbits = (
[condition[0]] if isinstance(condition[0], Clbit) else list(condition[0])
)

# Proceed only if we have access to all conditioned classical bits
if all(clbit in mid_circ_regs for clbit in clbits):
Expand Down Expand Up @@ -984,13 +1013,17 @@ def _process_switch_condition(condition, mid_circ_regs):
# if the target is not an Expr
if not isinstance(condition[0], expr.Expr):
# Prepare the classical bits used for the condition
clbits = [condition[0]] if isinstance(condition[0], Clbit) else list(condition[0])
clbits = (
[condition[0]] if isinstance(condition[0], Clbit) else list(condition[0])
)

# Proceed only if we have access to all conditioned classical bits
meas_pl_op = None
if all(clbit in mid_circ_regs for clbit in clbits):
# Build an integer representation for each switch case
meas_pl_op = sum(2**idx * mid_circ_regs[clbit] for idx, clbit in enumerate(clbits))
meas_pl_op = sum(
2**idx * mid_circ_regs[clbit] for idx, clbit in enumerate(clbits)
)

# if the target is an Expr
else:
Expand Down Expand Up @@ -1072,7 +1105,9 @@ def _expr_evaluation(condition, mid_circ_regs):

# divide the bits among left and right cbit registers
if len(clbits) == 2:
condition_res = _expr_eval_clregs(clbits, _expr_mapping[condition_name], bitwise_flag)
condition_res = _expr_eval_clregs(
clbits, _expr_mapping[condition_name], bitwise_flag
)

# divide the bits into a cbit register and integer
else:
Expand Down Expand Up @@ -1115,7 +1150,8 @@ def _expr_eval_clregs(clbits, expr_func, bitwise=False):
# So we build an integer form 'before' performing the operation.
else:
meas1, meas2 = [
sum(2**idx * meas for idx, meas in enumerate(clreg)) for clreg in [clreg1, clreg2]
sum(2**idx * meas for idx, meas in enumerate(clreg))
for clreg in [clreg1, clreg2]
]
condition_res = expr_func(meas1, meas2)

Expand Down
58 changes: 58 additions & 0 deletions tests/.pylintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
[MASTER]
# A comma-separated list of package or module names from where C extensions may
# be loaded. Extensions are loading into the active Python interpreter and may
# run arbitrary code
extension-pkg-whitelist=numpy,scipy,autograd,toml,appdir,autograd.numpy,autograd.numpy.linalg,autograd.numpy.builtins,semantic_version,torch,tensorflow,tensorflow.contrib,tensorflow.contrib.eager,LazyLoader,networkx,networkx.dag

[TYPECHECK]

# List of module names for which member attributes should not be checked
# (useful for modules/projects where namespaces are manipulated during runtime
# and thus existing member attributes cannot be deduced by static analysis. It
# supports qualified module names, as well as Unix pattern matching.
ignored-modules=numpy,scipy,autograd,toml,appdir,autograd.numpy,autograd.numpy.linalg,autograd.numpy.builtins,semantic_version,torch,tensorflow,tensorflow.contrib,tensorflow.contrib.eager,LazyLoader,networkx,networkx.dag,math,pennylane.numpy

# List of classes names for which member attributes should not be checked
# (useful for classes with attributes dynamically set). This supports can work
# with qualified names.
ignored-classes=numpy,scipy,autograd,toml,appdir,autograd.numpy,autograd.numpy.linalg,autograd.numpy.builtins,semantic_version,torch,tensorflow,tensorflow.contrib,tensorflow.contrib.eager,LazyLoader,networkx,networkx.dag,math,pennylane.numpy,pennylane.numpy.random,pennylane.numpy.linalg,pennylane.numpy.builtins,pennylane.operation,rustworkx,kahypar

[MESSAGES CONTROL]

# Enable the message, report, category or checker with the given id(s). You can
# either give multiple identifier separated by comma (,) or put this option
# multiple time.
#enable=

# Disable the message, report, category or checker with the given id(s). You
# can either give multiple identifier separated by comma (,) or put this option
# multiple time (only on the command line, not in the configuration file where
# it should appear only once).
# Cyclical import checks are disabled for now as they are frequently used in
# the code base, but this can be removed in the future once cycles are resolved.
disable=
line-too-long,
invalid-name,
too-many-lines,
redefined-builtin,
too-many-locals,
duplicate-code,
cyclic-import,
import-error,
bad-option-value,
import-outside-toplevel,
missing-class-docstring,
missing-function-docstring,
no-self-use,
no-member, # because of qnode decorator
comparison-with-callable,
unsubscriptable-object, # because of qnode decorator
not-callable, # because of qnode decorator
unexpected-keyword-arg,
arguments-differ,
no-value-for-parameter

[MISCELLANEOUS]

# List of note tags to take in consideration, separated by a comma.
notes=
7 changes: 7 additions & 0 deletions tests/test_converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -1896,6 +1896,10 @@ def test_mp_to_pauli_for_general_operator(
pauli_op_list = list(pauli_op.paulis.to_labels()[0])
# all qubits in register are accounted for
assert len(pauli_op_list) == register_size
<<<<<<< HEAD
=======
print(SparsePauliOp(data=data, coeffs=coeffs).simplify())
>>>>>>> 1bbd62e (refactor)
assert pauli_op == SparsePauliOp(data=data, coeffs=coeffs).simplify()

@pytest.mark.parametrize("measurement_type", [qml.expval, qml.var])
Expand Down Expand Up @@ -1925,10 +1929,13 @@ def test_mp_to_pauli_for_general_operator(
+ 0.7 * (SparsePauliOp("IXI") @ SparsePauliOp("XII"))
+ 0.8 * (SparsePauliOp("XII") @ SparsePauliOp("IXI")),
),
<<<<<<< HEAD
(
qml.Hamiltonian([1], [qml.X(0)]) + 2 * qml.Z(0) @ qml.Z(1),
SparsePauliOp("IIX") + 2 * SparsePauliOp("IIZ") @ SparsePauliOp("IZI"),
),
=======
>>>>>>> 1bbd62e (refactor)
],
)
def test_mp_to_pauli_tensor_products(self, measurement_type, operator, expected):
Expand Down

0 comments on commit 2ffb59a

Please sign in to comment.