Skip to content

Commit

Permalink
Make ApproxTimeEvolution compatible with op math (#5362)
Browse files Browse the repository at this point in the history
[sc-58598] [sc-58407]

**Context:**

`ApproxTimeEvolution` tests are failing when used with new operator
arithmetic

**Description of the Change:**

Update `ApproxTimeEvolution` to rely on the pauli rep.

**Benefits:**

**Possible Drawbacks:**

**Related GitHub Issues:**

---------

Co-authored-by: qottmann <[email protected]>
Co-authored-by: Mudit Pandey <[email protected]>
Co-authored-by: Korbinian Kottmann <[email protected]>
Co-authored-by: lillian542 <[email protected]>
Co-authored-by: Pietropaolo Frisoni <[email protected]>
  • Loading branch information
6 people authored Mar 15, 2024
1 parent 70e9dbf commit d42f5ee
Show file tree
Hide file tree
Showing 7 changed files with 53 additions and 70 deletions.
3 changes: 3 additions & 0 deletions doc/releases/changelog-dev.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,9 @@
* Upgraded `null.qubit` to the new device API. Also, added support for all measurements and various modes of differentiation.
[(#5211)](https://github.com/PennyLaneAI/pennylane/pull/5211)

* `ApproxTimeEvolution` is now compatible with any operator that defines a `pauli_rep`.
[(#5362)](https://github.com/PennyLaneAI/pennylane/pull/5362)

* `Hamiltonian.pauli_rep` is now defined if the hamiltonian is a linear combination of paulis.
[(#5377)](https://github.com/PennyLaneAI/pennylane/pull/5377)

Expand Down
55 changes: 18 additions & 37 deletions pennylane/templates/subroutines/approx_time_evolution.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,14 +126,13 @@ def _unflatten(cls, data, metadata):
return cls(data[0], data[1], n=metadata[0])

def __init__(self, hamiltonian, time, n, id=None):
if not isinstance(hamiltonian, qml.Hamiltonian):
if getattr(hamiltonian, "pauli_rep", None) is None:
raise ValueError(
f"hamiltonian must be of type pennylane.Hamiltonian, got {type(hamiltonian).__name__}"
f"hamiltonian must be a linear combination of pauli words, got {type(hamiltonian).__name__}"
)

# extract the wires that the op acts on
wire_list = [term.wires for term in hamiltonian.ops]
wires = qml.wires.Wires.all_wires(wire_list)
wires = hamiltonian.wires

self._hyperparameters = {"hamiltonian": hamiltonian, "n": n}

Expand Down Expand Up @@ -187,38 +186,20 @@ def compute_decomposition(
... )
[PauliRot(0.1, ZZ, wires=[0, 1]), PauliRot(0.2, X, wires=[0]), PauliRot(0.3, X, wires=[1])]
"""
pauli = {"Identity": "I", "PauliX": "X", "PauliY": "Y", "PauliZ": "Z"}

theta = []
pauli_words = []
wires = []
coeffs = coeffs_and_time[:-1]
time = coeffs_and_time[-1]
for i, term in enumerate(hamiltonian.ops):
word = ""

try:
if isinstance(term.name, str):
word = pauli[term.name]

if isinstance(term.name, list):
word = "".join(pauli[j] for j in term.name)

except KeyError as error:
raise ValueError(
f"hamiltonian must be written in terms of Pauli matrices, got {error}"
) from error

# skips terms composed solely of identities
if word.count("I") != len(word):
theta.append((2 * time * coeffs[i]) / n)
pauli_words.append(word)
wires.append(term.wires)

op_list = []

for i in range(n):
for j, term in enumerate(pauli_words):
op_list.append(PauliRot(theta[j], term, wires=wires[j]))

return op_list
single_round = []
with qml.QueuingManager.stop_recording():
for pw, coeff in hamiltonian.pauli_rep.items():
if len(pw) == 0:
continue
theta = 2 * time * coeff / n
term_str = "".join(pw.values())
wires = qml.wires.Wires(pw.keys())
single_round.append(PauliRot(theta, term_str, wires=wires))

full_decomp = single_round * n
if qml.QueuingManager.recording():
_ = [qml.apply(op) for op in full_decomp]

return full_decomp
16 changes: 8 additions & 8 deletions pennylane/templates/subroutines/commuting_evolution.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,11 +121,12 @@ def __init__(self, hamiltonian, time, frequencies=None, shifts=None, id=None):
generate_shift_rule,
)

if not isinstance(hamiltonian, qml.Hamiltonian):
type_name = type(hamiltonian).__name__
raise TypeError(f"hamiltonian must be of type pennylane.Hamiltonian, got {type_name}")
if getattr(hamiltonian, "pauli_rep", None) is None:
raise TypeError(
f"hamiltonian must be a linear combination of pauli words. Got {hamiltonian}"
)

trainable_hamiltonian = qml.math.requires_grad(hamiltonian.coeffs)
trainable_hamiltonian = qml.math.requires_grad(hamiltonian.data)
if frequencies is not None and not trainable_hamiltonian:
c, s = generate_shift_rule(frequencies, shifts).T
recipe = qml.math.stack([c, qml.math.ones_like(c), s]).T
Expand All @@ -142,14 +143,14 @@ def __init__(self, hamiltonian, time, frequencies=None, shifts=None, id=None):

@staticmethod
def compute_decomposition(
time, *coeffs, wires, hamiltonian, **kwargs
time, *_, wires, hamiltonian, **__
): # pylint: disable=arguments-differ,unused-argument
r"""Representation of the operator as a product of other operators.
.. math:: O = O_1 O_2 \dots O_n.
Args:
time_and_coeffs (list[tensor_like or float]): list of coefficients of the Hamiltonian, prepended by the time
*time_and_coeffs (list[tensor_like or float]): list of coefficients of the Hamiltonian, prepended by the time
variable
wires (Any or Iterable[Any]): wires that the operator acts on
hamiltonian (.Hamiltonian): The commuting Hamiltonian defining the time-evolution operator.
Expand All @@ -164,11 +165,10 @@ def compute_decomposition(
list[.Operator]: decomposition of the operator
"""
# uses standard PauliRot decomposition through ApproxTimeEvolution.
hamiltonian = qml.Hamiltonian(coeffs, hamiltonian.ops)
return [qml.ApproxTimeEvolution(hamiltonian, time, 1)]

def adjoint(self):
hamiltonian = qml.Hamiltonian(self.parameters[1:], self.hyperparameters["hamiltonian"].ops)
hamiltonian = self.hyperparameters["hamiltonian"]
time = self.parameters[0]
frequencies = self.hyperparameters["frequencies"]
shifts = self.hyperparameters["shifts"]
Expand Down
6 changes: 3 additions & 3 deletions tests/circuit_graph/test_qasm.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,9 +117,9 @@ def test_to_ApproxTimeEvolution(self):
include "qelib1.inc";
qreg q[2];
creg c[2];
cx q[1],q[0];
rz(2.0) q[0];
cx q[1],q[0];
cx q[0],q[1];
rz(2.0) q[1];
cx q[0],q[1];
measure q[0] -> c[0];
measure q[1] -> c[1];
"""
Expand Down
19 changes: 9 additions & 10 deletions tests/templates/test_subroutines/test_approx_time_evolution.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,9 @@ class TestDecomposition:
2,
[
qml.PauliRot(4.0, "X", wires=["a"]),
qml.PauliRot(1.0, "ZX", wires=["b", "a"]),
qml.PauliRot(1.0, "XZ", wires=["a", "b"]),
qml.PauliRot(4.0, "X", wires=["a"]),
qml.PauliRot(1.0, "ZX", wires=["b", "a"]),
qml.PauliRot(1.0, "XZ", wires=["a", "b"]),
],
),
(
Expand All @@ -94,8 +94,8 @@ class TestDecomposition:
1,
[
qml.PauliRot(8.0, "X", wires=["a"]),
qml.PauliRot(2.0, "ZX", wires=[-15, "a"]),
qml.PauliRot(2.0, "IY", wires=[0, -15]),
qml.PauliRot(2.0, "XZ", wires=["a", -15]),
qml.PauliRot(2.0, "Y", wires=[-15]),
],
),
],
Expand All @@ -107,10 +107,7 @@ def test_evolution_operations(self, time, hamiltonian, steps, expected_queue):
queue = op.expand().operations

for expected_gate, gate in zip(expected_queue, queue):
prep = [gate.parameters, gate.wires]
target = [expected_gate.parameters, expected_gate.wires]

assert prep == target
assert qml.equal(expected_gate, gate)

@pytest.mark.parametrize(
("time", "hamiltonian", "steps", "expectation"),
Expand Down Expand Up @@ -203,7 +200,9 @@ def circuit():
qml.ApproxTimeEvolution(hamiltonian, 2, 3)
return [qml.expval(qml.PauliZ(wires=i)) for i in range(n_wires)]

with pytest.raises(ValueError, match="hamiltonian must be of type pennylane.Hamiltonian"):
with pytest.raises(
ValueError, match="hamiltonian must be a linear combination of pauli words"
):
circuit()

@pytest.mark.parametrize(
Expand All @@ -228,7 +227,7 @@ def circuit():
return [qml.expval(qml.PauliZ(wires=i)) for i in range(n_wires)]

with pytest.raises(
ValueError, match="hamiltonian must be written in terms of Pauli matrices"
ValueError, match="hamiltonian must be a linear combination of pauli words"
):
circuit()

Expand Down
6 changes: 3 additions & 3 deletions tests/templates/test_subroutines/test_commuting_evolution.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ def test_decomposition_expand():
decomp = op.decomposition()[0]

assert isinstance(decomp, qml.ApproxTimeEvolution)
assert all(decomp.hyperparameters["hamiltonian"].coeffs == hamiltonian.coeffs)
assert qml.math.allclose(decomp.hyperparameters["hamiltonian"].data, hamiltonian.data)
assert decomp.hyperparameters["n"] == 1

tape = op.expand()
Expand Down Expand Up @@ -141,9 +141,9 @@ class TestInputs:
"""Tests for input validation of `CommutingEvolution`."""

def test_invalid_hamiltonian(self):
"""Tests TypeError is raised if `hamiltonian` is not type `qml.Hamiltonian`."""
"""Tests TypeError is raised if `hamiltonian` does not have a pauli rep."""

invalid_operator = qml.PauliX(0)
invalid_operator = qml.Hermitian(np.eye(2), 0)

assert pytest.raises(TypeError, qml.CommutingEvolution, invalid_operator, 1)

Expand Down
18 changes: 9 additions & 9 deletions tests/test_qaoa.py
Original file line number Diff line number Diff line change
Expand Up @@ -1110,12 +1110,12 @@ def test_cost_layer_errors(self):
[
qaoa.xy_mixer(Graph([(0, 1), (1, 2), (2, 0)])),
[
qml.PauliRot(1, "XX", wires=[0, 1]),
qml.PauliRot(1, "YY", wires=[0, 1]),
qml.PauliRot(1, "XX", wires=[0, 2]),
qml.PauliRot(1, "YY", wires=[0, 2]),
qml.PauliRot(1, "XX", wires=[1, 2]),
qml.PauliRot(1, "YY", wires=[1, 2]),
qml.PauliRot(1, "XX", wires=[1, 0]),
qml.PauliRot(1, "YY", wires=[1, 0]),
qml.PauliRot(1, "XX", wires=[2, 0]),
qml.PauliRot(1, "YY", wires=[2, 0]),
qml.PauliRot(1, "XX", wires=[2, 1]),
qml.PauliRot(1, "YY", wires=[2, 1]),
],
],
],
Expand Down Expand Up @@ -1146,9 +1146,9 @@ def test_mixer_layer_output(self, mixer, gates):
[
qaoa.maxcut(Graph([(0, 1), (1, 2), (2, 0)]))[0],
[
qml.PauliRot(1, "ZZ", wires=[0, 1]),
qml.PauliRot(1, "ZZ", wires=[0, 2]),
qml.PauliRot(1, "ZZ", wires=[1, 2]),
qml.PauliRot(1, "ZZ", wires=[1, 0]),
qml.PauliRot(1, "ZZ", wires=[2, 0]),
qml.PauliRot(1, "ZZ", wires=[2, 1]),
],
],
],
Expand Down

0 comments on commit d42f5ee

Please sign in to comment.