Skip to content

Commit 9742ac5

Browse files
PietropaoloFrisoniactions-userandrijapauJerryChen97Alex-Preciado
authored
Merging dev into master (#1353)
Merging the dev branch into the master branch for the release of PL 0.41 --------- Co-authored-by: GitHub Nightly Merge Action <actions@github.com> Co-authored-by: Andrija Paurevic <46359773+andrijapau@users.noreply.github.com> Co-authored-by: Yushao Chen (Jerry) <chenys13@outlook.com> Co-authored-by: Alex Preciado <alex.preciado@xanadu.ai> Co-authored-by: Isaac De Vlugt <isaacdevlugt@gmail.com> Co-authored-by: Korbinian Kottmann <43949391+Qottmann@users.noreply.github.com> Co-authored-by: David Wierichs <davidwierichs@gmail.com> Co-authored-by: Josh Izaac <josh146@gmail.com> Co-authored-by: Isaac De Vlugt <34751083+isaacdevlugt@users.noreply.github.com> Co-authored-by: Christina Lee <chrissie.c.l@gmail.com>
1 parent 953ed1e commit 9742ac5

38 files changed

+404
-859
lines changed
Loading

demonstrations/tutorial_qnn_module_tf.metadata.json renamed to demonstrations/qnn_module_tf.metadata.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
}
77
],
88
"dateOfPublication": "2020-11-02T00:00:00+00:00",
9-
"dateOfLastModification": "2024-10-07T00:00:00+00:00",
9+
"dateOfLastModification": "2025-03-21T00:00:00+00:00",
1010
"categories": [
1111
"Devices and Performance",
1212
"Quantum Machine Learning"

demonstrations/tutorial_qnn_module_tf.py renamed to demonstrations/qnn_module_tf.py

+54-4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
"""
1+
r"""
2+
.. role:: html(raw)
3+
:format: html
4+
25
Turning quantum nodes into Keras Layers
36
=======================================
47
@@ -10,7 +13,13 @@
1013
1114
tutorial_qnn_module_torch Turning quantum nodes into Torch Layers
1215
13-
*Author: Tom Bromley — Posted: 02 November 2020. Last updated: 28 January 2021.*
16+
*Author: Tom Bromley — Posted: 02 November 2020. Last updated: 21 March 2025.*
17+
18+
.. warning::
19+
20+
This demo is only compatible with PennyLane version 0.40 or below.
21+
For usage with a later version of PennyLane, please consider using
22+
:doc:`PyTorch </demos/tutorial_qnn_module_torch>` or :doc:`JAX </demos/tutorial_How_to_optimize_QML_model_using_JAX_and_Optax>`.
1423
1524
Creating neural networks in `Keras <https://keras.io/>`__ is easy. Models are constructed from
1625
elementary *layers* and can be trained using a high-level API. For example, the following code
@@ -67,6 +76,11 @@
6776
plt.scatter(X[:, 0], X[:, 1], c=c)
6877
plt.show()
6978

79+
##############################################################################
80+
# .. figure:: /_static/demonstration_assets/qnn_module/sphx_glr_qnn_module_tf_001.png
81+
# :width: 100%
82+
# :align: center
83+
7084
###############################################################################
7185
# Defining a QNode
7286
# ----------------
@@ -166,7 +180,25 @@ def qnode(inputs, weights):
166180

167181
fitting = model.fit(X, y_hot, epochs=6, batch_size=5, validation_split=0.25, verbose=2)
168182

169-
###############################################################################
183+
##############################################################################
184+
# .. rst-class:: sphx-glr-script-out
185+
#
186+
# .. code-block:: none
187+
#
188+
# Epoch 1/6
189+
# 30/30 - 4s - loss: 0.4153 - accuracy: 0.7333 - val_loss: 0.3183 - val_accuracy: 0.7800 - 4s/epoch - 123ms/step
190+
# Epoch 2/6
191+
# 30/30 - 4s - loss: 0.2927 - accuracy: 0.8000 - val_loss: 0.2475 - val_accuracy: 0.8400 - 4s/epoch - 130ms/step
192+
# Epoch 3/6
193+
# 30/30 - 4s - loss: 0.2272 - accuracy: 0.8333 - val_loss: 0.2111 - val_accuracy: 0.8200 - 4s/epoch - 119ms/step
194+
# Epoch 4/6
195+
# 30/30 - 4s - loss: 0.1963 - accuracy: 0.8667 - val_loss: 0.1917 - val_accuracy: 0.8600 - 4s/epoch - 118ms/step
196+
# Epoch 5/6
197+
# 30/30 - 4s - loss: 0.1772 - accuracy: 0.8667 - val_loss: 0.1828 - val_accuracy: 0.8600 - 4s/epoch - 117ms/step
198+
# Epoch 6/6
199+
# 30/30 - 4s - loss: 0.1603 - accuracy: 0.8733 - val_loss: 0.2006 - val_accuracy: 0.8200 - 4s/epoch - 117ms/step
200+
#
201+
#
170202
# How did we do? The model looks to have successfully trained and the accuracy on both the
171203
# training and validation datasets is reasonably high. In practice, we would aim to push the
172204
# accuracy higher by thinking carefully about the model design and the choice of hyperparameters
@@ -224,7 +256,25 @@ def qnode(inputs, weights):
224256

225257
fitting = model.fit(X, y_hot, epochs=6, batch_size=5, validation_split=0.25, verbose=2)
226258

227-
###############################################################################
259+
##############################################################################
260+
# .. rst-class:: sphx-glr-script-out
261+
#
262+
# .. code-block:: none
263+
#
264+
# Epoch 1/6
265+
# 30/30 - 7s - loss: 0.3682 - accuracy: 0.7467 - val_loss: 0.2550 - val_accuracy: 0.8000 - 7s/epoch - 229ms/step
266+
# Epoch 2/6
267+
# 30/30 - 7s - loss: 0.2428 - accuracy: 0.8067 - val_loss: 0.2105 - val_accuracy: 0.8400 - 7s/epoch - 224ms/step
268+
# Epoch 3/6
269+
# 30/30 - 7s - loss: 0.2001 - accuracy: 0.8333 - val_loss: 0.1901 - val_accuracy: 0.8200 - 7s/epoch - 220ms/step
270+
# Epoch 4/6
271+
# 30/30 - 7s - loss: 0.1816 - accuracy: 0.8600 - val_loss: 0.1776 - val_accuracy: 0.8200 - 7s/epoch - 224ms/step
272+
# Epoch 5/6
273+
# 30/30 - 7s - loss: 0.1661 - accuracy: 0.8667 - val_loss: 0.1711 - val_accuracy: 0.8600 - 7s/epoch - 224ms/step
274+
# Epoch 6/6
275+
# 30/30 - 7s - loss: 0.1520 - accuracy: 0.8600 - val_loss: 0.1807 - val_accuracy: 0.8200 - 7s/epoch - 221ms/step
276+
#
277+
#
228278
# Great! We've mastered the basics of constructing hybrid classical-quantum models using
229279
# PennyLane and Keras. Can you think of any interesting hybrid models to construct? How do they
230280
# perform on realistic datasets?

demonstrations/tutorial_classically_boosted_vqe.metadata.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
}
1010
],
1111
"dateOfPublication": "2022-10-31T00:00:00+00:00",
12-
"dateOfLastModification": "2024-12-13T00:00:00+00:00",
12+
"dateOfLastModification": "2025-01-23T00:00:00+00:00",
1313
"categories": [
1414
"Quantum Chemistry"
1515
],

