Release 0.34.0
New features since last release
Statistics and drawing for mid-circuit measurements 🎨
-
It is now possible to return statistics of composite mid-circuit measurements. (#4888)
Mid-circuit measurement results can be composed using basic arithmetic operations and then statistics can be calculated by putting the result within a PennyLane measurement like
qml.expval()
. For example:import pennylane as qml dev = qml.device("default.qubit") @qml.qnode(dev) def circuit(phi, theta): qml.RX(phi, wires=0) m0 = qml.measure(wires=0) qml.RY(theta, wires=1) m1 = qml.measure(wires=1) return qml.expval(~m0 + m1) print(circuit(1.23, 4.56))
1.2430187928114291
Another option, for ease-of-use when using
qml.sample()
,qml.probs()
, orqml.counts()
, is to provide a simple list of mid-circuit measurement results:dev = qml.device("default.qubit") @qml.qnode(dev) def circuit(phi, theta): qml.RX(phi, wires=0) m0 = qml.measure(wires=0) qml.RY(theta, wires=1) m1 = qml.measure(wires=1) return qml.sample(op=[m0, m1]) print(circuit(1.23, 4.56, shots=5))
[[0 1] [0 1] [0 0] [1 0] [0 1]]
Composite mid-circuit measurement statistics are supported on
default.qubit
anddefault.mixed
. To learn more about which measurements and arithmetic operators are supported, refer to the measurements page and the documentation for qml.measure. -
Mid-circuit measurements can now be visualized with the text-based
qml.draw()
and the graphicalqml.draw_mpl()
methods. (#4775) (#4803) (#4832) (#4901) (#4850) (#4917) (#4930) (#4957)Drawing of mid-circuit measurement capabilities including qubit reuse and reset, postselection, conditioning, and collecting statistics is now supported. Here is an all-encompassing example:
def circuit(): m0 = qml.measure(0, reset=True) m1 = qml.measure(1, postselect=1) qml.cond(m0 - m1 == 0, qml.S)(0) m2 = qml.measure(1) qml.cond(m0 + m1 == 2, qml.T)(0) qml.cond(m2, qml.PauliX)(1)
The text-based drawer outputs:
>>> print(qml.draw(circuit)()) 0: ──┤↗│ │0⟩────────S───────T────┤ 1: ───║────────┤↗₁├──║──┤↗├──║──X─┤ ╚═════════║════╬═══║═══╣ ║ ╚════╩═══║═══╝ ║ ╚══════╝
The graphical drawer outputs:
>>> print(qml.draw_mpl(circuit)())
Catalyst is seamlessly integrated with PennyLane ⚗️
-
Catalyst, our next-generation compilation framework, is now accessible within PennyLane, allowing you to more easily benefit from hybrid just-in-time (JIT) compilation.
To access these features, simply install
pennylane-catalyst
:pip install pennylane-catalyst
The qml.compiler module provides support for hybrid quantum-classical compilation. (#4692) (#4979)
Through the use of the
qml.qjit
decorator, entire workflows can be JIT compiled — including both quantum and classical processing — down to a machine binary on first-function execution. Subsequent calls to the compiled function will execute the previously-compiled binary, resulting in significant performance improvements.import pennylane as qml dev = qml.device("lightning.qubit", wires=2) @qml.qjit @qml.qnode(dev) def circuit(theta): qml.Hadamard(wires=0) qml.RX(theta, wires=1) qml.CNOT(wires=[0,1]) return qml.expval(qml.PauliZ(wires=1))
>>> circuit(0.5) # the first call, compilation occurs here array(0.) >>> circuit(0.5) # the precompiled quantum function is called array(0.)
Currently, PennyLane supports the Catalyst hybrid compiler with the
qml.qjit
decorator. A significant benefit of Catalyst is the ability to preserve complex control flow around quantum operations — such asif
statements andfor
loops, and including measurement feedback — during compilation, while continuing to support end-to-end autodifferentiation. -
The following functions can now be used with the
qml.qjit
decorator:qml.grad
,qml.jacobian
,qml.vjp
,qml.jvp
, andqml.adjoint
. (#4709) (#4724) (#4725) (#4726)When
qml.grad
orqml.jacobian
are used with@qml.qjit
, they are patched to catalyst.grad and catalyst.jacobian, respectively.dev = qml.device("lightning.qubit", wires=1) @qml.qjit def workflow(x): @qml.qnode(dev) def circuit(x): qml.RX(np.pi * x[0], wires=0) qml.RY(x[1], wires=0) return qml.probs() g = qml.jacobian(circuit) return g(x)
>>> workflow(np.array([2.0, 1.0])) array([[ 3.48786850e-16, -4.20735492e-01], [-8.71967125e-17, 4.20735492e-01]])
-
JIT-compatible functionality for control flow has been added via
qml.for_loop
,qml.while_loop
, andqml.cond
. (#4698)qml.for_loop
andqml.while_loop
can be deployed as decorators on functions that are the body of the loop. The arguments to both follow typical conventions:@qml.for_loop(lower_bound, upper_bound, step)
@qml.while_loop(cond_function)
Here is a concrete example with
qml.for_loop
:dev = qml.device("lightning.qubit", wires=1) @qml.qjit @qml.qnode(dev) def circuit(n: int, x: float): @qml.for_loop(0, n, 1) def loop_rx(i, x): # perform some work and update (some of) the arguments qml.RX(x, wires=0) # update the value of x for the next iteration return jnp.sin(x) # apply the for loop final_x = loop_rx(x) return qml.expval(qml.PauliZ(0)), final_x
>>> circuit(7, 1.6) (array(0.97926626), array(0.55395718))
Decompose circuits into the Clifford+T gateset 🧩
-
The new
qml.clifford_t_decomposition()
transform provides an approximate breakdown of an input circuit into the Clifford+T gateset. Behind the scenes, this decomposition is enacted via thesk_decomposition()
function using the Solovay-Kitaev algorithm. (#4801) (#4802)The Solovay-Kitaev algorithm approximately decomposes a quantum circuit into the Clifford+T gateset. To account for this, a desired total circuit decomposition error,
epsilon
, must be specified when usingqml.clifford_t_decomposition
:dev = qml.device("default.qubit") @qml.qnode(dev) def circuit(): qml.RX(1.1, 0) return qml.state() circuit = qml.clifford_t_decomposition(circuit, epsilon=0.1)
>>> print(qml.draw(circuit)()) 0: ──T†──H──T†──H──T──H──T──H──T──H──T──H──T†──H──T†──T†──H──T†──H──T──H──T──H──T──H──T──H──T†──H ───T†──H──T──H──GlobalPhase(0.39)─┤
The resource requirements of this circuit can also be evaluated:
>>> with qml.Tracker(dev) as tracker: ... circuit() >>> resources_lst = tracker.history["resources"] >>> resources_lst[0] wires: 1 gates: 34 depth: 34 shots: Shots(total=None) gate_types: {'Adjoint(T)': 8, 'Hadamard': 16, 'T': 9, 'GlobalPhase': 1} gate_sizes: {1: 33, 0: 1}
Use an iterative approach for quantum phase estimation 🔄
-
Iterative Quantum Phase Estimation is now available with
qml.iterative_qpe
. (#4804)The subroutine can be used similarly to mid-circuit measurements:
import pennylane as qml dev = qml.device("default.qubit", shots=5) @qml.qnode(dev) def circuit(): # Initial state qml.PauliX(wires=[0]) # Iterative QPE measurements = qml.iterative_qpe(qml.RZ(2., wires=[0]), ancilla=[1], iters=3) return [qml.sample(op=meas) for meas in measurements]
>>> print(circuit()) [array([0, 0, 0, 0, 0]), array([1, 0, 0, 0, 0]), array([0, 1, 1, 1, 1])]
The
$i$ -th element in the list refers to the 5 samples generated by the$i$ -th measurement of the algorithm.
Improvements 🛠
Community contributions 🥳
-
The
+=
operand can now be used with aPauliSentence
, which has also provides a performance boost. (#4662) -
The Approximate Quantum Fourier Transform (AQFT) is now available with
qml.AQFT
. (#4715) -
qml.draw
andqml.draw_mpl
now render operator IDs. (#4749)The ID can be specified as a keyword argument when instantiating an operator:
>>> def circuit(): ... qml.RX(0.123, id="data", wires=0) >>> print(qml.draw(circuit)()) 0: ──RX(0.12,"data")─┤
-
Non-parametric operators such as
Barrier
,Snapshot
, andWirecut
have been grouped together and moved topennylane/ops/meta.py
. Additionally, the relevant tests have been organized and placed in a new file,tests/ops/test_meta.py
. (#4789) -
The
TRX
,TRY
, andTRZ
operators are now differentiable via backpropagation ondefault.qutrit
. (#4790) -
The function
qml.equal
now supportsControlledSequence
operators. (#4829) -
XZX decomposition has been added to the list of supported single-qubit unitary decompositions. (#4862)
-
==
and!=
operands can now be used withTransformProgram
andTransformContainers
instances. (#4858) -
A
qutrit_mixed
module has been added toqml.devices
to store helper functions for a future qutrit mixed-state device. A function calledcreate_initial_state
has been added to this module that creates device-compatible initial states. (#4861) -
The function
qml.Snapshot
now supports arbitrary state-based measurements (i.e., measurements of typeStateMeasurement
). (#4876) -
qml.equal
now supports the comparison ofQuantumScript
andBasisRotation
objects. (#4902) (#4919) -
The function
qml.draw_mpl
now accept a keyword argumentfig
to specify the output figure window. (#4956)
Better support for batching
-
qml.AmplitudeEmbedding
now supports batching when used with Tensorflow. (#4818) -
default.qubit
can now evolve already batched states withqml.pulse.ParametrizedEvolution
. (#4863) -
qml.ArbitraryUnitary
now supports batching. (#4745) -
Operator and tape batch sizes are evaluated lazily, helping run expensive computations less frequently and an issue with Tensorflow pre-computing batch sizes. (#4911)
Performance improvements and benchmarking
-
Autograd, PyTorch, and JAX can now use vector-Jacobian products (VJPs) provided by the device from the new device API. If a device provides a VJP, this can be selected by providing
device_vjp=True
to a QNode orqml.execute
. (#4935) (#4557) (#4654) (#4878) (#4841)>>> dev = qml.device('default.qubit') >>> @qml.qnode(dev, diff_method="adjoint", device_vjp=True) >>> def circuit(x): ... qml.RX(x, wires=0) ... return qml.expval(qml.PauliZ(0)) >>> with dev.tracker: ... g = qml.grad(circuit)(qml.numpy.array(0.1)) >>> dev.tracker.totals {'batches': 1, 'simulations': 1, 'executions': 1, 'vjp_batches': 1, 'vjps': 1} >>> g -0.09983341664682815
-
qml.expval
with largeHamiltonian
objects is now faster and has a significantly lower memory footprint (and constant with respect to the number ofHamiltonian
terms) when theHamiltonian
is aPauliSentence
. This is due to the introduction of a specializeddot
method in thePauliSentence
class which performsPauliSentence
-state
products. (#4839) -
default.qubit
no longer uses a dense matrix forMultiControlledX
for more than 8 operation wires. (#4673) -
Some relevant Pytests have been updated to enable its use as a suite of benchmarks. (#4703)
-
default.qubit
now appliesGroverOperator
faster by not using its matrix representation but a custom rule forapply_operation
. Also, the matrix representation ofGroverOperator
now runs faster. (#4666) -
A new pipeline to run benchmarks and plot graphs comparing with a fixed reference has been added. This pipeline will run on a schedule and can be activated on a PR with the label
ci:run_benchmarks
. (#4741) -
default.qubit
now supports adjoint differentiation for arbitrary diagonal state-based measurements. (#4865) -
The benchmarks pipeline has been expanded to export all benchmark data to a single JSON file and a CSV file with runtimes. This includes all references and local benchmarks. (#4873)
Final phase of updates to transforms
-
qml.quantum_monte_carlo
andqml.simplify
now use the new transform system. (#4708) (#4949) -
The formal requirement that type hinting be provided when using the
qml.transform
decorator has been removed. Type hinting can still be used, but is now optional. Please use a type checker such as mypy if you wish to ensure types are being passed correctly. (#4942)
Other improvements
-
PennyLane now supports Python 3.12. (#4985)
-
SampleMeasurement
now has an optional methodprocess_counts
for computing the measurement results from a counts dictionary. (#4941) -
A new function called
ops.functions.assert_valid
has been added for checking if anOperator
class is defined correctly. (#4764) -
Shots
objects can now be multiplied by scalar values. (#4913) -
GlobalPhase
now decomposes to nothing in case devices do not support global phases. (#4855) -
Custom operations can now provide their matrix directly through the
Operator.matrix()
method without needing to update thehas_matrix
property.has_matrix
will now automatically beTrue
ifOperator.matrix
is overridden, even ifOperator.compute_matrix
is not. (#4844) -
The logic for re-arranging states before returning them has been improved. (#4817)
-
When multiplying
SparseHamiltonian
s by a scalar value, the result now stays as aSparseHamiltonian
. (#4828) -
trainable_params
can now be set upon initialization of aQuantumScript
instead of having to set the parameter after initialization. (#4877) -
default.qubit
now calculates the expectation value ofHermitian
operators in a differentiable manner. (#4866) -
The
rot
decomposition now has support for returning a global phase. (#4869) -
The
"pennylane_sketch"
MPL-drawer style has been added. This is the same as the"pennylane"
style, but with sketch-style lines. (#4880) -
Operators now define a
pauli_rep
property, an instance ofPauliSentence
, defaulting toNone
if the operator has not defined it (or has no definition in the Pauli basis). (#4915) -
qml.ShotAdaptiveOptimizer
can now use a multinomial distribution for spreading shots across the terms of a Hamiltonian measured in a QNode. Note that this is equivalent to what can be done withqml.ExpvalCost
, but this is the preferred method becauseExpvalCost
is deprecated. (#4896) -
Decomposition of
qml.PhaseShift
now usesqml.GlobalPhase
for retaining the global phase information. (#4657) (#4947) -
qml.equal
forControlled
operators no longer returnsFalse
when equivalent but differently-ordered sets of control wires and control values are compared. (#4944) -
All PennyLane
Operator
subclasses are automatically tested byops.functions.assert_valid
to ensure that they follow PennyLaneOperator
standards. (#4922) -
Probability measurements can now be calculated from a
counts
dictionary with the addition of aprocess_counts
method in theProbabilityMP
class. (#4952) -
ClassicalShadow.entropy
now uses the algorithm outlined in 1106.5458 to project the approximate density matrix (with potentially negative eigenvalues) onto the closest valid density matrix. (#4959) -
The
ControlledSequence.compute_decomposition
default now decomposes thePow
operators, improving compatibility with machine learning interfaces. (#4995)
Breaking changes 💔
-
The functions
qml.transforms.one_qubit_decomposition
,qml.transforms.two_qubit_decomposition
, andqml.transforms.sk_decomposition
were moved toqml.ops.one_qubit_decomposition
,qml.ops.two_qubit_decomposition
, andqml.ops.sk_decomposition
, respectively. (#4906) -
The function
qml.transforms.classical_jacobian
has been moved to the gradients module and is now accessible asqml.gradients.classical_jacobian
. (#4900) -
The transforms submodule
qml.transforms.qcut
is now its own module:qml.qcut
. (#4819) -
The decomposition of
GroverOperator
now has an additional global phase operation. (#4666) -
qml.cond
and theConditional
operation have been moved from thetransforms
folder to theops/op_math
folder.qml.transforms.Conditional
will now be available asqml.ops.Conditional
. (#4860) -
The
prep
keyword argument has been removed fromQuantumScript
andQuantumTape
.StatePrepBase
operations should be placed at the beginning of theops
list instead. (#4756) -
qml.gradients.pulse_generator
is now namedqml.gradients.pulse_odegen
to adhere to paper naming conventions. (#4769) -
Specifying
control_values
passed toqml.ctrl
as a string is no longer supported. (#4816) -
The
rot
decomposition will now normalize its rotation angles to the range[0, 4pi]
for consistency (#4869) -
QuantumScript.graph
is now built usingtape.measurements
instead oftape.observables
because it depended on the now-deprecatedObservable.return_type
property. (#4762) -
The
"pennylane"
MPL-drawer style now draws straight lines instead of sketch-style lines. (#4880) -
The default value for the
term_sampling
argument ofShotAdaptiveOptimizer
is nowNone
instead of"weighted_random_sampling"
. (#4896)
Deprecations 👋
-
single_tape_transform
,batch_transform
,qfunc_transform
, andop_transform
are deprecated. Use the newqml.transform
function instead. (#4774) -
Observable.return_type
is deprecated. Instead, you should inspect the type of the surrounding measurement process. (#4762) (#4798) -
All deprecations now raise a
qml.PennyLaneDeprecationWarning
instead of aUserWarning
. (#4814) -
QuantumScript.is_sampled
andQuantumScript.all_sampled
are deprecated. Users should now validate these properties manually. (#4773) -
With an algorithmic improvement to
ClassicalShadow.entropy
, the keywordatol
becomes obsolete and will be removed in v0.35. (#4959)
Documentation 📝
-
Documentation for unitaries and operations' decompositions has been moved from
qml.transforms
toqml.ops.ops_math
. (#4906) -
Documentation for
qml.metric_tensor
andqml.adjoint_metric_tensor
andqml.transforms.classical_jacobian
is now accessible via the gradients API pageqml.gradients
in the documentation. (#4900) -
Documentation for
qml.specs
has been moved to theresource
module. (#4904) -
Documentation for QCut has been moved to its own API page:
qml.qcut
. (#4819) -
The documentation page for
qml.measurements
now links top-level accessible functions (e.g.,qml.expval
) to their top-level pages rather than their module-level pages (e.g.,qml.measurements.expval
). (#4750) -
Information for the documentation of
qml.matrix
about wire ordering has been added for usingqml.matrix
on a QNode which uses a device withdevice.wires=None
. (#4874)
Bug fixes 🐛
-
TransformDispatcher
now stops queuing when performing the transform when applying it to a qfunc. Only the output of the transform will be queued. (#4983) -
qml.map_wires
now works properly withqml.cond
andqml.measure
. (#4884) -
Pow
operators are now picklable. (#4966) -
Finite differences and SPSA can now be used with tensorflow-autograph on setups that were seeing a bus error. (#4961)
-
qml.cond
no longer incorrectly queues operators used arguments. (#4948) -
Attribute
objects now returnFalse
instead of raising aTypeError
when checking if an object is inside the set. (#4933) -
Fixed a bug where the parameter-shift rule of
qml.ctrl(op)
was wrong ifop
had a generator that has two or more eigenvalues and is stored as aSparseHamiltonian
. (#4899) -
Fixed a bug where trainable parameters in the post-processing of finite-differences were incorrect for JAX when applying the transform directly on a QNode. (#4879)
-
qml.grad
andqml.jacobian
now explicitly raise errors if trainable parameters are integers. (#4836) -
JAX-JIT now works with shot vectors. (#4772)
-
JAX can now differentiate a batch of circuits where one tape does not have trainable parameters. (#4837)
-
The decomposition of
GroverOperator
now has the same global phase as its matrix. (#4666) -
The
tape.to_openqasm
method no longer mistakenly includes interface information in the parameter string when converting tapes using non-NumPy interfaces. (#4849) -
qml.defer_measurements
now correctly transforms circuits when terminal measurements include wires used in mid-circuit measurements. (#4787) -
Fixed a bug where the adjoint differentiation method would fail if an operation that has a parameter with
grad_method=None
is present. (#4820) -
MottonenStatePreparation
andBasisStatePreparation
now raise an error when decomposing a broadcasted state vector. (#4767) -
Gradient transforms now work with overridden shot vectors and
default.qubit
. (#4795) -
Any
ScalarSymbolicOp
, likeEvolution
, now states that it has a matrix if the target is aHamiltonian
. (#4768) -
In
default.qubit
, initial states are now initialized with the simulator's wire order, not the circuit's wire order. (#4781) -
qml.compile
will now always decompose toexpand_depth
, even if a target basis set is not specified. (#4800) -
qml.transforms.transpile
can now handle measurements that are broadcasted onto all wires. (#4793) -
Parametrized circuits whose operators do not act on all wires return PennyLane tensors instead of NumPy arrays, as expected. (#4811) (#4817)
-
qml.transforms.merge_amplitude_embedding
no longer depends on queuing, allowing it to work as expected with QNodes. (#4831) -
qml.pow(op)
andqml.QubitUnitary.pow()
now also work with Tensorflow data raised to an integer power. (#4827) -
The text drawer has been fixed to correctly label
qml.qinfo
measurements, as well asqml.classical_shadow
qml.shadow_expval
. (#4803) -
Removed an implicit assumption that an empty
PauliSentence
gets treated as identity under multiplication. (#4887) -
Using a
CNOT
orPauliZ
operation with large batched states and the Tensorflow interface no longer raises an unexpected error. (#4889) -
qml.map_wires
no longer fails when mapping nested quantum tapes. (#4901) -
Conversion of circuits to openqasm now decomposes to a depth of 10, allowing support for operators requiring more than 2 iterations of decomposition, such as the
ApproxTimeEvolution
gate. (#4951) -
MPLDrawer
does not add the bonus space for classical wires when no classical wires are present. (#4987) -
Projector
now works with parameter-broadcasting. (#4993) * The jax-jit interface can now be used with float32 mode. (#4990) -
Keras models with a
qnn.KerasLayer
no longer fail to save and load weights properly when they are named "weights". (#5008)
Contributors ✍️
This release contains contributions from (in alphabetical order):
Guillermo Alonso, Ali Asadi, Utkarsh Azad, Gabriel Bottrill, Thomas Bromley, Astral Cai, Minh Chau, Isaac De Vlugt, Amintor Dusko, Pieter Eendebak, Lillian Frederiksen, Pietropaolo Frisoni, Josh Izaac, Juan Giraldo, Emiliano Godinez Ramirez, Ankit Khandelwal, Korbinian Kottmann, Christina Lee, Vincent Michaud-Rioux, Anurav Modak, Romain Moyard, Mudit Pandey, Matthew Silverman, Jay Soni, David Wierichs, Justin Woodring.