Skip to content

Commit

Permalink
more testing
Browse files Browse the repository at this point in the history
  • Loading branch information
albi3ro committed Feb 6, 2025
1 parent c322699 commit ca082cd
Show file tree
Hide file tree
Showing 9 changed files with 64 additions and 55 deletions.
2 changes: 2 additions & 0 deletions doc/releases/changelog-dev.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

<h3>Improvements 🛠</h3>

* `qml.gradients.hadamard_grad` can now differentiate anything with a generator, and can accept circuits with non-commuting measurements.

* Add a `qml.capture.pause()` context manager for pausing program capture in an error-safe way.
[(#6911)](https://github.com/PennyLaneAI/pennylane/pull/6911)

Expand Down
11 changes: 7 additions & 4 deletions pennylane/gradients/hadamard_gradient.py
Original file line number Diff line number Diff line change
Expand Up @@ -348,7 +348,9 @@ def _expval_hadamard_grad(tape, argnum, aux_wire):
)


def _new_measurement(mp, aux_wire, all_wires):
def _new_measurement(
mp: qml.measurement.MeasurementProcess, aux_wire, all_wires: qml.wires.Wires
) -> qml.measurement.MeasurementProcess:
obs = mp.obs or qml.prod(*(qml.Z(w) for w in mp.wires or all_wires))
new_obs = qml.simplify(obs @ qml.Y(aux_wire))
return type(mp)(obs=new_obs)
Expand All @@ -358,7 +360,7 @@ def _get_pauli_generators(trainable_op):
"""From a trainable operation, extract the unitary generators and their coefficients.
Any operator with a generator is supported.
"""
generator = qml.generator(trainable_op, format="observable")
generator = trainable_op.generator()
if generator.pauli_rep is None:
mat = qml.matrix(generator, wire_order=generator.wires)
op = qml.pauli_decompose(mat, wire_order=generator.wires)
Expand All @@ -382,14 +384,15 @@ def _postprocess_probs(res, measurement, tape):
return qml.math.tensordot(res, projector, axes=[[1], [0]])


def processing_fn(results, tape, coeffs, generators_per_parameter):
def processing_fn(results: qml.typing.ResultBatch, tape, coeffs, generators_per_parameter):
"""Post processing function for computing a hadamard gradient."""

final_res = []
for coeff, res in zip(coeffs, results):
if isinstance(res, (tuple, list)):
if isinstance(res, (tuple, list)): # more than one measurement
new_val = [qml.math.convert_like(2 * coeff * r, r) for r in res]
else:
# add singleton dimension back in for one measurement
new_val = [qml.math.convert_like(2 * coeff * res, res)]
final_res.append(new_val)

Expand Down
4 changes: 1 addition & 3 deletions pennylane/operation.py
Original file line number Diff line number Diff line change
Expand Up @@ -1418,7 +1418,7 @@ def has_generator(cls) -> bool:
"""
return cls.generator != Operator.generator

def generator(self): # pylint: disable=no-self-use
def generator(self) -> "Operator": # pylint: disable=no-self-use
r"""Generator of an operator that is in single-parameter-form.
For example, for operator
Expand All @@ -1435,8 +1435,6 @@ def generator(self): # pylint: disable=no-self-use
The generator may also be provided in the form of a dense or sparse Hamiltonian
(using :class:`.LinearCombination` and :class:`.SparseHamiltonian` respectively).
The default value to return is ``None``, indicating that the operation has
no defined generator.
"""
raise GeneratorUndefinedError(f"Operation {self.name} does not have a generator")

Expand Down
34 changes: 12 additions & 22 deletions pennylane/ops/functions/generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,30 +60,20 @@ def _generator_prefactor(gen):

prefactor = 1.0

gen = qml.simplify(gen) if isinstance(gen, Prod) else gen

if isinstance(gen, LinearCombination):
gen = qml.dot(gen.coeffs, gen.ops) # convert to Sum

if isinstance(gen, Prod):
coeffs, ops = gen.terms()
coeffs, ops = gen.terms()
if len(ops) == 1:
return ops[0], coeffs[0]

if isinstance(gen, Sum):
ops = [o.base if isinstance(o, SProd) else o for o in gen]
coeffs = [o.scalar if isinstance(o, SProd) else 1 for o in gen]
abs_coeffs = list(qml.math.abs(coeffs))
if qml.math.allequal(coeffs[0], coeffs):
# case where the Hamiltonian coefficients are all the same
return qml.sum(*ops), coeffs[0]
if qml.math.allequal(abs_coeffs[0], abs_coeffs):
# absolute value of coefficients is the same
prefactor = abs_coeffs[0]
coeffs = [c / prefactor for c in coeffs]
return qml.dot(coeffs, ops), prefactor

elif isinstance(gen, SProd):
return gen.base, gen.scalar
if qml.math.allequal(coeffs[0], coeffs):
# case where the Hamiltonian coefficients are all the same
return qml.sum(*ops), coeffs[0]

abs_coeffs = list(qml.math.abs(coeffs))
if qml.math.allequal(abs_coeffs[0], abs_coeffs):
# absolute value of coefficients is the same
prefactor = abs_coeffs[0]
coeffs = [c / prefactor for c in coeffs]
return qml.dot(coeffs, ops), prefactor

return gen, prefactor

Expand Down
2 changes: 2 additions & 0 deletions pennylane/ops/qubit/observables.py
Original file line number Diff line number Diff line change
Expand Up @@ -528,6 +528,8 @@ def pauli_rep(self):

pr = PauliWord({})
for val, wire in zip(self.data[0], self.wires):
if qml.math.is_abstract(val): # slicing can produce a tracer
return None
if qml.math.allclose(val, 0):
pr @= 0.5 * PauliWord({}) + 0.5 * PauliWord({wire: "Z"})
else:
Expand Down
9 changes: 5 additions & 4 deletions tests/gradients/core/test_hadamard_gradient.py
Original file line number Diff line number Diff line change
Expand Up @@ -257,12 +257,12 @@ def test_controlled_rotation_gradient_multi(self, G, theta, tol):
res_hadamard, _ = grad_fn(tape, dev)
res_param_shift, _ = grad_fn(tape, dev, qml.gradients.param_shift)

# assert isinstance(res_hadamard, tuple)
assert isinstance(res_hadamard, (list, tuple))
assert np.allclose(res_hadamard[0], res_param_shift[0], atol=tol, rtol=0)
assert np.allclose(res_hadamard[1], res_param_shift[1], atol=tol, rtol=0)

@pytest.mark.parametrize("theta", np.linspace(-2 * np.pi, 2 * np.pi, 7))
@pytest.mark.parametrize("G", [qml.IsingXX, qml.IsingYY, qml.IsingZZ])
@pytest.mark.parametrize("G", [qml.IsingXX, qml.IsingYY, qml.IsingZZ, qml.IsingXY])
def test_ising_gradient(self, G, theta, tol):
"""Test gradient of Ising coupling gates"""
dev = qml.device("default.qubit", wires=3)
Expand Down Expand Up @@ -402,18 +402,19 @@ def test_multiple_expectation_values(self, tol):
qml.CNOT(wires=[0, 1])
qml.expval(qml.PauliZ(0))
qml.expval(qml.PauliX(1))
qml.expval(qml.X(0))

tape = qml.tape.QuantumScript.from_queue(q)

res_hadamard, tapes = grad_fn(tape, dev)

assert len(tapes) == 2

assert len(res_hadamard) == 2
assert len(res_hadamard) == 3
assert len(res_hadamard[0]) == 2
assert len(res_hadamard[1]) == 2

expected = np.array([[-np.sin(x), 0], [0, np.cos(y)]])
expected = np.array([[-np.sin(x), 0], [0, np.cos(y)], [0, 0]])
assert np.allclose(res_hadamard[0], expected[0], atol=tol, rtol=0)
assert np.allclose(res_hadamard[1], expected[1], atol=tol, rtol=0)

Expand Down
18 changes: 4 additions & 14 deletions tests/ops/op_math/test_controlled.py
Original file line number Diff line number Diff line change
Expand Up @@ -478,21 +478,11 @@ def test_generator(self):

print(op.generator())

base_gen, base_gen_coeff = qml.generator(base, format="prefactor")
gen_tensor, gen_coeff = qml.generator(op, format="prefactor")
base_gen = base.generator()
gen = op.generator()

print(base_gen, base_gen_coeff)
print(gen_tensor, gen_coeff)
assert base_gen_coeff == gen_coeff

for wire, val in zip(op.control_wires, control_values):
ob = list(op for op in gen_tensor.operands if op.wires == qml.wires.Wires(wire))
assert len(ob) == 1
assert ob[0].data == ([val],)

ob = list(op for op in gen_tensor.operands if op.wires == base.wires)
assert len(ob) == 1
assert ob[0].__class__ is base_gen.__class__
expected = qml.prod(qml.Projector([0], "b"), qml.Projector([1], "c"), base_gen)
qml.assert_equal(gen, expected)

expected = qml.exp(op.generator(), 1j * op.data[0])
assert qml.math.allclose(
Expand Down
37 changes: 30 additions & 7 deletions tests/ops/qubit/test_observables.py
Original file line number Diff line number Diff line change
Expand Up @@ -768,16 +768,10 @@ def test_projector_diagonalization(self, basis_state):

def test_projector_exceptions(self):
"""Tests that the projector construction raises the proper errors on incorrect inputs."""
dev = qml.device("default.qubit", wires=2)

@qml.qnode(dev)
def circuit(basis_state):
obs = qml.Projector(basis_state, wires=range(2))
return qml.expval(obs)

with pytest.raises(ValueError, match="Basis state must only consist of 0s"):
basis_state = np.array([0, 2])
circuit(basis_state)
qml.Projector(basis_state, wires=range(2))

@pytest.mark.parametrize(
"basis_state,expected,n_wires",
Expand Down Expand Up @@ -826,6 +820,35 @@ def test_matrix_representation(self, basis_state, expected, n_wires, tol):
assert np.allclose(res_dynamic, expected, atol=tol)
assert np.allclose(res_static, expected, atol=tol)

def test_pauli_rep(self):
"""Test that BasisStateProjector has a pauli rep."""

op = qml.Projector([0, 1], wires=(0, 1))

equivalent_op = 0.25 * (qml.I(0) + qml.Z(0)) @ (qml.I(1) - qml.Z(1))
assert op.pauli_rep == equivalent_op.pauli_rep

pr_mat = op.pauli_rep.to_mat(wire_order=(0, 1))
assert qml.math.allclose(op.matrix(wire_order=(0, 1)), pr_mat)

@pytest.mark.jax
def test_pauli_rep_jitting(self):
"""Test that BasisStateProjector does not have a pauli rep when jitting."""

import jax

def f():
op = qml.Projector(jax.numpy.array([1, 0]), wires=(0, 1))
assert op.pauli_rep is None

_ = jax.jit(f)()

def f(x):
op = qml.Projector(x, wires=(0, 1))
assert op.pauli_rep is None

_ = jax.jit(f)(jax.numpy.array([1, 0]))

def test_integration_batched_state(self):
dev = qml.device("default.qubit", wires=1)

Expand Down
2 changes: 1 addition & 1 deletion tests/pauli/test_conversion.py
Original file line number Diff line number Diff line change
Expand Up @@ -551,7 +551,7 @@ def test_operator_private_ps(self, op, ps):
warnings.simplefilter("ignore")
error_ps = (
qml.Hadamard(wires=0),
qml.Hamiltonian([1, 2], [qml.Projector([0], wires=0), qml.PauliZ(wires=1)]),
qml.Hamiltonian([1, 2], [qml.Hermitian(np.eye(2), wires=0), qml.PauliZ(wires=1)]),
qml.RX(1.23, wires="a") + qml.PauliZ(wires=0),
)

Expand Down

0 comments on commit ca082cd

Please sign in to comment.