demonstrations/tutorial_classically_boosted_vqe.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -449,7 +449,7 @@ def hadamard_test(Uq, Ucl, component="real"):
449449

450450
qml.Hadamard(wires=[0])
451451
qml.ControlledQubitUnitary(
452-
Uq.conjugate().T @ Ucl, control_wires=[0], wires=wires[1:]
452+
Uq.conjugate().T @ Ucl, wires=wires
453453
)
454454
qml.Hadamard(wires=[0])
455455

demonstrations/tutorial_fixed_depth_hamiltonian_simulation_via_cartan_decomposition.metadata.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
}
77
],
88
"dateOfPublication": "2024-12-19T00:00:00+00:00",
9-
"dateOfLastModification": "2025-01-10T00:00:00+00:00",
9+
"dateOfLastModification": "2025-04-11T00:00:00+00:00",
1010
"categories": [
1111
"Quantum Computing",
1212
"Algorithms"

demonstrations/tutorial_fixed_depth_hamiltonian_simulation_via_cartan_decomposition.py

+29-85
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
Introduction
2121
------------
2222
23-
The :doc:`KAK theorem </demos/tutorial_kak_decomposition>` is an important result from Lie theory that states that any Lie group element :math:`U` can be decomposed
23+
The :doc:`KAK decomposition </demos/tutorial_kak_decomposition>` is an important result from Lie theory that states that any Lie group element :math:`U` can be decomposed
2424
as :math:`U = K_1 A K_2,` where :math:`K_{1, 2}` and :math:`A` are elements of two special sub-groups
2525
:math:`\mathcal{K}` and :math:`\mathcal{A},` respectively. In special cases, the decomposition simplifies to :math:`U = K A K^\dagger.`
2626
@@ -36,14 +36,14 @@
3636
We can use this general result from Lie theory as a powerful circuit decomposition technique.
3737
3838
.. note:: We recommend a basic understanding of Lie algebras, see e.g. our :doc:`introduction to (dynamical) Lie algebras for quantum practitioners </demos/tutorial_liealgebra>`.
39-
Otherwise, this demo should be self-contained, though for the mathematically inclined, we further recommend our :doc:`demo on the KAK theorem </demos/tutorial_kak_decomposition>`
40-
that dives into the mathematical depths of the theorem and provides more background info.
39+
Otherwise, this demo should be self-contained, though for the mathematically inclined, we further recommend our :doc:`demo on the KAK decomposition </demos/tutorial_kak_decomposition>`
40+
that dives into the mathematical depths of the decomposition and provides more background info.
4141
4242
Goal: Fast-forwarding time evolutions using the KAK decomposition
4343
-----------------------------------------------------------------
4444
4545
Unitary gates in quantum computing are described by the special unitary Lie group :math:`SU(2^n),` so we can use the KAK
46-
theorem to decompose quantum gates into :math:`U = K_1 A K_2.` While the mathematical statement is rather straightforward,
46+
decomposition to factorize quantum gates into :math:`U = K_1 A K_2.` While the mathematical statement is rather straightforward,
4747
actually finding this decomposition is not. We are going to follow the recipe prescribed in
4848
`Fixed Depth Hamiltonian Simulation via Cartan Decomposition <https://arxiv.org/abs/2104.00728>`__ [#Kökcü]_,
4949
which tackles this decomposition on the level of the associated Lie algebra via Cartan decomposition.
@@ -74,6 +74,7 @@
7474
import numpy as np
7575
import pennylane as qml
7676
from pennylane import X, Y, Z
77+
from pennylane.liealg import even_odd_involution, cartan_decomp, horizontal_cartan_subalgebra
7778

7879
import jax
7980
import jax.numpy as jnp
@@ -111,10 +112,8 @@
111112
# One common choice of involution is the so-called even-odd involution for Pauli words,
112113
# :math:`P = P_1 \otimes P_2 .. \otimes P_n,` where :math:`P_j \in \{I, X, Y, Z\}.`
113114
# It essentially counts whether the number of non-identity Pauli operators in the Pauli word is even or odd.
114-
115-
def even_odd_involution(op):
116-
[pw] = op.pauli_rep
117-
return len(pw) % 2
115+
# It is readily available in PennyLane as :func:`~.pennylane.liealg.even_odd_involution`, which
116+
# we already imported above.
118117

119118
even_odd_involution(X(0)), even_odd_involution(X(0) @ Y(3))
120119

@@ -126,30 +125,9 @@ def even_odd_involution(op):
126125
# sort the operators by whether or not they yield a plus or minus sign from the involution function.
127126
# This is possible because the operators and involution nicely align with the eigenspace decomposition.
128127

129-
def cartan_decomposition(g, involution):
130-
"""Cartan Decomposition g = k + m
131-
132-
Args:
133-
g (List[PauliSentence]): the (dynamical) Lie algebra to decompose
134-
involution (callable): Involution function :math:`\Theta(\cdot)` to act on PauliSentence ops, should return ``0/1`` or ``True/False``.
135-
136-
Returns:
137-
k (List[PauliSentence]): the vertical subspace :math:`\Theta(x) = x`
138-
m (List[PauliSentence]): the horizontal subspace :math:`\Theta(x) = -x` """
139-
m = []
140-
k = []
141-
142-
for op in g:
143-
if involution(op): # vertical space when involution returns True
144-
k.append(op)
145-
else: # horizontal space when involution returns False
146-
m.append(op)
147-
return k, m
148-
149-
k, m = cartan_decomposition(g, even_odd_involution)
128+
k, m = cartan_decomp(g, even_odd_involution)
150129
len(g), len(k), len(m)
151130

152-
153131
##############################################################################
154132
# We have successfully decomposed the 60-dimensional Lie algebra
155133
# into a 24-dimensional vertical subspace and a 36-dimensional subspace.
@@ -187,51 +165,11 @@ def cartan_decomposition(g, involution):
187165
# that commute with it.
188166
#
189167
# We then obtain a further split of the vector space :math:`\mathfrak{m} = \tilde{\mathfrak{m}} \oplus \mathfrak{h},`
190-
# where :math:`\tilde{\mathfrak{m}}` is just the remainder of :math:`\mathfrak{m}.`
168+
# where :math:`\tilde{\mathfrak{m}}` is just the remainder of :math:`\mathfrak{m}.` The function
169+
# :func:`~.pennylane.liealg.horizontal_cartan_subalgebra` returns some additional information, which we will
170+
# not use here.
191171

192-
def _commutes_with_all(candidate, ops):
193-
r"""Check if ``candidate`` commutes with all ``ops``"""
194-
for op in ops:
195-
com = candidate.commutator(op)
196-
com.simplify()
197-
198-
if not len(com) == 0:
199-
return False
200-
return True
201-
202-
def cartan_subalgebra(m, which=0):
203-
"""Compute the Cartan subalgebra from the horizontal subspace :math:`\mathfrak{m}`
204-
of the Cartan decomposition
205-
206-
This implementation is specific for cases of bases of m with pure Pauli words as
207-
detailed in Appendix C in `2104.00728 <https://arxiv.org/abs/2104.00728>`__.
208-
209-
Args:
210-
m (List[PauliSentence]): the horizontal subspace :math:`\Theta(x) = -x
211-
which (int): Choice for the initial element of m from which to construct
212-
the maximal Abelian subalgebra
213-
214-
Returns:
215-
mtilde (List): remaining elements of :math:`\mathfrak{m}`
216-
s.t. :math:`\mathfrak{m} = \tilde{\mathfrak{m}} \oplus \mathfrak{h}`.
217-
h (List): Cartan subalgebra :math:`\mathfrak{h}`.
218-
219-
"""
220-
221-
h = [m[which]] # first candidate
222-
mtilde = m.copy()
223-
224-
for m_i in m:
225-
if _commutes_with_all(m_i, h):
226-
if m_i not in h:
227-
h.append(m_i)
228-
229-
for h_i in h:
230-
mtilde.remove(h_i)
231-
232-
return mtilde, h
233-
234-
mtilde, h = cartan_subalgebra(m)
172+
g, k, mtilde, h, _ = horizontal_cartan_subalgebra(k, m, tol=1e-8)
235173
len(g), len(k), len(mtilde), len(h)
236174

237175
##############################################################################
@@ -241,7 +179,7 @@ def cartan_subalgebra(m, which=0):
241179
# Variational KhK decomposition
242180
# -----------------------------
243181
#
244-
# The KAK theorem is not constructive in the sense that it proves that there exists such a decomposition, but there is no general way of obtaining
182+
# The KAK decomposition is not constructive in the sense that it proves that there exists such a decomposition, but there is no general way of obtaining
245183
# it. In particular, there are no linear algebra subroutines implemented in ``numpy`` or ``scipy`` that just compute it for us.
246184
# Here, we follow the construction of [#Kökcü]_ for the special case of :math:`H` being in the horizontal space and the decomposition
247185
# simplifying to :math:`H = K^\dagger h K`.
@@ -282,10 +220,10 @@ def cartan_subalgebra(m, which=0):
282220
#
283221

284222
def run_opt(
285-
value_and_grad,
223+
loss,
286224
theta,
287-
n_epochs=500,
288-
lr=0.1,
225+
n_epochs=1000,
226+
lr=0.05,
289227
):
290228
"""Boilerplate JAX optimization"""
291229
value_and_grad = jax.jit(jax.value_and_grad(loss))
@@ -336,7 +274,7 @@ def loss(theta):
336274
A = K_m @ v_m @ K_m.conj().T
337275
return jnp.trace(A.conj().T @ H_m).real
338276

339-
theta0 = jnp.ones(len(k), dtype=float)
277+
theta0 = jax.random.normal(jax.random.PRNGKey(0), shape=(len(k),), dtype=float)
340278

341279
thetas, energy, _ = run_opt(loss, theta0, n_epochs=1000, lr=0.05)
342280
plt.plot(energy - np.min(energy))
@@ -359,13 +297,18 @@ def loss(theta):
359297
# .. math:: h_0 = K_c^\dagger H K_c.
360298

361299
h_0_m = Kc_m.conj().T @ H_m @ Kc_m
362-
h_0 = qml.pauli_decompose(h_0_m)
363300

364-
print(len(h_0))
301+
# decompose h_0_m in terms of the basis of h
302+
basis = [qml.matrix(op, wire_order=range(n_wires)) for op in h]
303+
coeffs = qml.pauli.trace_inner_product(h_0_m, basis)
304+
305+
# ensure that decomposition is correct, i.e. h_0_m is truely an element of just h
306+
h_0_m_recomposed = np.sum([c * op for c, op in zip(coeffs, basis)], axis=0)
307+
print("Decomposition of h_0 is faithful: ", np.allclose(h_0_m_recomposed, h_0_m, atol=1e-10))
308+
309+
# sanity check that the horizontal CSA is Abelian, i.e. all its elements commute
310+
print("All elements in h commute with each other: ", qml.liealg.check_abelian(h))
365311

366-
# assure that h_0 is in \mathfrak{h}
367-
h_vspace = qml.pauli.PauliVSpace(h)
368-
not h_vspace.is_independent(h_0.pauli_rep)
369312

370313
##############################################################################
371314
#
@@ -393,6 +336,7 @@ def loss(theta):
393336
t = 1.
394337
U_exact = qml.exp(-1j * t * H)
395338
U_exact_m = qml.matrix(U_exact, wire_order=range(n_wires))
339+
h_0 = qml.dot(coeffs, h)
396340

397341
def U_kak(theta_opt, t):
398342
qml.adjoint(K)(theta_opt, k)
@@ -478,7 +422,7 @@ def compute_res(Us):
478422
# Conclusion
479423
# ----------
480424
#
481-
# The KAK theorem is a very general mathematical result with far-reaching consequences.
425+
# The KAK decomposition is a very general mathematical result with far-reaching consequences.
482426
# While there is no canonical way of obtaining an actual decomposition in practice, we followed
483427
# the approach of [#Kökcü]_ which uses a specifically designed loss function and variational
484428
# optimization to find the decomposition.

demonstrations/tutorial_kak_decomposition.metadata.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
}
77
],
88
"dateOfPublication": "2024-11-25T00:00:00+00:00",
9-
"dateOfLastModification": "2025-01-07T09:00:00+00:00",
9+
"dateOfLastModification": "2025-03-26T09:00:00+00:00",
1010
"categories": [
1111
"Quantum Computing",
1212
"Algorithms"

demonstrations/tutorial_kak_decomposition.py

+6-5
Original file line numberDiff line numberDiff line change
@@ -348,14 +348,15 @@ def is_orthogonal(op, basis):
348348
#
349349
# Let us define it in code, and check whether it gives rise to a Cartan decomposition.
350350
# As we want to look at another example later, we wrap everything in a function.
351+
# A similar function is available in PennyLane as :func:`~.pennylane.liealg.check_cartan_decomp`.
351352
#
352353

353354

354-
def check_cartan_decomposition(g, k, space_name):
355+
def check_cartan_decomp(g, k, space_name):
355356
"""Given an algebra g and an operator subspace k, verify that k is a subalgebra
356-
and gives rise to a Cartan decomposition."""
357+
and gives rise to a Cartan decomposition. Similar to qml.liealg.check_cartan_decomp"""
357358
# Check Lie closure of k
358-
k_lie_closure = qml.pauli.dla.lie_closure(k)
359+
k_lie_closure = qml.lie_closure(k)
359360
k_is_closed = len(k_lie_closure) == len(k)
360361
print(f"The Lie closure of k is as big as k itself: {k_is_closed}.")
361362

@@ -387,7 +388,7 @@ def check_cartan_decomposition(g, k, space_name):
387388

388389
u1 = [Z(0)]
389390
space_name = "SU(2)/U(1)"
390-
p = check_cartan_decomposition(su2, u1, space_name)
391+
p = check_cartan_decomp(su2, u1, space_name)
391392

392393
######################################################################
393394
# Cartan subalgebras
@@ -811,7 +812,7 @@ def theta_Y(x):
811812
# Define subalgebra su(2) ⊕ su(2)
812813
su2_su2 = [X(0), Y(0), Z(0), X(1), Y(1), Z(1)]
813814
space_name = "SU(4)/(SU(2)xSU(2))"
814-
p = check_cartan_decomposition(su4, su2_su2, space_name)
815+
p = check_cartan_decomp(su4, su2_su2, space_name)
815816

816817
######################################################################
817818
# .. admonition:: Math detail: involution for two-qubit decomposition

demonstrations/tutorial_liesim.metadata.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
}
77
],
88
"dateOfPublication": "2024-06-07T00:00:00+00:00",
9-
"dateOfLastModification": "2024-10-07T00:00:00+00:00",
9+
"dateOfLastModification": "2025-03-17T00:00:00+00:00",
1010
"categories": [
1111
"Quantum Computing",
1212
"Getting Started"

0 commit comments

Comments
 (0)