New features since last release
Quantum Random Access Memory (QRAM) ๐พ
-
Three implementations of QRAM are now available in PennyLane, including Bucket Brigade QRAM (
BBQRAM), a Select-Only QRAM (SelectOnlyQRAM), and a Hybrid QRAM (HybridQRAM) that combines behaviour from bothBBQRAMandSelectOnlyQRAM. The choice of QRAM implementation depends on the application, ranging from width versus depth tradeoffs to noise resilience. (#8670) (#8679) (#8680) (#8801)Irrespective of the specific implementation, QRAM encodes bitstrings, $b_i$, corresponding to a given entry, $i$, of a data set of length $N$, and can do so in superposition: $\text{QRAM} \sum_i c_i \vert i \rangle \vert 0 \rangle = \sum_i c_i \vert i \rangle \vert b_i \rangle$. Here, the first register representing $\vert i \rangle$ is called the
control_wiresregister (often referred to as the "address" register in literature), and the second register containing $\vert b_i \rangle$ is called thetarget_wiresregister (where the $i^{\text{th}}$ entry of the data set is loaded).Each QRAM implementation available in this release can be briefly described as follows:
-
BBQRAM: a bucket-brigade style QRAM implementation that is also resilient to noise. -
SelectOnlyQRAM: a QRAM implementation that comprises a series ofMultiControlledXgates. -
HybridQRAM: a QRAM implementation that combinesBBQRAMandSelectOnlyQRAMin a manner that allows for tradeoffs between depth and width.
An example of using
BBQRAMto read data into a target register is given below, where the data set in question is given by a list ofbitstringsand we wish to read its second entry ("110"):import pennylane as qml bitstrings = ["010", "111", "110", "000"] bitstring_size = 3 num_control_wires = 2 # len(bitstrings) = 4 = 2**2 num_work_wires = 1 + 3 * ((1 << num_control_wires) - 1) # 10 reg = qml.registers( { "control": num_control_wires, "target": bitstring_size, "work_wires": num_work_wires } ) dev = qml.device("default.qubit") @qml.qnode(dev) def bb_quantum(): # prepare an address, e.g., |10> (index 2) qml.BasisEmbedding(2, wires=reg["control"]) qml.BBQRAM( bitstrings, control_wires=reg["control"], target_wires=reg["target"], work_wires=reg["work_wires"], ) return qml.probs(wires=reg["target"])
>>> import numpy as np >>> print(np.round(bb_quantum())) [0. 0. 0. 0. 0. 0. 1. 0.]
Note that
"110"in binary is equal to 6 in decimal, which is the position of the only non-zero entry in thetarget_wiresregister.For more information on each implementation of QRAM in this release, check out their respective documentation pages:
BBQRAM,SelectOnlyQRAM, andHybridQRAM. -
-
A lightweight representation of the
BBQRAMtemplate calledqml.estimator.BBQRAMhas been added for fast and efficient resource estimation. (#8825)Like with other existing lightweight representations of PennyLane operations, leveraging
qml.estimator.BBQRAMfor fast resource estimation can be done in two ways:-
Using
qml.estimator.BBQRAMdirectly inside of a function and then callingestimate:import pennylane.estimator as qre def circuit(): qre.CNOT() qre.QFT(num_wires=4) qre.BBQRAM(num_bitstrings=30, size_bitstring=8, num_wires=100) qre.Hadamard()
>>> print(qre.estimate(circuit)()) --- Resources: --- Total wires: 100 algorithmic wires: 100 allocated wires: 0 zero state: 0 any state: 0 Total gates : 4.504E+3 'Toffoli': 1.096E+3, 'T': 792, 'CNOT': 2.475E+3, 'Z': 120, 'Hadamard': 21 -
On a simulatable circuit with detailed information:
bitstrings = ["010", "111", "110", "000"] bitstring_size = 3 num_control_wires = 2 # len(bistrings) = 4 = 2**2 num_work_wires = 1 + 3 * ((1 << num_control_wires) - 1) # 10 reg = qml.registers( { "control": num_control_wires, "target": bitstring_size, "work_wires": num_work_wires } ) dev = qml.device("default.qubit") @qml.qnode(dev) def bb_quantum(): # prepare an address, e.g., |10> (index 2) qml.BasisEmbedding(2, wires=reg["control"]) qml.BBQRAM( bitstrings, control_wires=reg["control"], target_wires=reg["target"], work_wires=reg["work_wires"], ) return qml.probs(wires=reg["target"])
>>> print(qre.estimate(bb_quantum)()) --- Resources: --- Total wires: 15 algorithmic wires: 15 allocated wires: 0 zero state: 0 any state: 0 Total gates : 181 'Toffoli': 40, 'CNOT': 128, 'X': 1, 'Z': 6, 'Hadamard': 6
-
Quantum Automatic Differentiation ๐ค
-
The Hadamard test gradient method (
diff_method="hadamard") in PennyLane now has an"auto"mode, which automatically chooses the most efficient mode of differentiation. (#8640) (#8875)The Hadamard test gradient method is a hardware-compatible differentiation method that can differentiate a broad range of parameterized gates. Using the
"auto"mode withdiff_method="hadamard"will result in an automatic selection of the method (either"standard","reversed","direct", or"reversed-direct") which results in the fewest total executions. This takes into account the number of observables, the number of generators, the number of measurements, and the presence of available auxiliary wires. For more details on how"auto"works, consult the section titled "Variants of the standard hadamard gradient" in the documentation for the Hadamard test gradient (qml.gradients.hadamard_grad).The
"auto"method can be accessed by specifying it ingradient_kwargsin the QNode when usingdiff_method="hadamard":dev = qml.device('default.qubit') @qml.qnode(dev, diff_method="hadamard", gradient_kwargs={"mode": "auto"}) def circuit(x): qml.evolve(qml.X(0) @ qml.X(1) + qml.Z(0) @ qml.Z(1) + qml.H(0), x) return qml.expval(qml.Z(0) @ qml.Z(1) + qml.Y(0))
>>> print(qml.grad(circuit)(qml.numpy.array(0.5))) 0.7342549405478683
Theoretical information on how each mode works can be found in arXiv:2408.05406.
Instantaneous Quantum Polynomial Circuits ๐จ
-
A new template for defining an Instantaneous Quantum Polynomial (
IQP) circuit has been added, as well as an associatedResourceOperatorfor resource estimation in theestimatormodule. These new features facilitate the simulation and resource estimation of large-scale generative quantum machine learning tasks. (#8748) (#8807) (#8749) (#8882)While
IQPcircuits belong to a class of circuits that are believed to be hard to sample from using classical algorithms, Recio-Armengol et al. showed in a recent paper titled Train on classical, deploy on quantum that such circuits can still be optimized efficiently.Here is a simple example showing how to define an
IQPcircuit and how to estimate the required quantum resources using theestimatefunction:import pennylane as qml import pennylane.estimator as qre pattern = [[[0]],[[1]],[[0,1]]] @qml.qnode(qml.device('lightning.qubit', wires=2)) def circuit(): qml.IQP( weights=[1., 2., 3.], num_wires=2, pattern=pattern, spin_sym=False, ) return qml.state()
>>> res = qre.estimate(circuit)() >>> print(res) --- Resources: --- Total wires: 2 algorithmic wires: 2 allocated wires: 0 zero state: 0 any state: 0 Total gates : 138 'T': 132, 'CNOT': 2, 'Hadamard': 4
The expectation values of Pauli-Z type observables for parameterized
IQPcircuits can be efficiently evaluated with thepennylane.qnn.iqp_expvalfunction. This estimator function is based on a randomized method allowing for the efficient optimization of circuits with thousands of qubits and millions of gates.from pennylane.qnn import iqp_expval import jax num_wires = 2 ops = np.array([[0, 1], [1, 0], [1, 1]]) # binary array representing ops Z1, Z0, Z0Z1 n_samples = 1000 key = jax.random.PRNGKey(42) weights = np.ones(len(pattern)) pattern = [[[0]], [[1]], [[0, 1]]] expvals, stds = iqp_expval(ops, weights, pattern, num_wires, n_samples, key)
>>> print(expvals, stds) [0.14506625 0.17813912 0.18971463] [0.02614436 0.02615901 0.02615425]For more theoretical details, check out our Fast optimization of instantaneous quantum polynomial circuits demo.
Arbitrary State Preparation ๐
-
A new template
MultiplexerStatePreparationis now available, allowing for the preparation of arbitrary states usingSelectPauliRotoperations. (#8581)Using
MultiplexerStatePreparationis analogous to using other state preparation techniques in PennyLane.probs_vector = np.array([0.5, 0., 0.25, 0.25]) dev = qml.device("default.qubit", wires = 2) wires = [0, 1] @qml.qnode(dev) def circuit(): qml.MultiplexerStatePreparation(np.sqrt(probs_vector), wires) return qml.probs(wires)
>>> np.round(circuit(), 2) array([0.5 , 0. , 0.25, 0.25])
For theoretical details, see arXiv:0208112.
Pauli-based computation ๐ป
New tools dedicated to fault-tolerant quantum computing (FTQC) research based on the Pauli-based computation (PBC) framework are now available! With this release, you can express, compile, and inspect workflows written in terms of Pauli product rotations (PPRs) and Pauli product measurements (PPMs), which are the building blocks for the PBC framework.
-
Writing circuits in terms of Pauli product measurements (PPMs) in PennyLane is now possible with the new
pauli_measurefunction. Using this function in tandem withPauliRotto represent PPRs unlocks surface-code FTQC research spurred from A Game of Surface Codes. (#8461) (#8631) (#8623) (#8663) (#8692)The new
pauli_measurefunction is currently only for analysis on thenull.qubitdevice, which allows for circuit inspection withspecsanddraw.Using
pauli_measurein a circuit is similar toqml.measure(a mid-circuit measurement), but requires that apauli_wordbe specified for the measurement basis:import pennylane as qml dev = qml.device("null.qubit", wires=3) @qml.qnode(dev) def circuit(): qml.Hadamard(0) qml.Hadamard(2) qml.PauliRot(np.pi / 4, pauli_word="XYZ", wires=[0, 1, 2]) ppm = qml.pauli_measure(pauli_word="XY", wires=[0, 2]) qml.cond(ppm, qml.X)(wires=1) return qml.expval(qml.Z(0))
>>> print(qml.draw(circuit)()) 0: โโHโโญRXYZ(0.79)โโญโคโXโโโโโโค <Z> 1: โโโโโRXYZ(0.79)โโโโโโโโXโโค 2: โโHโโฐRXYZ(0.79)โโฐโคโYโโโโโโค โโโโโโYou can use the
specsfunction to easily determine the circuit's resources. In this case, in addition to other gates, we can see that the circuit includes one PPR and one PPM operation (represented by thePauliRotandPauliMeasuregate types, respectively):>>> print(qml.specs(circuit)()['resources']) Total wire allocations: 3 Total gates: 5 Circuit depth: 4 Gate types: Hadamard: 2 PauliRot: 1 PauliMeasure: 1 Conditional(PauliX): 1 Measurements: expval(PauliZ): 1
-
Several
qjit-compatible compilation passes designed for Pauli-based computation are now available with this release, and are designed to work directly withpauli_measureandPauliRotoperations. (#8609) (#8764) (#8762)The compilation passes included in this release are:
-
gridsynth: This pass decomposes $Z$-basis rotations andPhaseShiftgates to either the Clifford+T basis or to other PPRs.@qml.qjit @qml.transforms.gridsynth @qml.qnode(qml.device("lightning.qubit", wires=1)) def circuit(x): qml.Hadamard(0) qml.RZ(x, 0) qml.PhaseShift(x * 0.2, 0) return qml.state() ``` ```pycon >>> circuit(1.1) [0.60284353-0.36960984j 0.5076425 +0.4922066j ]
-
Seven transforms for compiling Clifford+T gates, PPRs, and/or PPMs, including
to_ppr,commute_ppr,merge_ppr_ppm,ppr_to_ppm,ppm_compilation,reduce_t_depth, anddecompose_arbitrary_ppr.@qml.qjit(target="mlir") @qml.transforms.to_ppr @qml.qnode(qml.device("null.qubit", wires=2)) def circuit(): qml.H(0) qml.CNOT([0, 1]) qml.T(0) return qml.expval(qml.Z(0))
>>> print(qml.specs(circuit, level=2)()) ... Resource specifications: Total wire allocations: 2 Total gates: 7 Circuit depth: Not computed Gate types: PPR-pi/4: 6 PPR-pi/8: 1 ...
-
-
Directly decomposing Clifford+T gates and other small gates into PPRs is possible using the
decomposetransform with graph-based decompositions enabled (enable_graph). This allows direct decomposition of certain operators without the need to use approximate methods such as those found in theclifford_t_decompositiontransform, which can sometimes be less efficient. (#8700) (#8704) (#8857)The following operations have newly added decomposition rules in terms of PPRs (
PauliRot): -CRX,CRY,CRZ-ControlledPhaseShift-IsingXX,IsingYY,IsingZZ-PSWAP-RX,RY,RZ-SingleExcitation,DoubleExcitation-SWAP,ISWAP,SISWAP-CY,CZ,CSWAP,CNOT,ToffoliTo access these decompositions, simply specify a target gate set including
PauliRotandGlobalPhase. The following example illustrates how theCNOTgate can be represented in terms of three $\tfrac{\pi}{2}$ PPRs (IX,ZIandZX) acting on two wires:from functools import partial qml.decomposition.enable_graph() @partial(qml.transforms.decompose, gate_set={qml.PauliRot, qml.GlobalPhase}) @qml.qnode(qml.device("null.qubit", wires=2)) def circuit(): qml.CNOT([0, 1]) return qml.expval(qml.Z(0))
>>> print(qml.draw(circuit)()) 0: โโRZ(-1.57)โโญRZX(1.57)โโญGlobalPhase(0.79)โโโโโโโโโโโโค <Z> 1: โโRX(-1.57)โโฐRZX(1.57)โโฐGlobalPhase(0.79)โโRZ(3.14)โโค
Flexible and modular compilation pipelines ๐ฆ
-
Defining large and complex compilation pipelines in intuitive, modular, and flexible ways is now possible with the new
CompilePipelineclass. (#8735) (#8750) (#8731) (#8817) (#8703) (#8730) (#8751) (#8774) (#8781) (#8834)The
CompilePipelineclass allows you to chain together multiple transforms to create custom circuit optimization pipelines with ease. For example,CompilePipelineobjects can compound:>>> pipeline = qml.CompilePipeline(qml.transforms.commute_controlled, qml.transforms.cancel_inverses) >>> qml.CompilePipeline(pipeline, qml.transforms.merge_rotations) CompilePipeline(commute_controlled, cancel_inverses, merge_rotations)They can be added together with
+:>>> pipeline += qml.transforms.merge_rotations >>> pipeline CompilePipeline(commute_controlled, cancel_inverses, merge_rotations)They can be multiplied by scalars via
*to repeat compilation passes a predetermined number of times:>>> pipeline += 2 * qml.transforms.cancel_inverses(recursive=True) >>> pipeline CompilePipeline(commute_controlled, cancel_inverses, merge_rotations, cancel_inverses, cancel_inverses)
Finally, they can be modified via
listoperations likeappend,extend, andinsert:>>> pipeline.insert(0, qml.transforms.remove_barrier) >>> pipeline CompilePipeline(remove_barrier, commute_controlled, cancel_inverses, merge_rotations, cancel_inverses, cancel_inverses)By applying a created
pipelinedirectly on a quantum function as a decorator, each compilation pass therein will be applied to the circuit:import pennylane as qml pipeline = qml.transforms.merge_rotations + qml.transforms.cancel_inverses(recursive=True) @pipeline @qml.qnode(qml.device("default.qubit")) def circuit(): qml.H(0) qml.H(0) qml.RX(0.5, 1) qml.RX(0.2, 1) return qml.expval(qml.Z(0) @ qml.Z(1)) ``` ```pycon >>> print(qml.draw(circuit)()) 0: โโโโโโโโโโโโค โญ<Z@Z> 1: โโRX(0.70)โโค โฐ<Z@Z>
Analyzing algorithms quickly and easily with resource estimation ๐
-
A new
algo_errorfunction has been added to compute algorithm-specific errors from quantum circuits. This provides a dedicated entry point for retrieving error information that was previously accessible throughspecs. (#8787)The function works with QNodes and returns a dictionary of error types and their computed values:
import pennylane as qml Hamiltonian = qml.dot([1.0, 0.5], [qml.X(0), qml.Y(0)]) dev = qml.device("default.qubit") @qml.qnode(dev) def circuit(): qml.TrotterProduct(Hamiltonian, time=1.0, n=4, order=2) return qml.state()
>>> qml.resource.algo_error(circuit)() {'SpectralNormError': SpectralNormError(0.25)} -
Fast resource estimation is now available for many algorithms, including:
-
The Generalized Quantum Signal Processing (GQSP) algorithm and its time evolution via the
qml.estimator.GQSPandqml.estimator.GQSPTimeEvolutionresource operations. (#8675) -
The Qubitization algorithm via two new resource operators:
qml.estimator.Reflectionandqml.estimator.Qubitization. (#8675) -
The Quantum Signal Processing (QSP) and Quantum Singular Value Transformation (QSVT) algorithms via two new resource operators:
qml.estimator.QSP. (#8733) -
The unary iteration implementation of QPE via the new
qml.estimator.UnaryIterationQPEsubroutine, which makes it possible to reduceTandToffoligate counts in exchange for using additional qubits. (#8708) -
Trotterization for Pauli Hamiltonians, using the new
qml.estimator.PauliHamiltonianresource Hamiltonian class and the newqml.estimator.TrotterPauliresource operator. (#8546) (#8761)>>> import pennylane.estimator as qre >>> pauli_terms = {"X": 10, "XX": 5, "XXXX": 3, "YY": 5, "ZZ":5, "Z": 2} >>> pauli_ham = qre.PauliHamiltonian(num_qubits=10, pauli_terms=pauli_terms) >>> res = qre.estimate(qre.TrotterPauli(pauli_ham, num_steps=1, order=2)) >>> res.total_gates 2844 ``` The ``PauliHamiltonian`` object also makes it easy to access the total number of terms (Pauli words) in the Hamiltonians with the ``PauliHamiltonian.num_terms`` property: ```pycon >>> pauli_ham.num_terms 30
-
Linear combination of unitaries (LCU) representations of
qml.estimator.PauliHamiltonianHamiltonians via the newqml.estimator.SelectPaulioperator. (#8675)
-
-
The new
resource_keykeyword argument of theResourceConfig.set_precisionmethod makes it possible to set precisions for a larger variety ofResourceOperators in theestimatormodule, includingphase_grad_precisionandcoeff_precisionforTrotterVibronicandTrotterVibrational,rotation_precisionforGQSPandQSPandpoly_approx_precisionforGQSPTimeEvolution. (#8561)>>> vibration_ham = qre.VibrationalHamiltonian(num_modes=2, grid_size=4, taylor_degree=2) >>> trotter = qre.TrotterVibrational(vibration_ham, num_steps=10, order=2) >>> config = qre.ResourceConfig() >>> qre.estimate(trotter, config = config).total_gates 123867.0 >>> config.set_precision(qre.TrotterVibrational, precision=1e-10, resource_key='phase_grad_precision') >>> qre.estimate(trotter, config = config).total_gates 124497.0
Seamless inspection for compiled programs ๐
-
Analyzing resources throughout each step of a compilation pipeline can now be done on
qjit'd workflows withspecs, providing a pass-by-pass overview of quantum circuit resources. (#8606) (#8860)Consider the following
qjit'd circuit with two compilation passes applied:@qml.qjit @qml.transforms.merge_rotations @qml.transforms.cancel_inverses @qml.qnode(qml.device('lightning.qubit', wires=2)) def circuit(): qml.RX(1.23, wires=0) qml.RX(1.23, wires=0) qml.X(0) qml.X(0) qml.CNOT([0, 1]) return qml.probs()
The supplied
leveltospecscan be an individualintvalue or an iterable of multiple levels. Additionally, the strings"all"and"all-mlir"are allowed, returning circuit resources for all user-applied transforms and MLIR passes, or all user-applied MLIR passes only, respectively.>>> print(qml.specs(circuit, level=[2, 3])()) Device: lightning.qubit Device wires: 2 Shots: Shots(total=None) Level: ['cancel-inverses (MLIR-1)', 'merge-rotations (MLIR-2)'] Resource specifications: Level = cancel-inverses (MLIR-1): Total wire allocations: 2 Total gates: 3 Circuit depth: Not computed Gate types: RX: 2 CNOT: 1 Measurements: probs(all wires): 1 ------------------------------------------------------------ Level = merge-rotations (MLIR-2): Total wire allocations: 2 Total gates: 2 Circuit depth: Not computed Gate types: RX: 1 CNOT: 1 Measurements: probs(all wires): 1
-
A new
markerfunction allows for easy inspection at particular points in a set of applied compilation passes withspecsanddrawinstead of having to incrementlevelby integer amounts. (#8684)The
markerfunction works like a transform in PennyLane, and can be deployed as a decorator on top of QNodes:@qml.marker(level="rotations-merged") @qml.transforms.merge_rotations @qml.marker(level="my-level") @qml.transforms.cancel_inverses @qml.transforms.decompose(gate_set={qml.RX}) @qml.qnode(qml.device('lightning.qubit')) def circuit(): qml.RX(0.2,0) qml.X(0) qml.X(0) qml.RX(0.2, 0) return qml.state()
The string supplied to
markercan then be used as an argument tolevelindrawandspecs, showing the cumulative result of applying transforms up to the marker:>>> print(qml.draw(circuit, level="my-level")()) 0: โโRX(0.20)โโRX(3.14)โโRX(3.14)โโRX(0.20)โโค State >>> print(qml.draw(circuit, level="rotations-merged")()) 0: โโRX(6.68)โโค State
Note that
markeris currently not compatible with programs compiled withqjit.
Improvements ๐
Resource estimation
-
It is now easier to access the total gates and wires in resource estimates with the
total_wiresandtotal_gatesproperties in theqml.estimator.Resourcesclass. (#8761)import pennylane.estimator as qre def circuit(): qml.X(0) qml.Z(0) qml.Y(1)
>>> resources = qre.estimate(circuit)() >>> resources.total_gates 3 >>> resources.total_wires 2 -
The
QROMtemplate now uses fewer resources when argument values arerestored=Trueandsel_swap_depth=1. (#8761) -
The resource decomposition of
PauliRotnow matches the optimal resources when thepauli_stringargument isXXorYY. (#8562) -
It is now possible to estimate the resources for quantum circuits that contain or decompose into any of the following symbolic operators:
ChangeOpBasis,Prod,Controlled,ControlledOp,Pow, and/orAdjoint. (#8464) -
Qualtran call graphs built via
qml.to_bloqnow provide faster resource counting by using PennyLane's resource estimation module. To use the previous behaviour based on PennyLane decompositions, setcall_graph='decomposition'. (#8390)The old behaviour was the following:
>>> qml.to_bloq(qml.QFT(wires=range(5)), map_ops=False, call_graph='decomposition').call_graph()[1] {Hadamard(): 5, ZPowGate(exponent=-0.15915494309189535, eps=1e-11): 10, ZPowGate(exponent=-0.15915494309189535, eps=5e-12): 10, ZPowGate(exponent=0.15915494309189535, eps=5e-12): 10, CNOT(): 20, TwoBitSwap(): 2 }
The new behaviour is now this:
>>> qml.to_bloq(qml.QFT(wires=range(5)), map_ops=False).call_graph()[1] {Hadamard(): 5, CNOT(): 26, TGate(is_adjoint=False): 1320}
-
The
CDFHamiltonian,THCHamiltonian,VibrationalHamiltonian, andVibronicHamiltonianclasses have been modified to take the 1-norm of the Hamiltonian as an optional argument. (#8697) -
Input validation has been added to various operators and functions in the
estimatormodule to raise more informative errors. (#8835) -
The
ResourcesUndefinedErrorhas been removed from theadjoint,ctrl, andpowresource decomposition methods ofResourceOperatorto avoid using errors as control flow. (#8598) (#8811)
Decompositions
-
The graph-based decomposition system now supports basis-changing Clifford gates and decomposing
RX,RYandRZrotations into each other. (#8569) -
A new decomposition has been added for the Controlled
SemiAdder, which reduces the number of gates in its decomposition by controlling fewer gates. (#8423) -
A new
gate_setmethod has been added toDeviceCapabilitiesthat makes it easy to produce a set of gate names that are directly compatible with the given device. (#8522)>>> dev = qml.device('lightning.qubit') >>> dev.capabilities.gate_set() {'Adjoint(CNOT)', 'Adjoint(CRX)', 'Adjoint(CRY)', 'Adjoint(CRZ)', 'Adjoint(CRot)', ...
-
It is now possible to minimize the number of work wires in decompositions by activating the new graph-based decomposition system (
enable_graph) and settingminimize_work_wires=Truein thedecomposetransform. The decomposition system will select decomposition rules that minimize the maximum number of simultaneously allocated work wires. (#8729) (#8734) -
A new decomposition rule has been added to
QubitUnitarywhich reduces the number of CNOTs used to decompose certain two-qubitQubitUnitaryoperations. (#8717) -
Operator decompositions now only need to be defined in the graph decomposition system, as
Operator.decompositionwill fallback to the first entry inqml.list_decompsif theOperator.compute_decompositionmethod is not overridden. (#8686) -
The
BasisRotationgraph decomposition can now scale to larger workflows withqjitas it has been re-written in aqjitfriendly way using PennyLane control flow. (#8560) (#8608) (#8620) -
The graph-based decompositions system enabled via
enable_graphnow additionally supports many existing templates. (#8520) (#8515) (#8516) (#8555) (#8558) (#8538) (#8534) (#8582) (#8543) (#8554) (#8616) (#8602) (#8600) (#8601) (#8595) (#8586) (#8614)The supported templates with this release include:
QSVT-AmplitudeEmbedding-AllSinglesDoubles-SimplifiedTwoDesign-GateFabric-AngleEmbedding-IQPEmbedding-kUpCCGSD-QAOAEmbedding-BasicEntanglerLayers-HilbertSchmidt-LocalHilbertSchmidt-QuantumMonteCarlo-ArbitraryUnitary-ApproxTimeEvolution-ParticleConservingU2-ParticleConservingU1-CommutingEvolution
-
A new decomposition has been added to
Toffoli. This decomposition uses one work wire andTemporaryANDoperators to reduce the resources needed. (#8549) -
The
pauli_decomposenow supports decomposing scipy's sparse matrices, allowing for efficient decomposition of large matrices that cannot fit in memory when written as dense arrays. (#8612)import scipy import numpy as np arr = np.array([[0, 0, 0, 1, 0, 0, 0, 0], [0, 0, 1, 0, 0, 0, 0, 0], [0, 1, 0, 0, 0, 0, 0, 0], [1, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 1], [0, 0, 0, 0, 0, 0, 1, 0], [0, 0, 0, 0, 0, 1, 0, 0], [0, 0, 0, 0, 1, 0, 0, 0]]) sparse = scipy.sparse.csr_array(arr)
>>> qml.pauli_decompose(sparse) 1.0 * (I(0) @ X(1) @ X(2))
-
The graph-based decomposition system now supports decomposition rules that contain mid-circuit measurements. (#8079)
-
A new decomposition has been added to the adjoint of
TemporaryANDthat relies on mid-circuit measurements and does not require anyTgates. (#8633) -
The decompositions for several templates have been updated to use
ChangeOpBasis, which makes their decompositions more resource-efficient by eliminating unnecessary controlled operations. The templates includePhaseAdder,TemporaryAND,QSVT, andSelectPauliRot. (#8490) (#8577) (#8721) -
The
clifford_t_decompositiontransform now uses the Ross-Selinger algorithm (method="gridsynth") as the default method for decomposing single-qubit Pauli rotation gates in the Clifford+T basis. The Solovay-Kitaev algorithm (method="sk") was used as default in previous releases. (#8862)
Other improvements
-
Quantum compilation passes in MLIR and xDSL can now be applied using the core PennyLane transform infrastructure, instead of using Catalyst-specific tools. This is made possible by a new argument in
transformandTransformcalledpass_name, which accepts a string corresponding to the name of the compilation pass. Thepass_nameargument ensures that the given compilation pass will be used whenqjitis applied to a workflow, where the pass is performed in MLIR or xDSL. (#8539) (#8810)my_transform = qml.transform(pass_name="cancel-inverses") @qml.qjit @my_transform @qml.qnode(qml.device('lightning.qubit', wires=4)) def circuit(): qml.X(0) qml.X(0) return qml.expval(qml.Z(0))
For additional details see the "Transforms with Catalyst" section in
transform. -
When program capture is enabled,
qml.adjointandqml.ctrlcan now be called on operators that were constructed ahead of time and used as closure variables. (#8816) -
The constant to convert the length unit Bohr to Angstrom in
qml.qchemhas been updated to use scipy constants, leading to more consistent and standardized conversion. (#8537) -
Transform decorator arguments can now be defined without
@partial, leading to a simpler interface. (#8730) (#8754)For example, the following two usages are equivalent:
@partial(qml.transforms.decompose, gate_set={qml.RX, qml.CNOT}) @qml.qnode(qml.device('default.qubit', wires=2)) def circuit(): qml.Hadamard(wires=0) qml.CZ(wires=[0,1]) return qml.expval(qml.Z(0))
@qml.transforms.decompose(gate_set={qml.RX, qml.CNOT}) @qml.qnode(qml.device('default.qubit', wires=2)) def circuit(): qml.Hadamard(wires=0) qml.CZ(wires=[0,1]) return qml.expval(qml.Z(0))
-
TransformContainerhas been renamed toBoundTransform. The old name is still available in the same location. (#8753) -
More programs can be captured because
qml.for_loopnow falls back to a standard Pythonforloop if capturing a condensed, structured loop fails with program capture enabled. (#8615) -
qml.condwill now use standard Python logic if all predicates have concrete values, leading to shorter, more efficient jaxpr programs. Nested control flow primitives will no longer be captured as they are not needed. (#8634) -
Added a keyword argument
recursivetoqml.transforms.cancel_inversesthat enables recursive cancellation of nested pairs of mutually inverse gates. This allows the transform to cancel larger blocks of inverse gates without having to scan the circuit from scratch. By default, the recursive cancellation is enabled (recursive=True). To obtain the previous behaviour, disable it by settingrecursive=False. (#8483) -
qml.while_loopandqml.for_loopcan now lazily dispatch to Catalyst when called, instead of dispatching upon creation. (#8786) -
qml.gradandqml.jacobiannow lazily dispatch to Catalyst and program capture, allowing forqml.qjit(qml.grad(c))andqml.qjit(qml.jacobian(c))to work. (#8382) -
Both the generic and transform-specific application behavior of a
qml.transforms.core.TransformDispatchercan now be overwritten withTransformDispatcher.generic_registerandmy_transform.register, leading to easier customization of transforms. (#7797) -
With capture enabled, measurements can now be performed on
Operatorinstances passed as closure variables from outside the workflow scope. This makes it possible to define observables outside of a QNode and still measure them inside the QNode. (#8504) -
Wires can now be specified via the
rangefunction with program capture enabled and Autograph activated via@qml.qjit(autograph=True). (#8500) -
The
decomposetransform no longer raises an error if bothgate_setandstopping_conditionare provided, or ifgate_setis a dictionary, when the new graph-based decomposition system is disabled. (#8532) -
The
SelectTHCresource operation is upgraded to allow for a trade-off between the number of qubits and T-gates. This provides more flexibility in optimizing algorithms. (#8682) -
Added a custom solver to
qml.transforms.intermediate_reps.rowcolfor linear systems over $\mathbb{Z}_2$ based on Gauss-Jordan elimination. This removes the need to install thegaloispackage for this single function and provides a performance improvement. (#8771) -
qml.measurecan now be used as a frontend forcatalyst.measure. (#8782) -
qml.condwill also accept a partial of an operator type as the true function without a false function when capture is enabled. (#8776) -
Solovay-Kitaev decomposition using the
clifford_t_decompositiontransform withmethod="sk"or directly viask_decompositionnow raises a more informativeRuntimeErrorwhen used with JAX-JIT orqjit. (#8489)
Labs: a place for unified and rapid prototyping of research software ๐งช
-
A new transform
qml.labs.transforms.select_pauli_rot_phase_gradienthas been added. This transform may reduce the number ofTgates in circuits withSelectPauliRotrotations by implementing them with a phase gradient resource state and semi-in-place addition (SemiAdder). (#8738)import pennylane as qml from pennylane.labs.transforms import select_pauli_rot_phase_gradient import numpy as np @qml.qnode(qml.device("default.qubit")) def select_pauli_rot_circ(phis): # prepare phase gradient state for i, w in enumerate([6,7,8,9]): qml.H(w) qml.PhaseShift(-np.pi / 2**i, w) for wire in [0,1]: qml.Hadamard(wire) qml.SelectPauliRot(phis, [0,1], 13, rot_axis="X") return qml.probs(13) phase_grad = select_pauli_rot_phase_gradient(select_pauli_rot_circ, angle_wires=[2,3,4,5], phase_grad_wires=[6,7,8,9], work_wires=[10,11,12], ) phis = [ (1 / 2 + 1 / 4 + 1 / 8) * 2 * np.pi, (1 / 2 + 1 / 4 + 0 / 8) * 2 * np.pi, (1 / 2 + 0 / 4 + 1 / 8) * 2 * np.pi, (0 / 2 + 1 / 4 + 1 / 8) * 2 * np.pi, ] clifford_T = qml.clifford_t_decomposition(select_pauli_rot_circ) clifford_T_phase_gradient = qml.clifford_t_decomposition(phase_grad) ``` ```pycon >>> qml.specs(clifford_T)(phis).resources.gate_types['T'] 128 >>> qml.specs(clifford_T_phase_gradient)(phis).resources.gate_types['T'] 84
Breaking changes ๐
-
The
TransformProgramclass has been renamed toCompilePipeline. For backward compatibility, theTransformProgramclass can still be accessed frompennylane.transforms.core. For naming consistency, uses of the term "transform program" have been updated to "compile pipeline" across the codebase. Correspondingly, the modulepennylane.transforms.core.transform_programhas been renamed topennylane.transforms.core.compile_pipeline, and the old name is no longer available. (#8735) -
The class to dispatch transforms, the
TransformDispatcherclass, has been renamed toTransformand is now available asqml.transform. For backward compatibility, theTransformDispatcherclass can still be accessed frompennylane.transforms.core. (#8756) -
The
final_transformproperty of theBoundTransformhas been renamed tois_final_transformto better follow the naming convention for boolean properties. Thetransformproperty of theTransformandBoundTransformhas been renamed totape_transformto avoid ambiguity. (#8756) -
The output format of
specshas been restructured into a dataclass to streamline the outputs. Some legacy information has been removed from the output, such as gradient and interface information. (#8713)Consider the following circuit:
dev = qml.device("default.qubit") @qml.qnode(dev) def circuit(): qml.X(0) qml.Y(1) qml.Z(2) return qml.state()
The new
specsoutput format is:>>> qml.specs(circuit)() Device: default.qubit Device wires: None Shots: Shots(total=None) Level: gradient Resource specifications: Total wire allocations: 3 Total gates: 3 Circuit depth: 1 Gate types: PauliX: 1 PauliY: 1 PauliZ: 1 Measurements: state(all wires): 1Whereas previously,
specsprovided:>>> qml.specs(circuit)() {'resources': Resources(num_wires=3, num_gates=3, gate_types=defaultdict(<class 'int'>, {'PauliX': 1, 'PauliY': 1, 'PauliZ': 1}), gate_sizes=defaultdict(<class 'int'>, {1: 3}), depth=1, shots=Shots(total_shots=None, shot_vector=())), 'errors': {}, 'num_observables': 1, 'num_trainable_params': 0, 'num_device_wires': 3, 'num_tape_wires': 3, 'device_name': 'default.qubit', 'level': 'gradient', 'gradient_options': {}, 'interface': 'auto', 'diff_method': 'best', 'gradient_fn': 'backprop'} -
The value
level=Noneis no longer a valid argument in the following:get_transform_program,construct_batch,draw,draw_mpl, andspecs. Please uselevel='device'instead to apply all transforms. (#8477) -
The
max_work_wiresargument of thedecomposetransform has been renamed tonum_work_wires. This change is only relevant with graph-based decompositions (enabled viaenable_graph). (#8769) -
QuantumScript.to_openqasmhas been removed. Please useto_openqasminstead. This removes duplicated functionality for converting a circuit to OpenQASM code. (#8499) -
Providing
num_stepstoevolve,exp,Evolution, andExphas been disallowed. Instead, useTrotterProductfor approximate methods, providing thenparameter to perform the Suzuki-Trotter product approximation of a Hamiltonian with the specified number of Trotter steps. (#8474)As a concrete example, consider the following case:
>>> coeffs = [0.5, -0.6] >>> ops = [qml.X(0), qml.X(0) @ qml.Y(1)] >>> H_flat = qml.dot(coeffs, ops)
Instead of computing the Suzuki-Trotter product approximation as:
>>> qml.evolve(H_flat, num_steps=2).decomposition() [RX(0.5, wires=[0]), PauliRot(-0.6, XY, wires=[0, 1]), RX(0.5, wires=[0]), PauliRot(-0.6, XY, wires=[0, 1])]
The same result would now be obtained using
TrotterProductas follows:>>> decomp_ops = qml.adjoint(qml.TrotterProduct(H_flat, time=1.0, n=2)).decomposition() >>> [simp_op for op in decomp_ops for simp_op in map(qml.simplify, op.decomposition())] [RX(0.5, wires=[0]), PauliRot(-0.6, XY, wires=[0, 1]), RX(0.5, wires=[0]), PauliRot(-0.6, XY, wires=[0, 1])]
-
Access to
add_noise,insertand noise mitigation transforms from thepennylane.transformsmodule has been removed. Instead, these functions should be imported from thepennylane.noisemodule. (#8477) -
qml.qnn.cost.SquaredErrorLosshas been removed. Instead, this hybrid workflow can be accomplished with a function such asloss = lambda *args: (circuit(*args) - target)**2. (#8477) -
Some unnecessary methods of the
CircuitGraphclass have been removed: (#8477)-
print_contentswas removed in favor ofprint(obj)-observables_in_orderwas removed in favor ofobservables-operations_in_orderwas removed in favor ofoperations-ancestors_in_order(obj)was removed in favor ofancestors(obj, sort=True)-descendants_in_order(obj)was removed in favor ofdescendants(obj, sort=True)
-
-
pennylane.devices.DefaultExecutionConfighas been removed. Instead, useExecutionConfig()to create a default execution configuration. (#8470) -
Specifying the
work_wire_typeargument inctrland other controlled operators as"clean"or"dirty"is disallowed. Use"zeroed"to indicate that the work wires are initially in the $|0\rangle$ state, and"borrowed"to indicate that the work wires can be in any arbitrary state. In both cases, the work wires are assumed to be restored to their original state upon completing the decomposition. (#8470) -
QuantumScript.shapeandQuantumScript.numeric_typeare removed. The correspondingMeasurementProcessattributes and methods should be used instead. (#8468) -
MeasurementProcess.expandhas been removed.qml.tape.QuantumScript(mp.obs.diagonalizing_gates(), [type(mp)(eigvals=mp.obs.eigvals(), wires=mp.obs.wires)])should be used instead. (#8468) -
The
qml.QNode.add_transformmethod is removed. Instead, please useQNode.transform_program.push_back(transform_container=transform_container). (#8468) -
The
dynamic_one_shottransform can no longer be applied directly on a QNode. Instead, specify the mid-circuit measurement method in QNode:@qml.qnode(..., mcm_method="one-shot"). (8781) -
The
qml.compiler.python_compilersubmodule has been removed from PennyLane. It has been migrated to Catalyst, available ascatalyst.python_interface. (#8662) -
qml.transforms.map_wiresno longer supports transforming jaxpr directly. (#8683) -
qml.cond, theQNode, transforms,qml.grad, andqml.jacobianno longer treat all keyword arguments as static arguments. They are instead treated as dynamic, numerical inputs, matching the behaviour of JAX and Catalyst. (#8290)
Deprecations ๐
-
Maintenance support of NumPy<2.0 is deprecated as of v0.44 and will be completely dropped in v0.45. Future versions of PennyLane will only work with NumPy>=2.0. We recommend upgrading your version of NumPy to benefit from enhanced support and features. (#8578) (#8497)
-
Passing a function to the
gate_setargument in thedecomposetransform is deprecated. Thegate_setargument expects a static iterable of operator type and/or operator names, and the function should be passed to thestopping_conditionargument instead. (#8533)The example below illustrates how you can provide a function as the
stopping_conditionin addition to providing agate_set. The decomposition of each operator will halt upon reaching the gates in thegate_setor when thestopping_conditionis satisfied.import pennylane as qml @qml.transforms.decompose(gate_set={"H", "T", "CNOT"}, stopping_condition=lambda op: len(op.wires) <= 2) @qml.qnode(qml.device("default.qubit")) def circuit(): qml.Hadamard(wires=[0]) qml.Toffoli(wires=[0,1,2]) return qml.expval(qml.Z(0))
>>> print(qml.draw(circuit)()) 0: โโHโโโโโโโโโญโโโโโโโโโโโโโญโโโโโโญโโโTโโโญโโโค <Z> 1: โโโโโญโโโโโโโโโโโโโญโโโโโโโโโโTโโฐXโโTโ โโฐXโโค 2: โโHโโฐXโโTโ โโฐXโโTโโฐXโโTโ โโฐXโโTโโHโโโโโโโโโค -
Access to the following functions and classes from the
resourcesmodule are deprecated. Instead, these functions must be imported from theestimatormodule. (#8484)qml.estimator.estimate_shotsrather thanqml.resources.estimate_shots-qml.estimator.estimate_errorrather thanqml.resources.estimate_error-qml.estimator.FirstQuantizationrather thanqml.resources.FirstQuantization-qml.estimator.DoubleFactorizationrather thanqml.resources.DoubleFactorization
-
The
argnumparameter has been renamed toargnumsforgrad,jacobian,jvpandvjpto better adhere to conventions in JAX and Catalyst. (#8496) (#8481) -
The
custom_decompskeyword argument toqml.devicehas been deprecated and will be removed in 0.45. Instead, withenable_graph, new decomposition rules can be defined as quantum functions with registered resources. Seepennylane.decompositionfor more details. -
qml.measure,qml.measurements.MidMeasureMP,qml.measurements.MeasurementValue, andqml.measurements.get_mcm_predicatesare now located inqml.ops.mid_measure.MidMeasureMPhas been renamed toMidMeasure.qml.measurements.find_post_processed_mcmsis nowqml.devices.qubit.simulate._find_post_processed_mcms, and is being made private, as it is a utility for tree-traversal mid-circuit measurements. (#8466) -
The
pennylane.operation.Operator.is_hermitianproperty has been deprecated and renamed topennylane.operation.Operator.is_verified_hermitianas it better reflects the functionality of this property. Access throughpennylane.operation.Operator.is_hermitianis deprecated and will be removed in v0.45. Alternatively, consider using theis_hermitianfunction instead for a thorough verification of hermiticity, at a higher computational cost. (#8494) -
The
pennylane.devices.preprocess.mid_circuit_measurementstransform is deprecated. Instead, the device should determine which MCM method to use, and explicitly include relevant preprocessing transforms if necessary. (#8467)
Internal changes โ๏ธ
-
The
_grad.pyfile has been split into multiple files within a folder for improved source code organization. (#8800) -
The
pyproject.tomlhas been updated with project dependencies to replace the requirements files. Workflows have also been updated to use installations frompyproject.toml. (8702) -
Some error handling has been updated in tests, to adjust to Python 3.14;
get_type_stradded a special branch to handleUnion. The import ofnetworkxis softened to not occur on import of PennyLane to work around a bug in Python 3.14.1. (#8568) (#8737) -
The
jaxversion has been updated to0.7.1for thecapturemodule. (#8715) (#8701) -
Improved error handling when using PennyLane's experimental program capture functionality with an incompatible JAX version. (#8723)
-
The
autoraypackage version has been updated to0.8.2. (#8674) -
Updated the schedule of nightly TestPyPI uploads to occur at the end of all weekdays rather than the beginning of all weekdays. (#8672)
-
A github workflow was added to bump Catalyst and Lightning versions in the release candidate (RC) branch, create a new release tag and draft release, tag the RC branch, and create a PR to merge the RC branch into master. (#8352)
-
Added
MCM_METHODandPOSTSELECT_MODEStrEnumobjects to improve validation and handling ofMCMConfigcreation. (#8596) -
In program capture, transforms now have a single transform primitive with a
transformparam that stores theTransform. Before, each transform had its own primitive stored on theTransform._primitiveprivate property. (#8576) (#8639) -
Updated documentation check workflow to run on pull requests on
v[0-9]+\.[0-9]+\.[0-9]+-docsbranches. (#8590) -
When program capture is enabled, there is no longer caching of the jaxpr on the QNode. (#8629)
-
The
gradandjacobianprimitives now store the function underfn. There is also now a singlejacobian_pprimitive for use in program capture. (#8357) -
The versions for
pylint,isortandblackinformat.ymlhave been updated. (#8506) -
Reclassified
registersas a tertiary module for use withtach. (#8513) -
The
LegacyDeviceFacadewas refactored to implementsetup_execution_configandpreprocess_transformsseparately as opposed to implementing a singlepreprocessmethod. Additionally, themid_circuit_measurementstransform has been removed from the preprocess transform program. Instead, the best mcm method is chosen insetup_execution_config. By default, thecapabilitiesdictionary is queried for the"supports_mid_measure"property. If the underlying device defines a TOML file, thesupported_mcm_methodsfield in the TOML file is used as the source of truth. (#8469) (#8486) (#8495) -
The various private functions of the
qml.estimator.FirstQuantizationclass have been modified to avoid usingnumpy.matrixas this function is deprecated. (#8523) -
The
ftqcmodule now includes dummy transforms for several Catalyst/MLIR passes (to-ppr,commute-ppr,merge-ppr-ppm,decompose-clifford-ppr,decompose-non-clifford-ppr,ppr-to-ppm,ppr-to-mbqcandreduce-t-depth), to allow them to be captured as primitives in PLxPR and mapped to the MLIR passes in Catalyst. This enables using the passes with the unified compiler and program capture. (#8519) (#8544) -
Added a
skip_decomp_matrix_checkargument topennylane.ops.functions.assert_validthat allows the test to skip the matrix check part of testing a decomposition rule but still verify that the resource function is correct. (#8687) -
Simplified the decomposition pipeline for the
estimatormodule.qml.estimator.estimate()was updated to call the base class'ssymbolic_resource_decompmethod directly. (#8641) -
Disabled autograph for the
PauliRotdecomposition rule, as it should not be used. (#8765)
Documentation ๐
-
Minor corrections in the docstring code examples for
QAOAEmbeddingandParticleConservingU1were made. (#8895) -
A note was made in the documentation of
qml.transforms.decomposefor its behaviour when graph-based decompositions are enabled withqjitpresent. It clarifies that, when used withqjit, non-deterministic graph solutions may lead to non-executable programs if intermediate gates are not executable by Catalyst. (#8894) -
The code example in the documentation for
qml.decomposition.register_resourceshas been updated to adhere to renamed keyword arguments and default behaviour ofnum_work_wires. (#8550) -
A note clarifying that the factors of a
ChangeOpBasisare iterated in reverse order has been added to the documentation ofChangeOpBasis. (#8757) -
The documentation of
qml.transforms.rz_phase_gradienthas been updated with respect to the sign convention of phase gradient states, how it prepares the phase gradient state in the code example, and the verification of the code example result. (#8536) -
The docstring for
qml.devicehas been updated to include a section on custom decompositions, and a warning about the removal of thecustom_decompskwarg in v0.45. Additionally, the Building a plugin page now includes instructions on using thedecomposetransform for device-level decompositions. The documentation for Compiling circuits has also been updated with a warning message aboutcustom_decompsfuture removal. (#8492) (#8564) -
The documentation for
GeneralizedAmplitudeDampinghas been updated to match the standard convention in literature for the definition of the Kraus matrices. (#8707) -
Improved documentation in the
pennylane.transformsmodule and added documentation testing. (#8557) -
Updated various docstring examples in the
fouriermodule to be compatible with the new documentation testing approach. (#8635) -
The
estimatormodule documentation has been revised for clarity. (#8827) (#8829) (#8830) (#8832) (#8892)
Bug fixes ๐
-
Fixed the difference between the output dimensions of the dynamic one-shot and single-branch-statistics mid-circuit measurement methods. (#8856)
-
Fixed a bug in
qml.estimator.QubitizeTHCwhere specified arguments forPrepareandSelectresource operators were being ignored in favor of default ones. [(#8858)] (#8858) -
Fixed a bug in
torch.vmapthat produced an error when it was used with native parameter broadcasting andqml.RZ. (#8760) -
Fixed various incorrect decomposition rules. (#8812)
-
Fixed a bug where
_double_factorization_compressedofpennylane/qchem/factorization.pyused to useXforZparameter initialization. (#8689) -
Fixed some numerical stability issues of
apply_uniform_rotation_daggerby using a fixed floating-point number tolerance fromnp.finfo. (#8780) -
Fixed handling of floating-point errors in the norm of the state when applying mid-circuit measurements. (#8741)
-
Updated
interface-unit-tests.ymlto use the input parameterpytest_additional_argswhen running pytest. (#8705) -
Fixed a bug in
resolve_work_wire_typewhich incorrectly returned a value ofzeroedifboth work_wiresandbase_work_wireswere empty, causing an incorrect work wire type. (#8718) -
Fixed the warnings-as-errors CI action which was failing due to an incompatibility between
pytest-xdistandpytest-benchmark. Disabling the benchmark package allows the tests to be collected and executed. (#8699) -
Added a missing
expand_transformtoparam_shift_hessianto pre-decompose operations until they are supported. (#8698) -
Fixed a bug in the
default.mixeddevice where certain diagonal operations were incorrectly reshaped during application when using parameter broadcasting. (#8593) -
If
.allocation.Allocateor.allocation.Deallocateinstructions are encountered with graph-based decompositions enabled, they are now ignored instead of raising a warning. (#8553) -
Fixed a bug in
clifford_t_decompositionwithmethod="gridsynth"andqjit, where using a cached decomposition with the same parameter caused an error. (#8535) -
Fixed a bug in
SemiAdderwhere the results were incorrect when morework_wiresthan required were passed. (#8423) -
Fixed a bug where the deferred-measurement method was used silently even if
mcm_method="one-shot"was explicitly requested, when a device that extends theLegacyDevicedoes not declare support for mid-circuit measurements. (#8486) -
Fixed a bug where a
KeyErrorwas raised when querying the decomposition rule for an operator in the gate set from aDecompGraphSolution. (#8526) -
Fixed a bug where mid-circuit measurements were generating incomplete QASM. (#8556)
-
Fixed a bug where
specsincorrectly computed the circuit depth in the presence of classically controlled operators. (#8668) -
Fixed a bug where an error was raised when trying to decompose a nested composite operator with capture and the new graph system enabled. (#8695)
-
Fixed a bug where the
change_op_basisfunction could not be captured when theuncompute_opargument is left out. (#8695) -
Fixed a bug in the
rs_decompositionfunction where valid solution candidates were being rejected. (#8625) -
Fixed a bug where decomposition rules were sometimes incorrectly disregarded by the
DecompositionGraphwhen a higher level decomposition rule uses dynamically allocated work wires viaqml.allocate. (#8725) -
Fixed a bug where
ChangeOpBasiswas not correctly reconstructed usingqml.pytrees.unflatten(*qml.pytrees.flatten(op)). (#8721) -
Fixed a bug where
qml.estimator.SelectTHC,qml.estimator.QubitizeTHC, andqml.estimator.PrepTHCwere not accounting for auxiliary wires correctly. (#8719) -
Fixed a bug where the associated
expand_transformdoes not stay with the originalTransformin aCompilePipelineduring manipulations of theCompilePipeline. (#8774) -
Fixed a bug where an error was raised when
to_openqasmis used withqml.decomposition.enable_graph(). (#8809)
Contributors โ๏ธ
This release contains contributions from (in alphabetical order):
Runor Agbaire, Guillermo Alonso, Utkarsh Azad, Joseph Bowles, Astral Cai, Yushao Chen, Isaac De Vlugt, Diksha Dhawan, Marcus Edwards, Lillian Frederiksen, Diego Guala, Sengthai Heng, Austin Huang, Soran Jahangiri, Jeffrey Kam, Jacob Kitchen, Christina Lee, Joseph Lee, Anton Naim Ibrahim, Lee J. O'Riordan, Mudit Pandey, Gabriela Sanchez Diaz, Shuli Shu, Jay Soni, Nate Stemen, Theodoros Trochatos, Leo Wei, David Wierichs, Shifan Xu, Hongsheng Zheng, Zinan Zhou.