Release 0.41.0
New features since last release
Resource-efficient Decompositions 🔎
A new, experimental graph-based decomposition system is now available in PennyLane, offering more resource-efficiency and versatility. (#6950) (#6951) (#6952) (#7028) (#7045) (#7058) (#7064) (#7149) (#7184) (#7223) (#7263)
PennyLane's new experimental graph decomposition system offers a resource-efficient and versatile alternative to the current system. This is done by traversing an internal graph structure that is weighted by the resources (e.g., gate counts) required to decompose down to a given set of gates.
The graph-based system is experimental and is disabled by default, but it can be enabled by adding qml.decomposition.enable_graph() to the top of your program. Conversely, qml.decomposition.disable_graph() disables the new system from being active.
With qml.decomposition.enable_graph(), the following new features are available:
-
Operators in PennyLane can now accommodate multiple decompositions, which can be queried with the new
qml.list_decompsfunction:>>> import pennylane as qml >>> qml.decomposition.enable_graph() >>> qml.list_decomps(qml.CRX) [<pennylane.decomposition.decomposition_rule.DecompositionRule at 0x136da9de0>, <pennylane.decomposition.decomposition_rule.DecompositionRule at 0x136da9db0>, <pennylane.decomposition.decomposition_rule.DecompositionRule at 0x136da9f00>] >>> decomp_rule = qml.list_decomps(qml.CRX)[0] >>> print(decomp_rule, "\n") @register_resources(_crx_to_rx_cz_resources) def _crx_to_rx_cz(phi: TensorLike, wires: WiresLike, **__): qml.RX(phi / 2, wires=wires[1]) qml.CZ(wires=wires) qml.RX(-phi / 2, wires=wires[1]) qml.CZ(wires=wires) >>> print(qml.draw(decomp_rule)(0.5, wires=[0, 1])) 0: ───────────╭●────────────╭●─┤ 1: ──RX(0.25)─╰Z──RX(-0.25)─╰Z─┤
When an operator within a circuit needs to be decomposed (e.g., when
qml.transforms.decomposeis present), the chosen decomposition rule is that which is most resource efficient (results in the smallest gate count). -
New decomposition rules can be globally added to operators in PennyLane with the new
qml.add_decompsfunction. Creating a valid decomposition rule requires:- Defining a quantum function that represents the decomposition.
- Adding resource requirements (gate counts) to the above quantum function by decorating it with the new
qml.register_resourcesfunction, which requires a dictionary mapping operator types present in the quantum function to their number of occurrences.
qml.decomposition.enable_graph() @qml.register_resources({qml.H: 2, qml.CZ: 1}) def my_cnot(wires): qml.H(wires=wires[1]) qml.CZ(wires=wires) qml.H(wires=wires[1]) qml.add_decomps(qml.CNOT, my_cnot)
This newly added rule for
qml.CNOTcan be verified as being available to use:>>> my_new_rule = qml.list_decomps(qml.CNOT)[-1] >>> print(my_new_rule) @qml.register_resources({qml.H: 2, qml.CZ: 1}) def my_cnot(wires): qml.H(wires=wires[1]) qml.CZ(wires=wires) qml.H(wires=wires[1])
Operators with dynamic resource requirements must be declared in a resource estimate using the new
qml.resource_repfunction. For each operator class, the set of parameters that affects the type of gates and their number of occurrences in its decompositions is given by theresource_keysattribute.>>> qml.MultiRZ.resource_keys {'num_wires'}The output of
resource_keysindicates that custom decompositions for the operator should be registered to a resource function (as opposed to a static dictionary) that accepts those exact arguments and returns a dictionary. Consider this dummy example of a fictitious decomposition rule comprising threeqml.MultiRZgates:qml.decomposition.enable_graph() def resource_fn(num_wires): return { qml.resource_rep(qml.MultiRZ, num_wires=num_wires - 1): 1, qml.resource_rep(qml.MultiRZ, num_wires=3): 2 } @qml.register_resources(resource_fn) def my_decomp(theta, wires): qml.MultiRZ(theta, wires=wires[:3]) qml.MultiRZ(theta, wires=wires[1:]) qml.MultiRZ(theta, wires=wires[:3])
More information for defining complex decomposition rules can be found in the documentation for
qml.register_resources. -
The
qml.transforms.decomposetransform works when the new decompositions system is enabled and offers the ability to inject new decomposition rules for operators in QNodes. (#6966)With the graph-based system enabled, the
qml.transforms.decomposetransform offers the ability to inject new decomposition rules via two new keyword arguments:fixed_decomps: decomposition rules provided to this keyword argument will be used by the new system, bypassing all other decomposition rules that may exist for the relevant operators.alt_decomps: decomposition rules provided to this keyword argument are alternative decomposition rules that the new system may choose if they're the most resource efficient.
Each keyword argument must be assigned a dictionary that maps operator types to decomposition rules. Here is an example of both keyword arguments in use:
qml.decomposition.enable_graph() @qml.register_resources({qml.CNOT: 2, qml.RX: 1}) def my_isingxx(phi, wires, **__): qml.CNOT(wires=wires) qml.RX(phi, wires=[wires[0]]) qml.CNOT(wires=wires) @qml.register_resources({qml.H: 2, qml.CZ: 1}) def my_cnot(wires, **__): qml.H(wires=wires[1]) qml.CZ(wires=wires) qml.H(wires=wires[1]) @partial( qml.transforms.decompose, gate_set={"RX", "RZ", "CZ", "GlobalPhase"}, alt_decomps={qml.CNOT: my_cnot}, fixed_decomps={qml.IsingXX: my_isingxx}, ) @qml.qnode(qml.device("default.qubit")) def circuit(): qml.CNOT(wires=[0, 1]) qml.IsingXX(0.5, wires=[0, 1]) return qml.state()
>>> circuit() array([ 9.68912422e-01+2.66934210e-16j, -1.57009246e-16+3.14018492e-16j, 8.83177008e-17-2.94392336e-17j, 5.44955495e-18-2.47403959e-01j])More details about what
fixed_decompsandalt_decompsdo can be found in the usage details section in theqml.transforms.decomposedocumentation.
Capturing and Representing Hybrid Programs 📥
Quantum operations, classical processing, structured control flow, and dynamicism can be efficiently expressed with program capture (enabled with qml.capture.enable).
In the last several releases of PennyLane, we have been working on a new and experimental feature called
program capture, which allows for compactly expressing details about hybrid workflows while also providing a smoother integration with just-in-time compilation frameworks like Catalyst (via the ~.pennylane.qjit decorator) and JAX-jit.
Internally, program capture is supported by representing hybrid programs via a new intermediate representation (IR) called plxpr, rather than a quantum tape. The plxpr IR is an adaptation of JAX's jaxpr IR.
There are some quirks and restrictions to be aware of, which are outlined in the :doc:/news/program_capture_sharp_bits page. But with this release, many of the core features of PennyLane—and more!—are available with program capture enabled by adding qml.capture.enable() to the top of your program:
-
QNodes can now contain mid-circuit measurements (MCMs) and classical processing on MCMs with
mcm_method = "deferred"when program capture is enabled. (#6838) (#6937) (#6961)With
mcm_method = "deferred", workflows with mid-circuit measurements can be executed with program capture enabled. Additionally, program capture unlocks the ability to classically process MCM values and use MCM values as gate parameters.import jax import jax.numpy as jnp jax.config.update("jax_enable_x64", True) qml.capture.enable() @qml.qnode(qml.device("default.qubit", wires=3), mcm_method="deferred") def f(x): m0 = qml.measure(0) m1 = qml.measure(0) # classical processing on m0 and m1 a = jnp.sin(0.5 * jnp.pi * m0) phi = a - (m1 + 1) ** 4 qml.s_prod(x, qml.RX(phi, 0)) return qml.expval(qml.Z(0))
>>> f(0.1) Array(0.00540302, dtype=float64)Note that with capture enabled, automatic qubit management is not supported on devices; the number of wires given to the device must coincide with how many MCMs are present in the circuit, since deferred measurements add one auxiliary qubit per MCM.
-
Quantum circuits can now be differentiated on
default.qubitandlightning.qubitwithdiff_method="finite-diff","adjoint", and"backprop"when program capture is enabled. (#6853) (#6875) (#7019)qml.capture.enable() @qml.qnode(qml.device("default.qubit", wires=3), diff_method="adjoint") def f(phi): qml.RX(phi, 0) return qml.expval(qml.Z(0))
>>> qml.grad(f)(jnp.array(0.1)) Array(-0.09983342, dtype=float64, weak_type=True) -
QNode arguments can now be assigned as static. In turn, PennyLane can now determine when plxpr needs to be reconstructed based on dynamic and static arguments, providing efficiency for repeated circuit executions. (#6923)
Specifying static arguments can be done at the QNode level with the
static_argnumskeyword argument. Its values (integers) indicate which arguments are to be treated as static. By default, all QNode arguments are dynamic.Consider the following example, where the first argument is indicated to be static:
qml.capture.enable() @qml.qnode(qml.device("default.qubit", wires=1), static_argnums=0) def f(x, y): print("I constructed plxpr") qml.RX(x, 0) qml.RY(y, 0) return qml.expval(qml.Z(0))
When the value of
xchanges, PennyLane must (re)construct the plxpr representation of the program for the program to execute. In this example, the act of (re)constructing plxpr is indicated by theprintstatement executing. However, if the value ofychanges (a dynamic argument), PennyLane does not need to reconstruct the plxpr representation of the program:>>> f(0.1, 0.2) I constructed plxpr 0.97517043 >>> f(0.1, 0.3) 0.9505638 >>> f(0.2, 0.3) I constructed plxpr 0.93629336
-
All PennyLane transforms that return one device execution are compatible with program capture, including those without a plxpr-native implementation. (#6916) (#6922) (#6925) (#6945) (#6946) (#6957) (#6977) (#7020) (#7199) (#7247)
The following transforms now have native support for program capture (i.e., they directly transform
plxpr) and can be used as you would normally use a transform in PennyLane:merge_rotationssingle_qubit_fusionunitary_to_rotmerge_amplitude_embeddingcommute_controlleddecomposemap_wirescancel_inverses
Other transforms without a plxpr-native implementation are also supported by internally converting the tape implementation. As an example, here is a custom tape transform that shifts every
qml.RXgate to the end of the circuit:qml.capture.enable() @qml.transform def shift_rx_to_end(tape): """Transform that moves all RX gates to the end of the operations list.""" new_ops, rxs = [], [] for op in tape.operations: if isinstance(op, qml.RX): rxs.append(op) else: new_ops.append(op) operations = new_ops + rxs new_tape = tape.copy(operations=operations) return [new_tape], lambda res: res[0] @shift_rx_to_end @qml.qnode(qml.device("default.qubit", wires=1)) def circuit1(): qml.RX(0.1, wires=0) qml.H(wires=0) return qml.state() @qml.qnode(qml.device("default.qubit", wires=1)) def circuit2(): qml.H(wires=0) qml.RX(0.1, wires=0) return qml.state()
>>> circuit1() == circuit2() Array([ True, True], dtype=bool)There are some exceptions to getting transforms without a plxpr-native implementation to work with capture enabled:
- Transforms that return multiple device executions cannot be converted.
- Transforms that return non-trivial post-processing functions cannot be converted.
- Transforms will fail to execute if the transformed quantum function or QNode contains:
qml.condwith dynamic parameters as predicates.qml.for_loopwith dynamic parameters forstart,stop, orstep.qml.while_loop.
-
Python control flow (
if/else,for,while) is now supported when program capture is enabled. (#6837) (#6931)One of the strengths of program capture is preserving a program's structure, eliminating the need to unroll control flow operations. In previous releases, this could only be accomplished by using
qml.for_loop,qml.cond, andqml.while_loop. With this release, standard Python control flow operations are now supported with program capture:qml.capture.enable() dev = qml.device("default.qubit", wires=[0, 1, 2]) @qml.qnode(dev) def circuit(num_loops: int): for i in range(num_loops): if i % 2 == 0: qml.H(i) else: qml.RX(1, i) return qml.state()
>>> print(qml.draw(circuit)(num_loops=3)) 0: ──H────────┤ State 1: ──RX(1.00)─┤ State 2: ──H────────┤ State >>> circuit(3) Array([0.43879125+0.j , 0.43879125+0.j , 0. -0.23971277j, 0. -0.23971277j, 0.43879125+0.j , 0.43879125+0.j , 0. -0.23971277j, 0. -0.23971277j], dtype=complex64)
This is enabled internally by diastatic-malt, which is our home-brewed implementation of Autograph.
Support for this can be disabled by setting
autograph=Falseat the QNode level (by default,autograph=True).
End-to-end Sparse Execution 🌌
-
Sparse data structures in compressed-sparse-row (csr) format are now supported end-to-end in PennyLane, resulting in faster execution for large sparse objects. (#6883) (#7139) (#7191)
Sparse-array input and execution are now supported on
default.qubitandlightning.qubitwith a variety of templates, preserving sparsity throughout the entire simulation.Specifically, the following templates now support sparse data structures:
qml.StatePrep(#6863)qml.QubitUnitary(#6889) (#6986) (#7143)qml.BlockEncode(#6963) (#7140)qml.SWAP(#6965)Controlledoperations (#6994)
import scipy import numpy as np sparse_state = scipy.sparse.csr_array([0, 1, 0, 0]) mat = np.kron(np.identity(2**12), qml.X.compute_matrix()) sparse_mat = scipy.sparse.csr_array(mat) sparse_x = scipy.sparse.csr_array(qml.X.compute_matrix()) dev = qml.device("default.qubit") @qml.qnode(dev) def circuit(): qml.StatePrep(sparse_state, wires=range(2)) for i in range(10): qml.H(i) qml.CNOT(wires=[i, i + 1]) qml.QubitUnitary(sparse_mat, wires=range(13)) qml.ctrl(qml.QubitUnitary(sparse_x, wires=0), control=1) return qml.state()
>>> circuit() array([ 0. +0.j, 0.03125+0.j, 0. +0.j, ..., -0.03125+0.j, 0. +0.j, 0. +0.j]) -
Operators that have a
sparse_matrixmethod can now choose a sparse-matrix format and the order of their wires. Availablescipysparse-matrix formats include'bsr','csr','csc','coo','dia','dok', and'lil'. (#6995)>>> op = qml.CNOT([0,1]) >>> type(op.sparse_matrix(format='dok')) scipy.sparse._dok.dok_matrix >>> type(op.sparse_matrix(format='csc')) scipy.sparse._csc.csc_matrix >>> print(op.sparse_matrix(wire_order=[1,0])) (0, 0) 1.0 (1, 3) 1.0 (2, 2) 1.0 (3, 1) 1.0 >>> print(op.sparse_matrix(wire_order=[0,1])) (0, 0) 1 (1, 1) 1 (2, 3) 1 (3, 2) 1
-
Sparse functionality is now available in
qml.math:
QROM State Preparation 📖
-
A new state-of-the-art state preparation technique based on QROM is now available with the
qml.QROMStatePreparationtemplate. (#6974)Using
qml.QROMStatePreparationis analogous to using other state preparation techniques in PennyLane.state_vector = np.array([0.5, -0.5, 0.5, 0.5]) dev = qml.device("default.qubit") wires = qml.registers({"work_wires": 1, "prec_wires": 3, "state_wires": 2}) @qml.qnode(dev) def circuit(): qml.QROMStatePreparation( state_vector, wires["state_wires"], wires["prec_wires"], wires["work_wires"] ) return qml.state()
>>> print(circuit()[:4].real) [ 0.5 -0.5 0.5 0.5]
Dynamical Lie Algebras 🕓
The new qml.liealg module provides a variety of Lie algebra functionality:
-
Compute the dynamical Lie algebra from a set of generators with
qml.lie_closure. This function accepts and outputs matrices whenmatrix=True.import pennylane as qml from pennylane import X, Y, Z, I n = 2 gens = [qml.X(0), qml.X(0) @ qml.X(1), qml.Y(1)] dla = qml.lie_closure(gens)
>>> dla [X(0), X(0) @ X(1), Y(1), X(0) @ Z(1)]
-
Compute the structure constants that make up the adjoint representation of a Lie algebra using
qml.structure_constants. This function accepts and outputs matrices whenmatrix=True.>>> adjoint_rep = qml.structure_constants(dla) >>> adjoint_rep.shape (4, 4, 4) -
The center of a Lie algebra, which is the collection of operators that commute with all other operators in the DLA, can be found with
qml.center.>>> qml.center(dla) [X(0)]
-
Cartan decompositions,
g = k + m, can be performed withqml.liealg.cartan_decomp. These use involution functions that return a boolean value. A variety of typically encountered involution functions are included in the module, such aseven_odd_involution, concurrence_involution, A, AI, AII, AIII, BD, BDI, DIII, C, CI, CII.from pennylane.liealg import concurrence_involution k, m = qml.liealg.cartan_decomp(dla, concurrence_involution)
>>> k, m ([Y(1)], [X(0), X(0) @ X(1), X(0) @ Z(1)])
The vertical subspace
kandmfulfill the commutation relations[k, m] ⊆ m,[k, k] ⊆ kand[m, m] ⊆ kthat make them a proper Cartan decomposition. These can be verified using the functionqml.liealg.check_cartan_decomp.>>> qml.liealg.check_cartan_decomp(k, m) True
-
The horizontal Cartan subalgebra
aofmcan be computed withqml.liealg.horizontal_cartan_subalgebra.from pennylane.liealg import horizontal_cartan_subalgebra newg, k, mtilde, a, new_adj = horizontal_cartan_subalgebra(k, m, return_adjvec=True)
>>> newg.shape, k.shape, mtilde.shape, a.shape, new_adj.shape ((4, 4), (1, 4), (1, 4), (2, 4), (4, 4, 4))
newgis ordered such that the elements arenewg = k + mtilde + a, wheremtildeis the remainder ofmwithouta. A Cartan subalgebra is an Abelian subalgebra ofm, and we can confirm that all elements inaare mutually commuting viaqml.liealg.check_abelian.>>> qml.liealg.check_abelian(a) True
-
The following functions have also been added:
qml.liealg.check_commutation_relation(A, B, C)checks if all commutators betweenAandBmap to a subspace ofC, i.e.[A, B] ⊆ C.qml.liealg.adjvec_to_opandqml.liealg.op_to_adjvecallow transforming operators within a Lie algebra to and from their adjoint vector representations.qml.liealg.change_basis_ad_repallows the transformation of an adjoint representation tensor according to a basis transformation on the underlying Lie algebra, without re-computing the representation.qml.pauli.trace_inner_productcan handle batches of dense matrices.
(#6811) (#6861) (#6935) (#7026) (#7054) (#7129)
Qualtran Integration 🔗
-
It's now possible to use Qualtran bloqs in PennyLane with the new
qml.FromBloqclass. (#7148)qml.FromBloqtranslates Qualtran bloqs into equivalent PennyLane operators. It requires two inputs:bloq: an initialized Qualtran Bloqwires: the wires the operator acts on
The following example applies a PennyLane Operator and Qualtran Bloq in the same circuit:
>>> from qualtran.bloqs.basic_gates import CNOT >>> dev = qml.device("default.qubit") >>> @qml.qnode(dev) ... def circuit(): ... qml.X(wires=0) ... qml.FromBloq(CNOT(), wires=[0, 1]) ... return qml.state() >>> circuit() array([0.+0.j, 0.+0.j, 0.+0.j, 1.+0.j])
-
A new function called
qml.bloq_registersis available to help determine the required wires for complex QualtranBloqswith multiple registers. (#7148)Given a Qualtran Bloq, this function returns a dictionary of register names and wires.
>>> from qualtran.bloqs.phase_estimation import RectangularWindowState, TextbookQPE >>> from qualtran.bloqs.basic_gates import ZPowGate >>> textbook_qpe = TextbookQPE(ZPowGate(exponent=2 * 0.234), RectangularWindowState(3)) >>> registers = qml.bloq_registers(textbook_qpe) >>> registers {'q': Wires([0]), 'qpe_reg': Wires([1, 2, 3])}
In the following example, we use these registers to measure the correct qubits in quantum phase estimation:
dev = qml.device("default.qubit") @qml.qnode(dev) def circuit(): qml.FromBloq(textbook_qpe, wires=range(textbook_qpe.signature.n_qubits())) return qml.probs(wires=registers['qpe_reg'])
>>> print(qml.draw(circuit)()) 0: ─╭FromBloq─┤ 1: ─├FromBloq─┤ ╭Probs 2: ─├FromBloq─┤ ├Probs 3: ─╰FromBloq─┤ ╰Probs
Hadamard Gradient Variants and Improvements 🌈
-
Variants of the Hadamard gradient outlined in arXiv:2408.05406 have been added as new differentiation methods:
diff_method="reversed-hadamard","direct-hamadard", and"reversed-direct-hadamard". (#7046) (#7198)\The three variants of the Hadamard gradient added in this release offer tradeoffs that could be advantageous in certain cases:
"reversed-hadamard": the observable being measured and the generators of the unitary operations in the circuit are reversed; the generators are now the observables, and the Pauli decomposition of the observables are now gates in the circuit."direct-hadamard": the additional auxiliary qubit needed in the standard Hadamard gradient is exchanged for additional circuit executions."reversed-direct-hadamard": a combination of the "direct" and "reversed" modes, where the role of the observable and the generators of the unitary operations in the circuit swap, and the additional auxiliary qubit is exchanged for additional circuit executions.
Using them in your code is just like any other differentiation method in PennyLane:
import pennylane as qml dev = qml.device("default.qubit") @qml.qnode(dev, diff_method="reversed-hadamard") def circuit(x): qml.RX(x, 0) return qml.expval(qml.Z(0))
>>> qml.grad(circuit)(qml.numpy.array(0.5)) np.float64(-0.47942553860420284)More information on how these three new gradient methods work can be found in arXiv:2408.05406.
-
The Hadamard gradient method and its variants can now differentiate any operator with a generator defined, and can accept circuits with non-commuting measurements. (#6928)
Improvements 🛠
QNode execution configuration
-
QNodes now have an
updatemethod that allows for re-configuring settings likediff_method,mcm_method, and more. This allows for easier on-the-fly adjustments to workflows. (#6803)After constructing a QNode,
import pennylane as qml @qml.qnode(device=qml.device("default.qubit")) def circuit(): qml.H(0) qml.CNOT([0,1]) return qml.probs()
its settings can be modified with
update, which returns a newQNodeobject (note: any arguments not specified inupdatewill retain their original value). Here is an example of updating a QNode'sdiff_method:>>> print(circuit.diff_method) best >>> new_circuit = circuit.update(diff_method="parameter-shift") >>> print(new_circuit.diff_method) 'parameter-shift'
-
A new helper function called
qml.workflow.construct_execution_config(qnode)(*args,**kwargs)is now available, which allows users to construct an execution configuration from a given QNode instance. (#6901)@qml.qnode(qml.device("default.qubit", wires=1)) def circuit(x): qml.RX(x, 0) return qml.expval(qml.Z(0))
>>> config = qml.workflow.construct_execution_config(circuit)(1) >>> print(config) ExecutionConfig(grad_on_execution=False, use_device_gradient=True, use_device_jacobian_product=False, gradient_method='backprop', gradient_keyword_arguments={}, device_options={'max_workers': None, 'prng_key': None, 'rng': Generator(PCG64) at 0x15F6BB680}, interface=<Interface.NUMPY: 'numpy'>, derivative_order=1, mcm_config=MCMConfig(mcm_method=None, postselect_mode=None), convert_to_numpy=True)
-
QNodes now store their
ExecutionConfiginstead ofqnode_kwargs. (#6991)
Decompositions
-
The
qml.QROMtemplate has been upgraded to decompose more efficiently whenwork_wiresare not used. #6967) -
The decomposition of a single-qubit
qml.QubitUnitarynow includes the global phase. (#7143) -
The decompositions of
qml.SX,qml.Xandqml.Yuseqml.GlobalPhaseinstead ofqml.PhaseShift. (#7073) -
A new decomposition for multi-controlled global phases that uses one less controlled phase shift has been added. (#6936)
-
qml.ops.sk_decompositionhas been improved to produce less gates for certain edge cases. This greatly impacts the performance ofqml.clifford_t_decomposition, which should now give less extraneousqml.Tgates. (#6855) -
qml.MPSPrepnow has a gate decomposition. This enables its use with any device. Additionally, theright_canonicalize_mpsfunction has also been added to transform an MPS into its right-canonical form. (#6896) -
The
qml.clifford_t_decompositionhas been improved to use less gates when decomposingqml.PhaseShift. (#6842) -
An empty basis set in
qml.compileis now recognized as valid, resulting in decomposition of all operators that can be decomposed. (#6821) -
The
assert_validmethod now validates that an operator's decomposition does not contain the operator itself, instead of checking that it does not contain any operators of the same class as the operator. (#7099)
Improved drawing
-
qml.draw_mplcan now split deep circuits over multiple figures via amax_lengthkeyword argument. (#7128) -
qml.drawnow re-displays wire labels at the start of each partitioned chunk when using themax_lengthkeyword argument. (#7250) -
qml.drawandqml.draw_mplcan now reuse lines for different classical wires, saving whitespace without changing the represented circuit. (#7163) -
qml.PrepSelPrepnow has a concise representation when drawn withqml.draworqml.draw_mpl. (#7164)
Device improvements
-
Devices can now configure whether or not ML framework data is sent to them via an
ExecutionConfig.convert_to_numpyparameter. End-to-end jitting ondefault.qubitis used if the user specified ajax.random.PRNGKeyas a seed. (#6899) (#6788) (#6869) -
The
reference.qubitdevice now enforcessum(probs)==1insample_state. (#7076) -
The
default.mixeddevice now adheres to the newer device API introduced in v0.33. This means thatdefault.mixednow supports not having to specify the number of wires, more predictable behaviour with interfaces, support forqml.Snapshot, and more. (#6684) -
null.qubitcan now execute jaxpr. (#6924)
Experimental FTQC module
-
A template class,
qml.ftqc.GraphStatePrep, has been added for the Graph state construction. (#6985) (#7092) -
A new utility module
qml.ftqc.utilsis provided, with support for functionality such as dynamic qubit recycling. (#7075) -
A new class,
qml.ftqc.QubitGraph, is now available for representing a qubit memory-addressing model for mappings between logical and physical qubits. This representation allows for nesting of lower-level qubits with arbitrary depth to allow easy insertion of arbitrarily many levels of abstractions between logical qubits and physical qubits. (#6962) -
A
Latticeclass and agenerate_latticemethod has been added to theqml.ftqcmodule. Thegenerate_latticemethod is to generate 1D, 2D, 3D grid graphs with the given geometric parameters. (#6958) -
Measurement functions
measure_x,measure_yandmeasure_arbitrary_basisare added in the experimentalftqcmodule. These functions apply a mid-circuit measurement and return aMeasurementValue. They are analogous toqml.measurefor the computational basis, but instead measure in the X-basis, Y-basis, or an arbitrary basis, respectively. Functionqml.ftqc.measure_zis also added as an alias forqml.measure. (#6953) -
The function
cond_measurehas been added to the experimentalftqcmodule to apply a mid-circuit measurement with a measurement basis conditional on the function input. (#7037) -
A
ParametrizedMidMeasureclass has been added to represent a mid-circuit measurement in an arbitrary measurement basis in the XY, YZ or ZX plane. SubclassesXMidMeasureMPandYMidMeasureMPrepresent X-basis and Y-basis measurements. These classes are part of the experimentalftqcmodule. (#6938) (#6953) -
A
diagonalize_mcmstransform has been added that diagonalizes anyParametrizedMidMeasure, for devices that only natively support mid-circuit measurements in the computational basis. (#6938) (#7037)
Program capture and dynamic shapes
-
Workflows that create dynamically shaped arrays via
jnp.ones,jnp.zeros,jnp.arange, andjnp.fullcan now be captured. #6865) -
Workflows wherein the sizes of dynamically shaped arrays are updated in a
qml.while_looporqml.for_loopare now capturable. (#7084) (#7098) -
Workflows containing
qml.condinstances that return arrays with dynamic shapes can now be captured. (#6888) (#7080) -
A
qml.capture.pause()context manager has been added for pausing program capture in an error-safe way. (#6911) -
The requested
diff_methodis now validated when program capture is enabled. (#6852) -
A
qml.capture.register_custom_staging_rulehas been added for handling higher-order primitives that return new dynamically shaped arrays. (#7086) -
Support has been improved for when wires are specified as
jax.numpy.ndarrayif program capture is enabled. (#7108) -
qml.cond,qml.adjoint,qml.ctrl, and QNodes can now handle accepting dynamically shaped arrays with the abstract shape matching another argument. (#7059) -
A new
qml.capture.eval_jaxprfunction has been implemented. This is a variant ofjax.core.eval_jaxprthat can handle the creation of arrays with dynamic shapes. (#7052) -
A new, experimental
Operatormethod calledcompute_qfunc_decompositionhas been added to represent decompositions with structure (e.g., control flow). This method is only used when capture is enabled withqml.capture.enable(). (#6859) (#6881) (#7022) (#6917) (#7081) -
The higher order primitives in program capture can now accept inputs with abstract shapes. (#6786)
-
Execution interpreters and
qml.capture.eval_jaxprcan now handle jaxpjitprimitives when dynamic shapes are being used. (#7078) (#7117) -
Device preprocessing is now being performed in the execution pipeline for program capture. (#7057) (#7089) (#7131) (#7135)
Other improvements
-
The
poly_to_anglesfunction has been improved to correctly work with different interfaces and no longer manipulate the input angles tensor internally. (#6979) -
Dynamic one-shot workloads are now faster for
null.qubitby removing a redundantfunctools.lru_cachecall that was capturing allSampleMPobjects in a workload. (#7077) -
The coefficients of observables now have improved differentiability. (#6598)
-
An informative error is now raised when a QNode with
diff_method=Noneis differentiated. (#6770) -
qml.gradients.finite_diff_jvphas been added to compute the jvp of an arbitrary numeric function. (#6853) -
The qchem functions that accept a string input have been updated to consistently work with both lower-case and upper-case inputs. (#7186)
-
PSWAP.matrix()andPSWAP.eigvals()now support parameter broadcasting. (#7179) (#7228) -
Device.eval_jaxprnow accepts anexecution_configkeyword argument. (#6991) -
merge_rotationsnow correctly simplifies mergedqml.Rotoperators whose angles yield the identity operator. (#7011) -
The
qml.measurements.NullMeasurementmeasurement process has been added to allow for profiling problems without the overheads associated with performing measurements. (#6989) -
The
pauli_repproperty is now accessible forAdjointoperators when there is a Pauli representation. (#6871) -
qml.pauli.PauliVSpaceis now iterable. (#7054) -
qml.qchem.tapernow handles wire ordering for the tapered observables more robustly. (#6954) -
A
RuntimeWarningis now raised byqml.QNodeandqml.executeif executing JAX workflows and the installed version of JAX is greater than0.4.28. (#6864) -
The
rng_saltversion has been bumped tov0.40.0. (#6854)
Labs: a place for unified and rapid prototyping of research software 🧪
-
pennylane.labs.dla.lie_closure_densehas been removed and integrated intoqml.lie_closureusing the newdensekeyword. (#6811) -
pennylane.labs.dla.structure_constants_densehas been removed and integrated intoqml.structure_constantsusing the newmatrixkeyword. (#6861) -
ResourceOperator.resource_paramshas been changed to a property. (#6973) -
ResourceOperatorimplementations for theModExp,PhaseAdder,Multiplier,ControlledSequence,AmplitudeAmplification,QROM,SuperPosition,MottonenStatePreparation,StatePrep,BasisStatetemplates have been added. (#6638) -
pennylane.labs.khaneja_glaser_involutionhas been removed,pennylane.labs.check_commutationhas moved toqml.liealg.check_commutation_relation.pennylane.labs.check_cartan_decomphas moved toqml.liealg.check_cartan_decomp. All involution functions have been moved toqml.liealg.pennylane.labs.adjvec_to_ophas moved toqml.liealg.adjvec_to_op.pennylane.labs.op_to_adjvechas moved toqml.liealg.op_to_adjvec.pennylane.labs.change_basis_ad_rephas moved toqml.liealg.change_basis_ad_rep.pennylane.labs.cartan_subalgebrahas moved toqml.liealg.horizontal_cartan_subalgebra. (#7026) (#7054) -
New classes called
HOStateandVibronicHOhave been added for representing harmonic oscillator states. (#7035) -
Base classes for Trotter error estimation on Realspace Hamiltonians have been added:
RealspaceOperator,RealspaceSum,RealspaceCoeffs, andRealspaceMatrix(#7034) -
Functions for Trotter error estimation and Hamiltonian fragment generation have been added:
trotter_error,perturbation_error,vibrational_fragments,vibronic_fragments, andgeneric_fragments. (#7036)As an example we compute the perturbation error of a vibrational Hamiltonian. First we generate random harmonic frequencies and Taylor coefficients to initialize the vibrational Hamiltonian.
>>> from pennylane.labs.trotter_error import HOState, vibrational_fragments, perturbation_error >>> import numpy as np >>> n_modes = 2 >>> r_state = np.random.RandomState(42) >>> freqs = r_state.random(n_modes) >>> taylor_coeffs = [ >>> np.array(0), >>> r_state.random(size=(n_modes, )), >>> r_state.random(size=(n_modes, n_modes)), >>> r_state.random(size=(n_modes, n_modes, n_modes)) >>> ]
We call
vibrational_fragmentsto get the harmonic and anharmonic fragments of the vibrational Hamiltonian.>>> frags = vibrational_fragments(n_modes, freqs, taylor_coeffs)We build state vectors in the harmonic oscillator basis with the
HOStateclass.>>> gridpoints = 5 >>> state1 = HOState(n_modes, gridpoints, {(0, 0): 1}) >>> state2 = HOState(n_modes, gridpoints, {(1, 1): 1})
Finally, we compute the error by calling
perturbation_error.>>> perturbation_error(frags, [state1, state2]) [(-0.9189251160920879+0j), (-4.797716682426851+0j)]
-
Function
qml.labs.trotter_error.vibronic_fragmentsnow returnsRealspaceMatrixobjects with the correct number of electronic states. (#7251)
Breaking changes 💔
-
Executing
qml.specsis now much more efficient with the removal of accessingnum_diagonalizing_gates. The calculation of this quantity is extremely expensive, and the definition is ambiguous for non-commuting observables. (#7047) -
qml.gradients.gradient_transform.choose_trainable_paramshas been renamed tochoose_trainable_param_indicesto better reflect what it actually does. (#6928) -
qml.MultiControlledXno longer accepts strings as control values. (#6835) -
The
control_wiresargument inqml.MultiControlledXhas been removed. (#6832) (#6862) -
qml.executenow has a collection of keyword-only arguments. (#6598) -
The
decomp_depthargument inqml.transforms.set_decompositionhas been removed. (#6824) -
The
max_expansionargument inqml.devices.preprocess.decomposehas been removed. (#6824) -
The
tapeandqtapeproperties ofQNodehave been removed. Instead, use theqml.workflow.construct_tapefunction. (#6825) -
The
gradient_fnkeyword argument inqml.executehas been removed. Instead, it has been replaced withdiff_method. (#6830) -
The
QNode.get_best_methodandQNode.best_method_strmethods have been removed. Instead, use theqml.workflow.get_best_diff_methodfunction. (#6823) -
The
output_dimproperty ofqml.tape.QuantumScripthas been removed. Instead, use methodshapeofQuantumScriptorMeasurementProcessto get the same information. (#6829) -
The
qsvt_legacymethod, along with its private helper_qsp_to_qsvt, has been removed. (#6827)
Deprecations 👋
-
The
qml.qnn.KerasLayerclass has been deprecated because Keras 2 is no longer actively maintained. Please consider using a different machine learning framework instead ofTensorFlow/Keras 2, like Pytorch or JAX. (#7097) -
Specifying
pipeline=Nonewithqml.compileis now deprecated. A sequence of transforms should now always be specified. (#7004) -
qml.ControlledQubitUnitarywill stop acceptingqml.QubitUnitaryobjects as arguments as itsbase. Instead, useqml.ctrlto construct a controlledqml.QubitUnitary. A follow-up PR fixed accidental double-queuing when usingqml.ctrlwithQubitUnitary. (#6840) (#6926) -
The
control_wiresargument inqml.ControlledQubitUnitaryhas been deprecated. Instead, use thewiresargument as the second positional argument. (#6839) -
The
mcm_methodkeyword inqml.executehas been deprecated. Instead, use themcm_methodandpostselect_modearguments. (#6807) -
Specifying gradient keyword arguments as any additional keyword argument to the qnode is deprecated and will be removed in v0.42. The gradient keyword arguments should be passed to the new keyword argument
gradient_kwargsvia an explicit dictionary. This change will improve QNode argument validation. (#6828) -
The
qml.gradients.hamiltonian_gradfunction has been deprecated. This gradient recipe is not required with the new operator arithmetic system. (#6849) -
The
inner_transform_programandconfigkeyword arguments inqml.executehave been deprecated. If more detailed control over the execution is required, useqml.workflow.runwith these arguments instead. (#6822) (#6879) -
The property
MeasurementProcess.return_typehas been deprecated. If observable type checking is needed, please use directisinstance; if other text information is needed, please use class name, or another internal temporary private member_shortname. (#6841) (#6906) (#6910) -
Pauli module level imports of
lie_closure,structure_constants, andcenterare deprecated, as functionality has moved to the newliealgmodule. (#6935)
Internal changes ⚙️
-
An informative error message has been added if Autograph is employed on a function that has a
lambdaloop condition inqml.while_loop. (#7178) -
Logic in
qml.drawer.tape_texthas been cleaned. (#7133) -
An intermediate caching to
null.qubitzero-value generation has been added to improve memory consumption for larger workloads. (#7155) -
All use of
ABCfor intermediate variables has been renamed to preserve the label for the Python abstract base classabc.ABC. (#7156) -
The error message when device wires are not specified when program capture is enabled is more clear. (#7130)
-
Logic in
_capture_qnode.pyhas been cleaned. (#7115) -
The test for
qml.math.quantum._denman_beavers_iterationshas been improved such that tested random matrices are guaranteed positive. (#7071) -
The
matrix_powerdispatch for thescipyinterface has been replaced with an in-place implementation. (#7055) -
Support has been added to
CollectOpsandMeasfor handling QNode primitives. (#6922) -
Change some
scipyimports from submodules to whole module to reduce memory footprint of importing pennylane. (#7040) -
NotImplementedErrors have been added forgradandjacobianinCollectOpsandMeas. (#7041) -
Quantum transform interpreters now perform argument validation and will no longer check if the equation in the
jaxpris a transform primitive. (#7023) -
qml.for_loopandqml.while_loophave been moved from thecompilermodule to a newcontrol_flowmodule. (#7017) -
qml.capture.run_autographis now idempotent. This meansrun_autograph(fn) = run_autograph(run_autograph(fn)). (#7001) -
Minor changes to
DQInterpreterhave been made for speedups with program capture execution. (#6984) -
no-memberpylint issues from JAX are now globally silenced (#6987) -
pylint=3.3.4errors in our source code have been fixed. (#6980) (#6988) -
QNode.get_gradient_fnhas been removed from the source code. (#6898) -
The source code has been updated use
black==25.1.0. (#6897) -
The
InterfaceEnumobject has been improved to prevent direct comparisons tostrobjects. (#6877) -
A
QmlPrimitiveclass has been added that inheritsjax.core.Primitiveto a newqml.capture.custom_primitivesmodule. This class contains aprim_typeproperty so that we can differentiate between different sets of PennyLane primitives. Consequently,QmlPrimitiveis now used to define all PennyLane primitives. (#6847) -
The
RiemannianGradientOptimizerhas been updated to take advantage of newer features. (#6882) -
The
keep_intermediate=Trueflag is now used to keep Catalyst's IR when testing. (#6990)
Documentation 📝
-
A page on sharp bits and debugging tips has been added for PennyLane program capture: :doc:
/news/program_capture_sharp_bits. This page is recommended to consult any time errors occur whenqml.capture.enable()is present. (#7062) -
The :doc:
Compiling Circuits page <../introduction/compiling_circuits>has been updated to include information on using the new experimental decompositions system. (#7066) -
The docstring for
qml.transforms.decomposenow recommends theqml.clifford_t_decompositiontransform when decomposing to the Clifford + T gate set. (#7177) -
Typos were fixed in the docstring for
qml.QubitUnitary. (#7187) -
The docstring for
qml.prodhas been updated to explain that the order of the output may seem reversed, but it is correct. (#7083) -
The docstring for
qml.labs.trotter_errorhas been updated. (#7190) -
The
gates,qubitsandlambattributes ofDoubleFactorizationandFirstQuantizationhave dedicated documentation. (#7173) -
The code example in the docstring for
qml.PauliSentencenow properly copy-pastes. (#6949) -
The docstrings for
qml.unary_mapping,qml.binary_mapping,qml.christiansen_mapping,qml.qchem.localize_normal_modes, andqml.qchem.VibrationalPEShave been updated to include better code examples. (#6717) -
The docstrings for
qml.qchem.localize_normal_modesandqml.qchem.VibrationalPEShave been updated to include examples that can be copied. (#6834) -
A typo has been fixed in the code example for
qml.labs.dla.lie_closure_dense. (#6858) -
The code example in the docstring for
qml.BasisRotationwas corrected by includingwire_orderin the call toqml.matrix. (#6891) -
The docstring of
qml.noise.meas_eqhas been updated to make its functionality clearer. (#6920) -
The docstring for
qml.devices.default_tensor.DefaultTensorhas been updated to clarify differentiation support. (#7150) -
The docstring for
QuantumScriptshas been updated to remove outdated references toset_parameters. (#7174) -
The documentation for
qml.devicehas been updated to includenull.qubitin the list of readily available devices. (#7233)
Bug fixes 🐛
-
Using transforms with program capture enabled now works correctly with functions that accept pytree arguments. (#7233)
-
qml.math.requires_gradno longer returnsTruefor JAX inputs other thanjax.interpreters.ad.JVPTracerinstances. (#7233) -
PennyLane is now compatible with
pyzx 0.9. (#7188) -
Fixed a bug when
qml.matrixis applied on a sparse operator, which caused the output to have unnecessary epsilon inaccuracy. (#7147) (#7182) -
Reverted (#6933) to remove non-negligible performance impact due to wire flattening. (#7136)
-
Fixed a bug that caused the output of
qml.fourier.qnode_spectrum()to differ depending if equivalent gate generators are defined using different PennyLane operators. This was resolved by updatingqml.operation.gen_is_multi_term_hamiltonianto work with more complicated generators. (#7121) -
Modulo operator calls on MCMs now correctly offload to the autoray-backed
qml.math.moddispatch. (#7085) -
qml.transforms.single_qubit_fusionandqml.transforms.cancel_inversesnow correctly handle mid-circuit measurements when program capture is enabled. (#7020) -
qml.math.get_interfacenow correctly extracts the"scipy"interface if provided a list/array of sparse matrices. (#7015) -
qml.ops.Controlled.has_sparse_matrixnow provides the correct information by checking if the target operator has a sparse or dense matrix defined. (#7025) -
qml.capture.PlxprInterpreternow flattens pytree arguments before evaluation. (#6975) -
qml.GlobalPhase.sparse_matrixnow correctly returns a sparse matrix of the same shape asmatrix. (#6940) -
qml.expvalno longer silently casts to a real number when observable coefficients are imaginary. (#6939) -
Fixed
qml.wires.Wiresinitialization to disallowWiresobjects as wires labels. Now,Wiresis idempotent, e.g.Wires([Wires([0]), Wires([1])])==Wires([0, 1]). (#6933) -
qml.capture.PlxprInterpreternow correctly handles propagation of constants when interpreting higher-order primitives. (#6913) -
qml.capture.PlxprInterpreternow usesPrimitive.get_bind_paramsto resolve primitive calling signatures before binding primitives. (#6913) -
The interface is now detected from the data in the circuit, not the arguments to the QNode. This allows interface data to be strictly passed as closure variables and still be detected. (#6892)
-
qml.BasisStatenow casts its input to integers. (#6844) -
The
workflow.contstruct_batchandworkflow.construct_tapefunctions now correctly reflect themcm_methodpassed to theQNode, instead of assuming the method is alwaysdeferred. (#6903) -
Applying mid-circuit measurements inside
qml.condis not supported, and previously resulted in unclear error messages or incorrect results. It is now explicitly not allowed, and raises an error when calling the function returned byqml.cond. (#7027) (#7051) -
qml.qchem.givens_decompositionno longer raises aRuntimeWarningwhen the input is a zero matrix. #7053) -
Comparing an adjoint of an
Observablewith anotherOperationusingqml.equalno longer incorrectly skips the check ensuring that the operator types match. (#7107) -
Downloading specific attributes of datasets in the
'other'category viaqml.data.loadno longer fails. (#7144)
Contributors ✍️
This release contains contributions from (in alphabetical order):
Guillermo Alonso, Daniela Angulo, Ali Asadi, Utkarsh Azad, Astral Cai, Joey Carter, Henry Chang, Yushao Chen, Isaac De Vlugt, Diksha Dhawan, Lillian M.A. Frederiksen, Pietropaolo Frisoni, Marcus Gisslén, Diego Guala, Austin Huang, Soran Jahangiri, Korbinian Kottmann, Christina Lee, Joseph Lee, Dantong Li, William Maxwell, Anton Naim Ibrahim, Lee J. O'Riordan, Mudit Pandey, Vyom Patel, Andrija Paurevic, Justin Pickering, Alex Preciado, Shuli Shu, David Wierichs