Skip to content

Commit 72e4baa

Browse files
vincentmrgithub-actions[bot]AmintorDuskomaliasadimudit2812
authored
Add test_templates module + Fix LQ decomposition of StatePrep Ops + Fix LQ decomposition strategy of QFT and GroverOperator (#684)
* Add test for QSVT. * Auto update version * Update changelog. * Add tests for embedding, layer and stateprep templates. * Parametrize tests over n_qubits. * Add qchem template tests. * Add a few misc template tests. * Auto update version * trigger ci * Fix serialize. * Fix formatting. * Update tests/test_templates.py Co-authored-by: Amintor Dusko <[email protected]> * Update tests/test_templates.py Co-authored-by: Amintor Dusko <[email protected]> * Add xfail condition for AmplitudeEmbedding. * Fix pytest.skip top cond. * Update tests/test_templates.py Co-authored-by: Ali Asadi <[email protected]> * Update pennylane_lightning/core/_serialize.py Co-authored-by: Ali Asadi <[email protected]> * Add breaking tests for if not observable * trigger ci * Auto update version * Fix decompositions with `LightningQubit` (#687) * Fix bug; add tests; update changelog * Auto update version * Update tests * Update qft/grover decomp * Addressing code review * Update pennylane_lightning/lightning_qubit/lightning_qubit.py Co-authored-by: Ali Asadi <[email protected]> * Trigger CI * Auto update version --------- Co-authored-by: Dev version update bot <github-actions[bot]@users.noreply.github.com> Co-authored-by: Ali Asadi <[email protected]> * Fix test params --------- Co-authored-by: Dev version update bot <github-actions[bot]@users.noreply.github.com> Co-authored-by: Amintor Dusko <[email protected]> Co-authored-by: Ali Asadi <[email protected]> Co-authored-by: Mudit Pandey <[email protected]>
1 parent 288b08d commit 72e4baa

File tree

8 files changed

+875
-45
lines changed

8 files changed

+875
-45
lines changed

.github/CHANGELOG.md

+9
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,9 @@
5252

5353
### Improvements
5454

55+
* Add `test_templates.py` module where Grover and QSVT are tested.
56+
[(#684)](https://github.com/PennyLaneAI/pennylane-lightning/pull/684)
57+
5558
* Create `cuda_utils` for common usage of CUDA related backends.
5659
[(#676)](https://github.com/PennyLaneAI/pennylane-lightning/pull/676)
5760

@@ -74,6 +77,12 @@
7477

7578
### Bug fixes
7679

80+
* `LightningQubit` correctly decomposes state prep operations when used in the middle of a circuit.
81+
[(#687)](https://github.com/PennyLaneAI/pennylane/pull/687)
82+
83+
* `LightningQubit` correctly decomposes `qml.QFT` and `qml.GroverOperator` if `len(wires)` is greater than 9 and 12 respectively.
84+
[(#687)](https://github.com/PennyLaneAI/pennylane/pull/687)
85+
7786
* Specify `isort` `--py` (Python version) and `-l` (max line length) to stabilize `isort` across Python versions and environments.
7887
[(#647)](https://github.com/PennyLaneAI/pennylane-lightning/pull/647)
7988

pennylane_lightning/core/_serialize.py

+3
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,9 @@ def map_wire(wire: int):
255255

256256
def _pauli_sentence(self, observable, wires_map: dict = None):
257257
"""Serialize a :class:`pennylane.pauli.PauliSentence` into a Hamiltonian."""
258+
# Trivial Pauli sentences' items is empty, cannot unpack
259+
if not observable:
260+
return self.hamiltonian_obs(np.array([0.0]).astype(self.rtype), [self._ob(Identity(0))])
258261
pwords, coeffs = zip(*observable.items())
259262
terms = [self._pauli_word(pw, wires_map) for pw in pwords]
260263
coeffs = np.array(coeffs).astype(self.rtype)

pennylane_lightning/core/_version.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,4 @@
1616
Version number (major.minor.patch[-label])
1717
"""
1818

19-
__version__ = "0.36.0-dev31"
19+
__version__ = "0.36.0-dev32"

pennylane_lightning/lightning_qubit/lightning_qubit.py

+9-4
Original file line numberDiff line numberDiff line change
@@ -182,9 +182,6 @@ def simulate_and_vjp(
182182
_operations = frozenset(
183183
{
184184
"Identity",
185-
"BasisState",
186-
"QubitStateVector",
187-
"StatePrep",
188185
"QubitUnitary",
189186
"ControlledQubitUnitary",
190187
"MultiControlledX",
@@ -292,13 +289,20 @@ def simulate_and_vjp(
292289

293290
def stopping_condition(op: Operator) -> bool:
294291
"""A function that determines whether or not an operation is supported by ``lightning.qubit``."""
292+
# These thresholds are adapted from `lightning_base.py`
293+
# To avoid building matrices beyond the given thresholds.
294+
# This should reduce runtime overheads for larger systems.
295+
if isinstance(op, qml.QFT):
296+
return len(op.wires) < 10
297+
if isinstance(op, qml.GroverOperator):
298+
return len(op.wires) < 13
295299
return op.name in _operations
296300

297301

298302
def stopping_condition_shots(op: Operator) -> bool:
299303
"""A function that determines whether or not an operation is supported by ``lightning.qubit``
300304
with finite shots."""
301-
return op.name in _operations or isinstance(op, (MidMeasureMP, qml.ops.op_math.Conditional))
305+
return stopping_condition(op) or isinstance(op, (MidMeasureMP, qml.ops.op_math.Conditional))
302306

303307

304308
def accepted_observables(obs: Operator) -> bool:
@@ -536,6 +540,7 @@ def preprocess(self, execution_config: ExecutionConfig = DefaultExecutionConfig)
536540
decompose,
537541
stopping_condition=stopping_condition,
538542
stopping_condition_shots=stopping_condition_shots,
543+
skip_initial_state_prep=True,
539544
name=self.name,
540545
)
541546
program.add_transform(qml.transforms.broadcast_expand)

tests/new_api/test_device.py

+60-3
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
adjoint_measurements,
3232
adjoint_observables,
3333
decompose,
34+
mid_circuit_measurements,
3435
no_sampling,
3536
stopping_condition,
3637
stopping_condition_shots,
@@ -258,13 +259,12 @@ def test_preprocess(self, adjoint):
258259
expected_program.add_transform(validate_measurements, name=device.name)
259260
expected_program.add_transform(validate_observables, accepted_observables, name=device.name)
260261
expected_program.add_transform(validate_device_wires, device.wires, name=device.name)
261-
expected_program.add_transform(
262-
qml.devices.preprocess.mid_circuit_measurements, device=device
263-
)
262+
expected_program.add_transform(mid_circuit_measurements, device=device)
264263
expected_program.add_transform(
265264
decompose,
266265
stopping_condition=stopping_condition,
267266
stopping_condition_shots=stopping_condition_shots,
267+
skip_initial_state_prep=True,
268268
name=device.name,
269269
)
270270
expected_program.add_transform(qml.transforms.broadcast_expand)
@@ -293,6 +293,63 @@ def test_preprocess(self, adjoint):
293293
actual_program, _ = device.preprocess(config)
294294
assert actual_program == expected_program
295295

296+
@pytest.mark.parametrize(
297+
"op, is_trainable",
298+
[
299+
(qml.StatePrep([1 / np.sqrt(2), 1 / np.sqrt(2)], wires=0), False),
300+
(qml.StatePrep(qml.numpy.array([1 / np.sqrt(2), 1 / np.sqrt(2)]), wires=0), True),
301+
(qml.StatePrep(np.array([1, 0]), wires=0), False),
302+
(qml.BasisState([1, 1], wires=[0, 1]), False),
303+
(qml.BasisState(qml.numpy.array([1, 1]), wires=[0, 1]), True),
304+
],
305+
)
306+
def test_preprocess_state_prep_first_op_decomposition(self, op, is_trainable):
307+
"""Test that state prep ops in the beginning of a tape are decomposed with adjoint
308+
but not otherwise."""
309+
tape = qml.tape.QuantumScript([op, qml.RX(1.23, wires=0)], [qml.expval(qml.PauliZ(0))])
310+
device = LightningDevice(wires=3)
311+
312+
if is_trainable:
313+
# Need to decompose twice as the state prep ops we use first decompose into a template
314+
decomp = op.decomposition()[0].decomposition()
315+
else:
316+
decomp = [op]
317+
318+
config = ExecutionConfig(gradient_method="best" if is_trainable else None)
319+
program, _ = device.preprocess(config)
320+
[new_tape], _ = program([tape])
321+
expected_tape = qml.tape.QuantumScript([*decomp, qml.RX(1.23, wires=0)], tape.measurements)
322+
assert qml.equal(new_tape, expected_tape)
323+
324+
@pytest.mark.parametrize(
325+
"op, decomp_depth",
326+
[
327+
(qml.StatePrep([1 / np.sqrt(2), 1 / np.sqrt(2)], wires=0), 1),
328+
(qml.StatePrep(np.array([1, 0]), wires=0), 1),
329+
(qml.BasisState([1, 1], wires=[0, 1]), 1),
330+
(qml.BasisState(qml.numpy.array([1, 1]), wires=[0, 1]), 1),
331+
(qml.AmplitudeEmbedding([1 / np.sqrt(2), 1 / np.sqrt(2)], wires=0), 2),
332+
(qml.MottonenStatePreparation([1 / np.sqrt(2), 1 / np.sqrt(2)], wires=0), 0),
333+
],
334+
)
335+
def test_preprocess_state_prep_middle_op_decomposition(self, op, decomp_depth):
336+
"""Test that state prep ops in the middle of a tape are always decomposed."""
337+
tape = qml.tape.QuantumScript(
338+
[qml.RX(1.23, wires=0), op, qml.CNOT([0, 1])], [qml.expval(qml.PauliZ(0))]
339+
)
340+
device = LightningDevice(wires=3)
341+
342+
for _ in range(decomp_depth):
343+
op = op.decomposition()[0]
344+
decomp = op.decomposition()
345+
346+
program, _ = device.preprocess()
347+
[new_tape], _ = program([tape])
348+
expected_tape = qml.tape.QuantumScript(
349+
[qml.RX(1.23, wires=0), *decomp, qml.CNOT([0, 1])], tape.measurements
350+
)
351+
assert qml.equal(new_tape, expected_tape)
352+
296353
@pytest.mark.usefixtures("use_legacy_and_new_opmath")
297354
@pytest.mark.parametrize("theta, phi", list(zip(THETA, PHI)))
298355
@pytest.mark.parametrize(

tests/test_adjoint_jacobian.py

+7
Original file line numberDiff line numberDiff line change
@@ -1481,6 +1481,13 @@ def convert_to_array_gpu(params):
14811481
def convert_to_array_gpu_default(params):
14821482
return np.hstack(qnode_gpu_default(params))
14831483

1484+
i_cpu = qnode_cpu(params)
1485+
i_gpu = qnode_gpu(params)
1486+
i_gpu_default = qnode_gpu_default(params)
1487+
1488+
assert np.allclose(i_cpu, i_gpu)
1489+
assert np.allclose(i_gpu, i_gpu_default)
1490+
14841491
j_cpu = qml.jacobian(qnode_cpu)(params)
14851492
j_gpu = qml.jacobian(qnode_gpu)(params)
14861493
j_gpu_default = qml.jacobian(qnode_gpu_default)(params)

tests/test_execute.py

-37
Original file line numberDiff line numberDiff line change
@@ -87,40 +87,3 @@ def dev_l_execute(t):
8787

8888
assert np.allclose(grad_dev_l, grad_qml_l, tol)
8989
assert np.allclose(grad_dev_l, grad_qml_d, tol)
90-
91-
92-
class TestGrover:
93-
"""Test Grover's algorithm (multi-controlled gates, decomposition, etc.)"""
94-
95-
@pytest.mark.parametrize("num_qubits", range(4, 8))
96-
def test_grover(self, num_qubits):
97-
np.random.seed(42)
98-
omega = np.random.rand(num_qubits) > 0.5
99-
dev = qml.device(device_name, wires=num_qubits)
100-
wires = list(range(num_qubits))
101-
102-
@qml.qnode(dev, diff_method=None)
103-
def circuit(omega):
104-
iterations = int(np.round(np.sqrt(2**num_qubits) * np.pi / 4))
105-
106-
# Initial state preparation
107-
for wire in wires:
108-
qml.Hadamard(wires=wire)
109-
110-
# Grover's iterator
111-
for _ in range(iterations):
112-
qml.FlipSign(omega, wires=wires)
113-
qml.templates.GroverOperator(wires)
114-
115-
return qml.probs(wires=wires)
116-
117-
prob = circuit(omega)
118-
index = omega.astype(int)
119-
index = functools.reduce(
120-
lambda sum, x: sum + (1 << x[0]) * x[1],
121-
zip([i for i in range(len(index) - 1, -1, -1)], index),
122-
0,
123-
)
124-
assert np.allclose(np.sum(prob), 1.0)
125-
assert prob[index] > 0.95
126-
assert np.sum(prob) - prob[index] < 0.05

0 commit comments

Comments
 (0)