New features since last release
Sum of Slaters State Preparation 🆘
-
A new state preparation method called
SumOfSlatersPrepis now available. This state preparation routine introduced in Fomichev et al., PRX Quantum 5, 040339 (see the associated demo) is a state-of-the-art technique for algorithms that require a high-quality initial state, like in ground-state energy estimation algorithms of chemical systems. (#8964) (#8997) (#9228) (#9323)Consider this sparse state on three qubits, specified by normalized coefficients and statevector indices pointing to the populated computational basis states:
import pennylane as qp import numpy as np coefficients = np.array([1, -1j, 1j, 1]) / np.sqrt(4) indices = (0, 1, 2, 4) num_wires = 3 wires = list(range(num_wires))
The
SumOfSlatersPrepoperation requires thecoefficients,indices, andwiresas input. It efficiently prepares sparse states using auxiliary wires,QROM\ s, reversible bit encodings, and a dense-state preparation on a smaller subspace. Its implementation can be inspected by using PennyLane's graph-based decomposition algorithm (enable_graph) with the following gate set and five auxiliary wires (work_wires).qp.decomposition.enable_graph() gate_set = {"QROM", "TemporaryAND", "Adjoint(TemporaryAND)", "StatePrep", "CNOT", "X"} num_work_wires = 5 @qp.decompose(gate_set=gate_set, num_work_wires=num_work_wires) @qp.qnode(qp.device("lightning.qubit", wires=num_work_wires+num_wires)) def circuit(): qp.SumOfSlatersPrep(coefficients, wires, indices) return qp.state()
>>> print(qp.draw(circuit, show_matrices=False, max_length=180)()) 0: ────────────────╭QROM(M0)──X─╭●────────────●╮────╭●────────────●╮──X─╭●───────────────●╮─────────────┤ State 1: ────────────────├QROM(M0)──X─├●────────────●┤──X─├●────────────●┤──X─├●───────────────●┤──X──────────┤ State 2: ────────────────├QROM(M0)────│──╭●─────●╮───│──X─│──╭●─────●╮───│────│──╭●────────●╮───│──X──────────┤ State <DynamicWire>: ─╭Allocate─╭|Ψ⟩─├QROM(M0)────│──│───────│───│────│──│──╭X───│───│────│──│──╭X──────│───│─╭Deallocate─┤ State <DynamicWire>: ─├Allocate─╰|Ψ⟩─├QROM(M0)────│──│──╭X───│───│────│──│──│────│───│────│──│──│──╭X───│───│─├Deallocate─┤ State <DynamicWire>: ─├Allocate──────╰QROM(M0)────│──│──│────│───│────│──│──│────│───│────│──│──│──│────│───│─├Deallocate─┤ State <DynamicWire>: ─├Allocate───────────────────╰⊕─├●─│───●┤──⊕╯────╰⊕─├●─│───●┤──⊕╯────╰⊕─├●─│──│───●┤──⊕╯─├Deallocate─┤ State <DynamicWire>: ─╰Allocate──────────────────────╰⊕─╰●──⊕╯───────────╰⊕─╰●──⊕╯───────────╰⊕─╰●─╰●──⊕╯─────╰Deallocate─┤ State
The smaller dense state preparation is represented by
|Ψ⟩, which is on two wires, and the<DynamicWire>labels represent dynamically allocated wires (withallocate) used in its decomposition.
Workflow Inspection 🔍
-
When using
specswithqjitandlevel="all", you can now display the returnedCircuitSpecsas a table, allowing you to easily see how circuit resources evolve with each stage of compilation. In addition,specsnow supports settinglevel="user"for workflows compiled withqjit, returning circuit specifications after all user-specified transforms have been applied. (#9088) (#9307) (#9426)@qp.qjit @qp.transforms.merge_rotations @qp.transforms.cancel_inverses @qp.qnode(qp.device("lightning.qubit", wires=2)) def circuit(): qp.RX(1.23,0) qp.RX(1.23,0) qp.X(0) qp.H(0) qp.H(0) return qp.probs()
>>> print(qp.specs(circuit, level="all")()) Device: lightning.qubit Device wires: 2 Shots: Shots(total=None) Levels: - 0: Before MLIR Passes - 1: cancel-inverses - 2: merge-rotations <BLANKLINE> ↓Metric Level→ | 0 | 1 | 2 --------------------------------- Wire allocations | 2 | 2 | 2 Total gates | 5 | 3 | 2 Gate counts: | - Hadamard | 2 | 0 | 0 - PauliX | 1 | 1 | 1 - RX | 2 | 2 | 1 Measurements: | - probs(all wires) | 1 | 1 | 1
We can also observe the specifications after all user-applied transforms using
level="user":>>> print(qp.specs(circuit, level="user")()) Device: lightning.qubit Device wires: 2 Shots: Shots(total=None) Level: merge-rotations Wire allocations: 2 Total gates: 2 Gate counts: - PauliX: 1 - RX: 1 Measurements: - probs(all wires): 1 Depth: Not computed
-
When inspecting a circuit via the
levelargument inspecsordraw, markers placed in aCompilePipeline(withmarker) are now accessible exclusively via theirlabel, making it much easier to track levels of compilation without having to track shifting integerlevelvalues. In addition, markers can now be added directly to aCompilePipelinewith theadd_markermethod, and printing aCompilePipelinenow legibly distinguishes transforms and markers. (#8990) (#9007) (#9076) (#9102)pipeline = qp.CompilePipeline() pipeline.add_marker("no-transforms") pipeline += qp.transforms.cancel_inverses @qp.marker("after-cancel-inverses") @pipeline @qp.qnode(qp.device("default.qubit")) def circuit(): qp.X(0) qp.H(0) qp.H(0) return qp.probs()
The new string representation of
CompilePipelineallows you to inspect the transforms and markers:>>> print(circuit.compile_pipeline) CompilePipeline( ├─▶ no-transforms [1] cancel_inverses() └─▶ after-cancel-inverses )As usual, marker labels can be used as an argument to
levelinspecsanddraw, showing the cumulative result of compilation up to the provided marker:>>> print(qp.draw(circuit, level="no-transforms")()) # or level=0 0: ──X──H──H─┤ Probs >>> print(qp.draw(circuit, level="after-cancel-inverses")()) # or level=1 0: ──X─┤ Probs
-
specsnow includes PPR and PPM weights in its output, allowing for better categorization of PPMs and PPRs in workflows compiled withqjit. (#8983)@qp.qjit @qp.transforms.to_ppr @qp.qnode(qp.device("null.qubit", wires=2)) def circuit(): qp.H(0) qp.CNOT([0, 1]) m = qp.measure(0) qp.T(0) return qp.expval(qp.Z(0))
>>> print(qp.specs(circuit, level="user")()) Device: null.qubit Device wires: 2 Shots: Shots(total=None) Level: to-ppr <BLANKLINE> Wire allocations: 2 Total gates: 11 Gate counts: - GlobalPhase: 3 - PPM-w1: 1 - PPR-pi/4-w1: 5 - PPR-pi/4-w2: 1 - PPR-pi/8-w1: 1 Measurements: - expval(PauliZ): 1 Depth: Not computed
-
specshas been upgraded with significantly faster processing of large workflows with many gates and/or measurements forqjitcompiled workflows in pass-by-pass mode. This is achieved using Catalyst'sResourceAnalysispass behind the scenes, improving upon the former implementation. For more details, check out the Catalyst v0.15 release notes. (#9279) -
specsnow returns measurement information forqjitworkloads when usinglevel="device". (#8988) -
When using pass-by-pass
specswith Catalyst, the output will no longer display a"Before Tape Transforms"level if no tape transforms have been applied. In particular, for scenarios where no tape transforms are present, the"Before MLIR passes"level becomes level0. In scenarios with at least one tape transform, level0corresponds to"Before Tape Transforms"and"Before MLIR passes"is the level after all tape transforms but before the first MLIR pass. (#9091) (#9166)
QSVT Angle Solver 📐
-
A new angle solver has been added to find QSVT phase angles faster for large-degree polynomials. This can be accessed by setting
angle_solver="iterative-optax"inqsvtandpoly_to_angles, where the benefits are seen when when repeatedly evaluating the same-degree polynomial with different coefficients. Note that this requiresoptaxto be installed. (#8685) (#9435)poly = np.array([0, 1.0, 0, -1/2, 0, 1/3]) qsvt_angles = qp.poly_to_angles(poly, "QSVT", angle_solver="iterative-optax")
>>> print(qsvt_angles) [-4.74724627 1.51868559 0.57952342 0.57952342 1.51868559 -0.03485729]
Decomposition Inspection and Pre-defined Gate Sets 📠
New tools dedicated to accessible inspectability of PennyLane's graph-based decomposition system (enabled with enable_graph) are now available! With this release, you can query the solutions of the graph-based system to understand how PennyLane decomposed a circuit, why specific rules where chosen over others, and more.
-
It is now possible to assign custom names to decomposition rules using the
nameargument inqp.register_resources, making it easier to identify specific decomposition rules. (#9257)import pennylane as qp qp.decomposition.enable_graph() @qp.register_resources({qp.CNOT: 1, qp.H: 2}, name='my_cz_rule') def cz_to_h_cnot(wires): qp.H(wires[1]) qp.CNOT(wires) qp.H(wires[1]) ``` ```pycon >>> cz_to_h_cnot.name 'my_cz_rule'
-
A new function called
inspect_decompsallows for the visualization and inspection of all possible decomposition paths the graph system can take for a concrete operator instance. (#9322) (#9359) (#9427)For each decomposition rule applicable to the operator instance, the output includes its name, circuit diagram, gate count, and wire allocation (if any):
>>> qp.inspect_decomps(qp.CRX(0.5, wires=[0, 1])) Decomposition 0 (name: _crx_to_rx_cz) 0: ───────────╭●────────────╭●─┤ 1: ──RX(0.25)─╰Z──RX(-0.25)─╰Z─┤ Gate Count: {RX: 2, CZ: 2} Decomposition 1 (name: _crx_to_rz_ry) 0: ─────────────────────╭●────────────╭●────────────┤ 1: ──RZ(1.57)──RY(0.25)─╰X──RY(-0.25)─╰X──RZ(-1.57)─┤ Gate Count: {RZ: 2, RY: 2, CNOT: 2} Decomposition 2 (name: _crx_to_h_crz) 0: ────╭●───────────┤ 1: ──H─╰RZ(0.50)──H─┤ Gate Count: {Hadamard: 2, CRZ: 1} Decomposition 3 (name: _crx_to_ppr) 0: ───────────╭RZX(-0.25)─┤ 1: ──RX(0.25)─╰RZX(-0.25)─┤ Gate Count: {PauliRot(pauli_word=ZX): 1, PauliRot(pauli_word=X): 1}
By default,
inspect_decompsdisplays all available decomposition rules for an operator. Alternatively, a single decomposition rule can be inspected by passing its name:>>> qp.inspect_decomps(qp.CRX(0.5, wires=[0, 1]), "_crx_to_h_crz") Decomposition 0 (name: _crx_to_h_crz) 0: ────╭●───────────┤ 1: ──H─╰RZ(0.50)──H─┤ Gate Count: {Hadamard: 2, CRZ: 1}
-
A new function called
decomp_inspectoris available for verifying how the decomposition graph chooses decomposition rules for each operator instance in a circuit. (#9359) (#9436)The
decomp_inspectoracts as a transform that can be applied on a QNode as a decorator. It returns an object that allows for interactively querying a given operator to identify which decomposition rules were considered and which one was chosen.Consider the following example where we want to efficiently decompose a
MultiRZinto single-qubit rotations andCNOTs:qp.decomposition.enable_graph() gate_sets = {"CNOT", "RX", "RY", "RZ", "Identity", "GlobalPhase", "MidMeasureMP"} @qp.decomp_inspector(gate_set=gate_sets, num_work_wires=2) @qp.qnode(qp.device("default.qubit")) def circuit(): qp.ctrl(qp.MultiRZ(0.5, [0, 1]), control=[3, 4, 5]) return qp.probs() inspector = circuit()
We can then call the
inspector'sinspect_decompsmethod and provide theMultiRZinstance of interest to see which decomposition rules were considered.>>> inspector.inspect_decomps(qp.ctrl(qp.MultiRZ(0.5, [0, 1]), control=[3, 4, 5]), num_work_wires=2) CHOSEN: Decomposition 0 (name: flip_zero_ctrl_values(_ctrl_single_work_wire)) <DynamicWire>: ──Allocate─╭X─╭●─────────────╭X──Deallocate─┤ 3: ───────────├●─│──────────────├●─────────────┤ 4: ───────────├●─│──────────────├●─────────────┤ 5: ───────────╰●─│──────────────╰●─────────────┤ 0: ──────────────├MultiRZ(0.50)────────────────┤ 1: ──────────────╰MultiRZ(0.50)────────────────┤ First-Level Expansion Gates: {MultiControlledX(num_control_wires=3, num_work_wires=0, num_zero_control_values=0, work_wire_type=borrowed): 2, Controlled(MultiRZ(num_wires=2), num_control_wires=1, num_work_wires=0, num_zero_control_values=0, work_wire_type=borrowed): 1} Wire Allocations: {'zero': 1} Full Expansion Gates: {RZ: 58, CNOT: 34, GlobalPhase: 64, RY: 18, RX: 8, MidMeasure: 2} Weighted Cost: 120.0 Decomposition 1 (name: to_controlled_qubit_unitary) Not applicable (provided operator instance does not meet all conditions for this rule). Decomposition 2 (name: controlled(_multi_rz_decomposition)) 0: ─╭X─╭RZ(0.50)─╭X─┤ 1: ─├●─│─────────├●─┤ 3: ─├●─├●────────├●─┤ 4: ─├●─├●────────├●─┤ 5: ─╰●─╰●────────╰●─┤ First-Level Expansion Gates: {Controlled(RZ, num_control_wires=3, num_work_wires=0, num_zero_control_values=0, work_wire_type=borrowed): 1, MultiControlledX(num_control_wires=4, num_work_wires=0, num_zero_control_values=0, work_wire_type=borrowed): 2} Full Expansion Gates: {GlobalPhase: 76, RX: 16, MidMeasure: 4, RY: 24, RZ: 80, CNOT: 72} Weighted Cost: 196.0
For each decomposition rule applicable to the controlled
MultiRZoperator instance, the inspector provides a summary of its weighted cost, wire allocations, and the "Full Expansion" (the final gate counts produced after decomposing all the way down to the target gate set).Similar to the
qp.decomposetransform, thedecomp_inspectorprovides the ability to inject new decomposition rules via the keyword argumentsfixed_decompsandalt_decomps. For more details on the inspection capabilities please consult the documentation fordecomp_inspector. -
The
list_decompsfunction now returns an object that is easier to interact with, including better legibility when printing the entire set of available decomposition rules and when printing individual ones. Additionally, the object returned supports accessing a specific rule by index or by name. (#9260)>>> collection = qp.list_decomps(qp.CRX) >>> print(collection) Available Decomposition Rules: 0: _crx_to_rx_cz 1: _crx_to_rz_ry 2: _crx_to_h_crz 3: _crx_to_ppr >>> collection[0] DecompositionRule(name=_crx_to_rx_cz) >>> collection['_crx_to_ppr'] DecompositionRule(name=_crx_to_ppr) >>> print(qp.draw(collection[0])(0.5, wires=[0, 1])) 0: ───────────╭●────────────╭●─┤ 1: ──RX(0.25)─╰Z──RX(-0.25)─╰Z─┤
-
A new
gate_setsmodule contains pre-defined gate sets that can be plugged into thegate_setargument of thedecomposetransform. These pre-defined gate sets can be easily accessed and integrated into decompositions workflows. Key gate sets include: (#8915) (#9045) (#9259) (#9417)qp.gate_sets.CLIFFORD_Twhich contains the Clifford+T gate set andqp.gate_sets.CLIFFORD_T_PLUS_RZwith an additionalRZgate. -qp.gate_sets.ROTATIONS_PLUS_CNOTwhich contains single-qubit rotations andCNOT. -qp.gate_sets.IDENTITYwhich contains theIdentityand theGlobalPhasegates.
Here is an example using the
ROTATIONS_PLUS_CNOTgate set to decompose a controlledMultiRZgate:qp.decomposition.enable_graph() @qp.decompose(gate_set=qp.gate_sets.ROTATIONS_PLUS_CNOT, num_work_wires=2) @qp.qnode(qp.device("default.qubit")) def circuit(): qp.ctrl(qp.MultiRZ(0.5, [0, 1]), control=[3, 4, 5]) return qp.expval(qp.Z(0))
>>> print(qp.specs(circuit, level="device")().resources.gate_counts) {'RZ': 54, 'RY': 14, 'GlobalPhase': 52, 'CNOT': 36, 'CRZ': 4, 'CRY': 4, 'C(GlobalPhase)': 4, 'Toffoli': 2}
Resource Estimation Templates 📏
-
New lightweight representations of the
HybridQRAM,SelectOnlyQRAM,BasisEmbedding, andBasisStatetemplates have been added for fast and efficient resource estimation. These are available in theestimatormodule as:qp.estimator.HybridQRAM,qp.estimator.SelectOnlyQRAM,qp.estimator.BasisEmbedding, andqp.estimator.BasisState. (#8828) (#8826) (#9415) (#9449)import pennylane.estimator as qre data = [[0, 1, 0], [1, 1, 1], [1, 1, 0], [0, 0, 0], [0, 1, 0], [1, 1, 1], [1, 1, 0], [0, 0, 0]] bitstring_size = 3 k = 2 num_control_wires = 3 num_work_wires = 1 + 1 + 3 * (1 << (num_control_wires - k) - 1) reg = qp.registers( { "control": num_control_wires, "target": bitstring_size, "work": num_work_wires } ) dev = qp.device("null.qubit") @qp.qnode(dev) def hybrid_qram(): # prepare an address, e.g., |010> (index 2) qp.BasisEmbedding(2, wires=reg["control"]) qp.HybridQRAM( data, control_wires=reg["control"], target_wires=reg["target"], work_wires=reg["work"], k=k ) return qp.probs(wires=reg["target"])
>>> qre.estimate(hybrid_qram)() --- Resources: --- Total wires: 12 algorithmic wires: 11 allocated wires: 1 zero state: 1 any state: 0 Total gates : 2.797E+3 'Toffoli': 142, 'T': 2.112E+3, 'CNOT': 262, 'X': 65, 'Hadamard': 216
Improvements 🛠
Decompositions 🍏
-
qp.transforms.decomposeis now conveniently accessible from the top level asqp.decompose. (#9011) -
It is now possible to locally add decomposition rules to an operator via a
qp.decomposition.local_decompscontext manager. These rules will only be available within the context. (#8955) (#8998)@qp.register_resources({qp.CNOT: 1, qp.H: 2}) def custom_decomp(wires): qp.H(wires[1]) qp.CNOT(wires) qp.H(wires[1])
>>> with qp.decomposition.local_decomps(): >>> qp.add_decomps(qp.CZ, custom_decomp) >>> print(qp.list_decomps(qp.CZ)) Available Decomposition Rules: 0: _cz_to_cps 1: _cz_to_cnot 2: _cz_to_ppr 3: my_cz_rule >>> print(qp.list_decomps(qp.CZ)) Available Decomposition Rules: 0: _cz_to_cps 1: _cz_to_cnot 2: _cz_to_ppr
-
The following gate decompositions have been optimized for resource efficiency:
-
Several new decomposition rules have been added that can be accessed with
enable_graph:-
MultiControlledXhas a new decomposition into a pair ofTemporaryANDgates and oneCNOT. It takes two control wires and at least onezeroedwork wire that has been passed explicitly. (#9291) -
TemporaryANDcan now be decomposed into the equivalent (although slightly more expensive)Toffoligate. Note that this decomposition only is valid ifTemporaryANDis used as intended - on zeroed input target qubits or zeroed output target qubits forAdjoint(TemporaryAND). (#9303) (#9424) -
A new decomposition of
Evolutionintoqp.PauliRothas been added which is compatible with the new graph-based decomposition system. Similarly, a decomposition ofqp.RZintoqp.PhaseShift, including a global phase, has been added. (#9001) (#9049)
-
-
The graph-based decompositions system, enabled via
enable_graph, now additionally supports the customadjointmethod of qutrit operators such asQutritUnitary,ControlledQutritUnitary, andTRZ. (#9056) -
The custom
adjointmethod of qutrit operators are implemented as decomposition rules compatible with the new graph-based decomposition system. (#9056) -
Now, when the new graph-based decomposition system is enabled, the
decomposetransform no longer tries to find a decomposition for an operator that meets a providedstopping_condition, even if it is not in the definedgate_set. (#9036) -
A
strictkeyword argument was added to thedecomposetransform that, when set toFalse, allows the decomposition graph to treat operators without a decomposition as part of the gate set. This prevents the decomposition graph from erroring out by keeping these operators in the circuit. (#9025) -
The inspectibility of general symbolic decomposition rules is improved. The string representation of a decomposition rule is by default its source code. Now for symbolic decomposition rules that wrap a base decomposition rule, the source code for the base decomposition rule is also displayed when printing this rule. (#9305)
-
Allowed the passing of
num_work_wires,alt_decompsandfixed_decompsto the device preprocessing functiondecompose, which are then passed through to the graph-based decomposition system. (#9094) -
The decomposition of
BasisStateis now compatible withqjitandjax.jitfor static wires and/or states. Additionally, the parametric decomposition for traced states withoutqjitwas updated to use powers ofXrather thanRX. (#9069) (#9124) (#9339) -
The decompositions of
TemporaryAND,MultiRZ, andDiagonalQubitUnitaryare now compatible with Catalyst. (#9157) -
The
sk_decompositionnow accepts"Adjoint(T)"and"Adjoint(S)"in thebasis_setas a now-preferred alternative to the old"T*"and"S*"convention for gate adjoints. (#9231) -
Some decomposition rules for
MultiControlledX(e.g.,_mcx_two_borrowed_workersand_mcx_one_borrowed_worker) became identical for instances of this operator on less than 6 wires. To prevent this, stricter conditions have been applied on these decompositions. (#9324) -
Some wire reusage was removed in
Selectthat was not consistent with the approach to work wires elsewhere in PennyLane, and that was not taken into account in the resource functions for the graph-based decomposition system (leading to decompositions not being resolved correctly). Also simplified the resource calculation of one decomposition ofSelect. (#9222) -
The decomposition of
QSVThas been updated to be consistent with or without the graph-based decomposition system enabled. (#8994) -
When the new graph-based decomposition system is enabled, the
decomposetransform no longer raises duplicate warnings about operators that cannot be decomposed. (#9025) -
A new
DecompositionWarningis now raised instead of a generalUserWarningif the decomposition graph is unable to find a solution for an operator. (#9001) -
With the new graph-based decomposition system enabled, internal use of the
decomposetransform no longer raises warnings when the graph is unable to find a decomposition for an operator in the following scenarios: (#9001)- circuit execution in the
null.qubitdevice. - compilation withqp.compile. - gradient transforms within theexpand_transformofhadamard_gradandparam_shift.
In these cases the operators will be treated as supported.
- circuit execution in the
Disentangling Transforms 🧶
-
The
disentangle_cnotanddisentangle_swapare now callable from PennyLane, not just from Catalyst'spassesmodule. These compilation passes simplify rendundantCNOTandSWAPgates. (#9133).. note::
The
disentangle_cnotanddisentangle_swapare compilation passes that are only compatible withqjitworkflows.Both compilation passes are designed to recognize patterns that include
CNOTandSWAPgates that are redundant. In the case ofdisentangle_cnot,CNOTgates are replaced when the control wire is preceded by anXgate (and the control wire is guaranteed to be in the $\vert 1 \rangle$ state). This is illustrated in the example below, where noCNOTgates remain after the compilation pass is applied.import pennylane as qp dev = qp.device("lightning.qubit", wires=2) @qp.qjit(capture=True) @qp.transforms.disentangle_cnot @qp.qnode(dev) def circuit(): qp.X(0) qp.CNOT([0, 1]) return qp.state()
>>> print(qp.specs(circuit, level=1)().resources.gate_counts) {'PauliX': 2}
Drawing ✏️
-
The
draw_graphfunction is now accessible from PennyLane, not just from Catalyst. This function allows for compact graphical inspection ofqjit-compiled circuits, preserving structured control flow. (#9020).. note::
The
draw_graphfunction is only compatible withqjitworkflows.Like with
draw,draw_mpl, andspecs,draw_graphcan be given alevelto inspect how compilation passes affect the circuit.import pennylane as qp @qp.qjit(capture=True, autograph=True) @qp.transforms.merge_rotations @qp.transforms.cancel_inverses @qp.qnode(qp.device("null.qubit", wires=3)) def circuit(x): for i in range(10): w = i % 3 qp.H(w) qp.H(w) qp.CNOT((i % 3, (i + 1) % 3)) qp.RX(0.1, wires=w) qp.RX(0.2, wires=w) if x > 1.2: qp.Toffoli((0, 1, 2)) else: qp.RZ(0.1, wires=2) qp.RZ(0.1, wires=2) return qp.expval(qp.X(0))
After all optimizations are applied (
level=2), we can still see the structure of the circuit.>>> fig, ax = qp.draw_graph(circuit, level=2)(0.1) >>> fig.savefig('path_to_file.png', dpi=300, bbox_inches="tight")
-
A new function called
labelhas been added, which allows for attaching custom labels to operator instances for circuit drawing. (#9078)@qp.qnode(qp.device("default.qubit")) def circuit(): qp.drawer.label(qp.H(0), "my-h") qp.CNOT([0, 1]) return qp.probs()
>>> print(qp.draw(circuit)()) 0: ──H("my-h")─╭●─┤ Probs 1: ────────────╰X─┤ ProbsIn addition to this change, the
markwas added to mark an operator as an input-encoding gate forcircuit_spectrum, andqnode_spectrum.
Program Capture 📥
-
With program capture enabled and using
for_loopandwhile_loop, constant closure variables with dynamic shapes can be used as such multiple times, no longer leading to leaked-tracer errors. (#9275) (#9335) -
A more informative error is raised if something that is not a measurement process is returned from a QNode when program capture is turned on. (#9072)
-
qp.condconverts non-boolean predicates to boolean immediately during capture time instead offrom_plxprdoing this, allowing for easier maintenance and organization offrom_plxpr. (#9336) -
qp.for_loopwith negative step sizes is now handled immediately during capture time instead of handling this withinfrom_plxpr, allowing for easier maintenance and organization offrom_plxpr. (#9299) -
With program capture, dynamic-shaped arrays returned from
qp.for_loopandqp.while_loopcan now be combined with other dynamic-shaped arrays returned fromqp.for_loopandqp.while_loop. (#9245) -
qp.vjpandqp.jvpare now compatible with program capture. (#8736) (#8788) (#9019) -
A
qp.capture.subroutinehas been added for jitting quantum subroutines with program capture. (#8912) -
qp.countsof mid-circuit measurement results is now compatible with program capture. (#9022) -
Program capture support for
StatePrepandBasisStatehas been enhanced to acceptstatearguments oflistortupletypes. (#9338)
Catalyst Compatibility 🤝
-
BasisEmbeddingis now captured asBasisStatefor compatibility with Catalyst and program capture. (#9183) -
The
dynamic_one_shotandsplit_to_single_termstransforms are now compatible withqp.qjit. (#9129) -
BBQRAM,HybridQRAM,SelectOnlyQRAMandQROMnow accept their classical data as a 2-dimensional array data type, which increases compatibility with Catalyst. (#8791) -
There is now one single source of truth for documentation of Catalyst passes while still maintaining accessibility from both PennyLane and Catalyst. This includes the following transforms:
to_ppr,commute_ppr,merge_ppr_ppm,ppr_to_ppm,reduce_t_depth,decompose_arbitrary_ppr,ppm_compilation, andparity_synth. (#9020) (#9395) (#9444)The source code for these passes in PennyLane has been removed as part of this change. However, all transforms listed above can still be accessed from the
pennylane.transformsmodule as before (if Catalyst is installed:pip install pennylane-catalyst). -
qp.math.givens_decompositionandqp.BasisRotationare now compatible withqjitwhencaptureis disabled. (#9155) -
qp.value_and_gradis now available to simultaneously calculate the results and gradients in Catalyst. (#8814) -
Catalyst version information has been added to
about. (#9050)
Other improvements
-
Support has been added to
assert_validfor decompositions that include mid-circuit measurements alongside better verification for the length of compared iterables. (#9378) -
A convenience function called
ceil_log2has been added, which computes the ceiling of the base-2 logarithm of its input and casts the result to anint. It is equivalent toint(np.ceil(np.log2(n))). (#8972) (#9069) -
A new function called
binary_decimalshas been added to enable easy translation of rotation angles to the binary representation of their decimals. This is important for discretization steps, for example via phase gradient decompositions. (#9117) -
The
binary_finite_reduced_row_echelonfunction was moved to a new file and now includes further linear algebraic functionalities over $\mathbb{Z}_2$. (#8982)-
binary_is_independentcomputes whether a vector is linearly independent of a basis of binary vectors over $\mathbb{Z}_2$. -binary_matrix_rankcomputes the rank over $\mathbb{Z}_2$ of a binary matrix. -binary_solve_linear_systemsolves a linear system of the form $A\cdot x=b$ with binary matrix $A$ and binary coefficient vector $b$ over $\mathbb{Z}_2$. -binary_select_basisselects linearly independent columns out of a collection of binary column vectors. The result forms a basis for the columnspace of the input. The columns that are not selected are returned as well.
-
-
A
PauliSentence.pruneandFermiSentence.prunemethod has been added, which removes terms with coefficients below a provided threshold. (#9278) -
Replaced the O(n²) incremental
@=operator chaining inqp.pauli.string_to_pauli_wordandqp.pauli.binary_to_pauliwith a singleqp.prod(*tuple_of_ops)call, collecting operators via generator expressions. These operators are now much faster for large Pauli strings. (#9271) -
Operations using
PauliSentenceare now much faster due to additional memorization inPauliWord.__hash__(#9261) -
ZX-related transforms are now compatible with
pyzxv0.10.0. (#9179) -
The
to_zxtransform is now compatible with the new graph-based decomposition system. (#8994) -
A new function called
qp.decomposition.reconstructhas been added, which reconstructs the original operator instance from(*op.data, op.wires, **op.resource_params). This enablesqjit-compatible symbolic decomposition rules that do not need to take an instance of the base operator as input. (#9188) -
The output of the
qp.while_loopcondition is now automatically converted to abool. (#9184) -
A function for setting up transform inputs, including setting default values and basic validation, can now be provided to
qp.transformviasetup_inputs. (#8732) -
The
unitary_to_rottransform now recursively decomposesQubitUnitaryoperations. This fixed a bug where two-qubit unitaries would decompose incorrectly to two single-qubit unitaries rather than their rotation decomposition. (#9144) -
Operations involving
FermiWordobjects are now significantly faster due to various performance enhancements made to the class. (#9283) -
MottonenStatePreparationnow supports parameter broadcasting in its decomposition. (#9148) -
Circuits containing
GlobalPhaseare now trainable without removing theGlobalPhase. (#8950) -
Quantum functions defining quantum operations can now be passed to the
compute_op,target_opanduncompute_oparguments ofchange_op_basis. (#9163) -
The
qp.estimator.Resourcesclass now has a better string representation in Jupyter Notebooks. (#8880) -
matrixcan now also be applied to a sequence of operators. (#8861) -
A
qp.workflow.get_compile_pipeline(qnode, level)(*args, **kwargs)function has been added to extract theCompilePipelineof a given QNode at a specific level. (#8979) (#9425) -
No unnecessary classical registers will be created now when using
qp.to_openqasmwithmeasure_all=False. (#9033) -
Both
"subroutines"and"custom_gates"are now always initialized in the QASM interpreter, resulting in more robust behaviour with PennyLane's QASM interpreter. (#9201) -
Applying
qp.ctrlonSnapshotno longer produces aControlled(Snapshot). Instead, it now returns the originalSnapshot. (#9001) -
The
default.qubitdevice now supports parameter-broadcastedGlobalPhaseoperations. (#9148) -
Global phases are now supported in
from_qasm3so that QASM including thegphaseinstruction can be interpreted. (#9247)
Labs: a place for unified and rapid prototyping of research software 🧪
-
Added a new
labs.estimator_betafor experimental development of resource estimation tools. Added various classes and functions tolabs.estimator_betato support advanced qubit management for resource estimation. Removed existing resource estimation functionality from thelabs.resource_estimationmodule. (#8996) (#8868)Allocate, allows users to allocate qubits in a resource decomposition. -Deallocate, allows users to deallocate qubits in a resource decomposition. -MarkClean, allows users to mark the state of qubits as the zero state in a circuit. -MarkQubits, allows users to mark the state of qubits in a circuit. -estimate_wires_from_circuit, estimates the number of additional qubits required from a circuit. -estimate_wires_from_resources, estimates the number of additional qubits required from aResourcesobject.
-
A new
labs.estimator_beta.estimate()function has been created, which extends the functionality ofqp.estimator.estimate()to utilize the advanced qubit management features for resource estimation. (#9139) -
Created a new
LabsQROMresource operator in labs and added multiple alternate decompositions in labs forMultiControlledXthat utilize the new qubit management features. (#9258)mcx_many_clean_aux_resource_decomp, uses multiple clean qubits to decompose. -mcx_one_clean_aux_resource_decomp, uses only one clean qubit to decompose. -mcx_one_dirty_aux_resource_decomp, uses only one dirty qubit to decompose.
-
Added alternate decompositions for
CHandHadamardoperations inlabs.estimator_betato get optimal numbers. (#9178) -
Added comparator decompositions for
RegisterEqualityandOutOfPlaceIntegerComparatorinlabs.estimator_beta(#9220) -
Added alternate controlled decompositions for
PauliRotandSelectPauliRotoperations inlabs.estimator_betato get optimal numbers. (#9186) -
Added resource templates for state preparation operators, which include
LabsMottonenStatePreparation,LabsCosineWindow, andLabsSumOfSlatersPrep. (#9202) -
Added various alternate resource decomposition functions for operators which make use of the phase gradient trick to accurately track auxiliary qubits using the new qubit management features. (#9391)
-
Added custom phase gradient decomposition rules for
RZandSelectPauliRot.make_rz_to_phase_gradient_decompforRZ-make_selectpaulirot_to_phase_gradient_decompforSelectPauliRot
Their outputs can be passed as
fixed_decompsinqp.decomposeand are necessary for efficient discretization strategies in application algorithms. (#9115) -
The integration test for computing perturbation error of a compressed double-factorized (CDF) Hamiltonian in
labs.trotter_erroris upgraded to use a more realistic molecular geometry and a more reliable reference error. (#8790)
Breaking changes 💔
-
The
num_x_wiresandnum_work_wiresarguments were added to theresource_keysandresource_paramsofSemiAdder. (#9293)With this breaking change, please note the following:
- Decomposition rules for
SemiAddernow require those arguments. - When registering a resource function (qp.register_resources) to a decomposition rule of an operator that containsSemiAdder, the resource representation ofSemiAddermust also receive these new arguments. - These changes are relevant only withenable_graph.
- Decomposition rules for
-
All
Operatorclasses are now queued by default, unless they implement a customqueuemethod that changes this behaviour. (#8131) (#9029) (#9423)This change also affects operators commonly used for operator math:
Hermitian-SProd-Sum-SparseHamiltonian-Projector-BasisStateProjector
Note, however, that all
Operatorclasses that are used to construct new operators are de-queued, so the following example does not illustrate the changed behaviour (i.e., creatingBremovesAfrom the queue in the example below):import pennylane as qp import numpy as np coeff = np.array([0.2, 0.1]) @qp.qnode(qp.device("lightning.qubit", wires=3)) def expval(x: float): qp.RX(x, 1) A = qp.Hamiltonian(coeff, [qp.Y(1), qp.X(0)]) B = A @ qp.Z(2) return qp.expval(B)
>>> print(qp.draw(expval)(0.4)) 0: ───────────┤ ╭<𝓗(0.20,0.10)> 1: ──RX(0.40)─┤ ├<𝓗(0.20,0.10)> 2: ───────────┤ ╰<𝓗(0.20,0.10)>
However, if we convert an operator
Ato numerical data, from which a new operatorBis constructed, the chain of operator dependencies is broken and de-queuing will not work as previously expected:coeff = np.array([0.2, 0.1]) @qp.qnode(qp.device("lightning.qubit", wires=3)) def expval(x: float): qp.RX(x, 1) A = qp.Hamiltonian(coeff, [qp.Y(1), qp.X(0)]) numerical_data = A.matrix() B = qp.Hermitian(numerical_data, wires=[2, 0]) return qp.expval(B)
>>> print(qp.draw(expval, show_matrices=False)(0.4)) 0: ───────────╭𝓗(0.20,0.10)─┤ ╭<𝓗(M0)> 1: ──RX(0.40)─╰𝓗(0.20,0.10)─┤ │ 2: ─────────────────────────┤ ╰<𝓗(M0)>
As we can see, the
Hamiltonianinstance assigned toAremained in the queue. In cases where such a conversion to numerical data is unavoidable, perform the conversion outside of the quantum circuit. -
Support for NumPy 1.x has been dropped following its end-of-life. NumPy 2.0 or higher is now required. (#8914) (#8954) (#9017) (#9167)
-
compute_qfunc_decompositionandhas_qfunc_decompositionhave been removed fromOperatorand all subclasses that implemented them. The graph decomposition system (enable_graph) should be used when capture is enabled. (#8922) -
The
pennylane.devices.preprocess.mid_circuit_measurementstransform has been removed. Instead, the device should determine which MCM method to use, and explicitly includedynamic_one_shotordefer_measurementsin its preprocess transforms if necessary. SeeDefaultQubit.setup_execution_configandDefaultQubit.preprocess_transformsfor an example. (#8926) -
The
custom_decompskeyword argument toqp.devicehas been removed. Instead, with the graph decomposition system (enable_graph), new decomposition rules can be defined as quantum functions with registered resources. Seepennylane.decompositionfor more details. (#8928)As an example, consider the case of running the following circuit, where we wish to convert
CNOTgates intoHadamardandCZgates.def circuit(): qp.CNOT(wires=[0, 1]) return qp.expval(qp.X(1))
Instead of defining the
CNOTdecomposition as follows withcustom_decomps,def custom_cnot(wires): return [ qp.Hadamard(wires=wires[1]), qp.CZ(wires=[wires[0], wires[1]]), qp.Hadamard(wires=wires[1]) ] dev = qp.device('default.qubit', wires=2, custom_decomps={"CNOT" : custom_cnot}) qnode = qp.QNode(circuit, dev)
the same result would now be obtained using:
@qp.decomposition.register_resources({qp.H: 2, qp.CZ: 1}) def _custom_cnot_decomposition(wires, **_): qp.Hadamard(wires=wires[1]) qp.CZ(wires=[wires[0], wires[1]]) qp.Hadamard(wires=wires[1]) qp.decomposition.add_decomps(qp.CNOT, _custom_cnot_decomposition) qp.decomposition.enable_graph() @qp.transforms.decompose(gate_set={qp.CZ, qp.H}) def circuit(): qp.CNOT(wires=[0, 1]) return qp.expval(qp.X(1)) dev = qp.device('default.qubit', wires=2) qnode = qp.QNode(circuit, dev)
>>> print(qp.draw(qnode, level="device")()) 0: ────╭●────┤ 1: ──H─╰Z──H─┤ <X>
-
The
pennylane.operation.Operator.is_hermitianproperty has been removed and replaced withpennylane.operation.Operator.is_verified_hermitian, as it better reflects the functionality of this property. Alternatively, consider using thepennylane.is_hermitianfunction instead as it provides a more reliable check for hermiticity. Please be aware that it comes with a higher computational cost. (#8919) -
Passing a function to the
gate_setargument inpennylane.decomposehas been removed. Thegate_setargument expects a static iterable of operator type and/or operator names, and the function should be passed to thestopping_conditionargument instead. (#8919) -
argnumhas been renamedargnumsinqp.grad,qp.jacobian,qp.jvp, andqp.vjpto better match Catalyst and JAX. (#8919) -
Access to the following functions and classes from the
pennylane.resourcesmodule has been removed. Instead, these functions must be imported from thepennylane.estimatormodule. (#8919)qp.estimator.estimate_shotsin favor ofqp.resources.estimate_shots-qp.estimator.estimate_errorin favor ofqp.resources.estimate_error-qp.estimator.FirstQuantizationin favor ofqp.resources.FirstQuantization-qp.estimator.DoubleFactorizationin favor ofqp.resources.DoubleFactorization
Deprecations 👋
-
The
pennylane.workflow.get_transform_programfunction has been deprecated and will be removed in v0.46. Instead, please use the improvedpennylane.workflow.get_compile_pipelineto retrieve the compilation pipeline of a QNode. (#9077) -
The
idkeyword argument to several classes has been renamed or removed entirely, and those changes will be official in v0.46. (#8951) (#9051)- The
idkeyword argument toMeasureNodeandPrepareNodehas been renamed tonode_uid. - Theidkeyword argument toMidMeasurehas been renamed tomeas_uid. - Theidkeyword argument toMeasurementProcesswill be removed. - Theidkeyword argument toOperatorhas been deprecated and will be removed.
The
idargument previously served two purposes: (1) adding custom labels to operator instances which were rendered in circuit drawings and (2) tagging encoding gates for Fourier spectrum analysis.These are now handled by dedicated functions:
.. warning::
Neither of these functions are supported in a
qjit-compiled circuit, as the original behaviour was never supported.-
Use
labelto attach a custom label to an operator instance for circuit drawing:# Legacy method (deprecated): qp.RX(0.5, wires=0, id="my-rx") # New method: qp.drawer.label(qp.RX(0.5, wires=0), "my-rx")
-
Use
markto mark an operator as an input-encoding gate forcircuit_spectrum, andqnode_spectrum:# Legacy method (deprecated): qp.RX(0.5, wires=0, id="x0") # New method: qp.fourier.mark(qp.RX(0.5, wires=0), "x0")
- The
-
Setting
_queue_category=Nonein an operator class in order to deactivate its instances being queued has been deprecated. Implement a customqueuemethod for the respective class instead. Operator classes that used to have_queue_category=Nonehave been updated to_queue_category="_ops", so that they are queued now. (#8131) -
The
BoundTransform.transformproperty has been deprecated. UseBoundTransform.tape_transforminstead. (#8985) -
expandand related functions (expand_tape,expand_tape_state_prep, andcreate_expand_trainable_multipar) have been deprecated and will be removed in v0.46. Instead, please use theqp.transforms.decomposetransform for decomposing circuits. (#8943) (#9438) -
Providing a value of
Nonetoaux_wireofqp.gradients.hadamard_gradwithmode="reversed"ormode="standard"has been deprecated and will no longer be supported in v0.46. Anaux_wirewill no longer be automatically assigned. (#8905) -
The
qp.transforms.create_expand_fnfunction has been deprecated and will be removed in v0.46. Instead, please use theqp.transforms.decomposefunction for decomposing circuits. (#8941) (#8977) (#9006) -
The
transform_programproperty of QNodes has been renamed tocompile_pipeline. The deprecated access throughtransform_programwill be removed in PennyLane v0.46. (#8906) (#8945)
Internal changes ⚙️
-
Read and write permissions have been added to all GitHub Actions workflows. (#9377)
-
The
doctestgroup has been added in thepyproject.tomlto easily maintain dependencies of the documentation tests workflow. (#9237) -
Documentation tests have been added for various modules such as the decomposition, measurements and devices modules. (#9004) (#9206) (#8653) (#9062) (#9236)
-
A new
qp.templates.Subroutineclass has been added as a layer of abstraction for quantum functions. These objects can now return classical values or mid circuit measurements, and are compatible with Program Capture Catalyst. AnyOperatorwith a single definition in terms of its implementation, a more complicated call signature, and that exists at a higher, algorithmic layer of abstraction could switch to using this class instead ofOperator/Operation. (#8929) (#9080) (#9096) (#9070) (#9097) (#9138) (#9119) (#9151) (#9172) (#9180) (#9177) (#9191) (#9176)from pennylane.templates import Subroutine @Subroutine def MyTemplate(x, y, wires): qp.RX(x, wires[0]) qp.RY(y, wires[0]) @qp.qnode(qp.device('default.qubit')) def c(): MyTemplate(0.1, 0.2, 0) return qp.state()
>>> print(qp.draw(c)()) 0: ──MyTemplate(0.10,0.20)─┤ State -
Decomposition rules have been re-written for
qjitcompatibility, so that they can be lowered to Catalyst/MLIR. Rules for the followingSymbolicOpshave been re-written: -
qp.pytrees.PyTreeStructureis now frozen and hashable.PyTreeStructure.childrenshould now be a tuple instead of a list. (#9080) -
Pass-by-pass
specsnow usesBoundTransform.tape_transformrather than the deprecatedBoundTransform.transform. Additionally, several internal comments have been updated to bringspecsin line with the newCompilePipelineclass. (#9012) -
When using
specswith Catalyst and with multiple levels, with thesplit-non-commutingMLIR pass applied, the returnedCircuitSpecsobject will include a list ofSpecsResourcesobjects for the associatedlevel. (#9120) -
Largely unused PLxPR code was recently removed in Lightning. Consequently, tests from PennyLane that are no longer relevant have been removed. (#9345)
-
Patched
jax._src.pjit._infer_params_internalfor dynamic shapes to correctly handle the concatenation of closure variables and arguments before return. (#9250) -
Docker files and workflow have been removed given that the PennyLane Docker image is no longer being built and managed in the repo. (#9273)
-
The requirements file has been removed from the
docsfolder. (#9242) -
A transform's
setup_inputsis no longer called twice when applied on aQNode. (#9189) -
The
pyzxdependency has been upper bounded topyzx<0.10to ensure compatibility. (#9175) -
The internal
PassPipelineWrapperin Catalyst was removed in favour of PennyLane's new unified transforms API. Consequently, usage ofPassPipelineWrapperhas been removed in PennyLane. (#9123) -
Updated the
diastatic-maltdependency to versionv2.15.3. (#9154) -
A workflow was created to sync the
mainbranch tomasterand later on deleted, aftermasterbranch was deleted. (#9127) (#9316) -
Removed
pytest-benchmarkfrom thepyproject.tomldevdependency group. Benchmarking is no longer internally performed in our test suite. (#7900) -
References to the
masterbranch have been changed to the new default branchmain. (#9128) -
Update nightly RC builds to not be a schedule triggered in Pennylane anymore. Instead, it will be triggered in the order Lightning —> Catalyst —> Pennylane. (#9092)
-
Duplicate transforms found in both
ftqc/catalyst_pass_aliases.pyandtransforms/decompositions/pauli_based_computation.pyhave been removed. (#9090) -
PennyLane has been updated to use a uv lockfile for package dependency tracking. Added
UV_SYSTEM_PYTHONto the repository's nightly sync workflows. Removed stable dependency folder and files. (#8755) (#9110) (#9218) -
sybilhas been added to thedevdependency group inpyproject.toml. (#9060) -
Seeded a test
tests/measurements/test_classical_shadow.py::TestClassicalShadow::test_return_distributionto fix stochastic failures by adding aseedparameter to the circuit helper functions and the test method. (#8981) -
Standardized the tolerances of several stochastic tests to use a 3-sigma rule based on theoretical variance and number of shots, reducing spurious failures. This includes
TestHamiltonianSamples::test_multi_wires,TestSampling::test_complex_hamiltonian, andTestBroadcastingPRNG::test_nonsample_measure. Bumpedrng_salttov0.45.0. (#8959) (#8958) (#8938) (#8908) (#8963) -
The test helper
get_devicehas been updated to correctly seed Lightning devices. (#8942) -
Updated internal dependencies:
autoray(to 0.8.4) andtach(to 0.32.2). (#8911) (#8962) (#9030) -
The
torchdependency has been relaxed from==2.9.0to~=2.9.0to allow for compatible patch updates. (#8911) -
Internal calls to the
decomposetransform have been updated to provide atarget_gatesargument so that they are compatible with the new graph-based decomposition system. (#8939) -
A
qp.decomposition.toggle_graph_ctxcontext manager has been added to temporarily enable or disable graph-based decompositions in a thread-safe way. The fixtures"enable_graph_decomposition","disable_graph_decomposition", and"enable_and_disable_graph_decomp"have been updated to use this method so that they are thread-safe. (#8966) -
Specialized gate kernels have been added for
RX,RY,RZ, andHadamardin thedefault.qubitdevice. These bypass generic einsum/tensordot dispatches and use direct contractions for NumPy states, with correct fallbacks for autodiff interfaces (Autograd, Torch, JAX). (#9075)
Documentation 📝
-
The README has been updated to better introduce PennyLane. (#9370)
-
The
qmlalias as inimport pennylane as qmlhas been updated toqpin our source code and documentation. (#9310) (#9317) (#9320) (#9315) (#9312) (#9314) (#9319) (#9313) (#9326) (#9333) (#9331) (#9329) (#9280) (#9327) (#9330) (#9325) (#9358) (#9281) (#9360) (#9376) (#9375) (#9384) (#9397) -
Docstrings for several optimization transforms have been improved by showing the drawing of the circuit after the transform has been applied as opposed to just the numeric simulation result, making it clearer that they can be applied to both QNodes and quantum functions, and clarifying discrepancies when using with qjit where applicable. The improved transform docstrings include
cancel_inverses,commute_controlled,merge_amplitude_embedding,merge_rotations,pattern_matching_optimization,remove_barrier,single_qubit_fusion,undo_swaps,combine_global_phases,compile,decompose,transpile, andunitary_to_rot. (#9381) (#9441) -
A new AI policy document is now applied across the PennyLaneAI organization for all AI contributions. (#9079)
-
Wide-spread changes have been made to our documentation to recommend using program capture with
qjitonly, and enabling it viaqjit(capture=True)instead of the global toggle (qp.capture.enable()). (#9059) -
TensorFlow related documentation has been updated to clarify that maintenance support has been dropped since PennyLane v0.44. (#9362)
-
The
pennylane.transformsmodule has been reorganized to allow for easier indexing through available transforms in PennyLane. (#9130) -
The docstrings for
cancel_inverses,merge_rotations, andcombine_global_phasesnow has a "Usage with qjit" section to outline what the transform does when used with Catalyst. (#9134) (#9386) -
Docstring examples in the Pauli-based computation module have been updated to reflect the QEC-to-PBC dialect rename in Catalyst. References to
qec.fabricateandqec.prepareare nowpbc.fabricateandpbc.prepare. (#9071) -
Two warning notes have been removed from the
gridsynthdocstring as we now issue a warning when users provide epsilon smaller than1e-6, and simulation of PPRs is now possible. (#9221) -
The documentation of the QASM interpreter class has been updated to include
Raiseserror sections for its methods. (#9244) -
A typo causing a rendering issue in the docstring for
QNodehas been fixed. (#8652) -
A typo in the docstring for
ControlledOpwas fixed and theControlleddocstring recommends usingctrlinstead. (#7154) -
A note has been added to the documentation of
estimateto clarify that an error will be raised if aResourceOperatoris encountered that does not have a resource decomposition defined and it is not in the providedgate_set. (#9230) -
The documentation of the
shadow_expvalmeasurement has been refined to clarify the role of theseedkeyword argument and include instructions for achieving reproducible results with this argument. (#9216) -
The definition of the
pipelineargument forcompilewas clarified in its documentation. (#9159) -
A typo in the type of a parameter in the docstring of
BasicEntanglerLayershas been fixed. (#9046) -
Infrastructure has been put in place for features that should be accessible from both PennyLane and Catalyst to have a single source of truth for documentation, which will provide a better overall experience when consulting our documentation. (#9020)
The process for Catalyst frontend features to be automatically accessible from PennyLane, while ensuring that such features' documentation is properly sourced from Catalyst and hosted on PennyLane's documentation, has been outlined in the documentation development guide under the section titled "Making Catalyst functionality callable from PennyLane". Related work in Catalyst can be found in (#2409).
-
Though the documentation for this function is now solely in the Catalyst repository, a correction was made in the output of the code example for
decompose_arbitrary_pprwhile the documentation still resided in the PennyLane repository. (#9116) -
Fixed the docstring of
FermiSentencethat incorrectly claims that it is immutable. (#9278) -
Fixed a typo in the documentation for
qre.SelectPauli. (#9373) -
Broken documentation links to external demos and tutorials have been fixed by replacing hardcoded URLs with proper
:doc:cross references. (#9356) -
The description of NumPy array slicing used to get the subspace of a density matrix is now more clear in the docs of
_phase_shift. (#9246) (#9432)
Bug fixes 🐛
-
SProd.is_verified_hermitiancan now be calculated with abstract (jax tracer) coefficients. (#9446) -
Fixed a bug where
specswould fail in multi-threaded or multi-processed settings. (#9420) -
Fixed a bug where
ParametrizedHamiltonian,HardwareHamiltonian, andParametrizedEvolutiondid not follow PennyLane's queuing convention. They have been updated to de-queue their input operators and queue themselves like other PennyLane gate objects (that derive fromOperator). (#9423) -
Fixed a bug where the Pytree structure of the following operators were inconsistent with the structure of their data:
Pow,QPE,GQSP,estimator.qpe_resources.FirstQuantization, andestimator.qpe_resources.DoubleFactorization. (#9378) -
Fixed a bug where
Reflectiondid not queue all operators of its decomposition. (#9378) -
Fixed a bug where
Hermitiandid not queue its decomposition. (#9378) -
Fixed a bug in the decomposition of
Adjoint(TemporaryAND)where control values were ignored. (#9303) -
Fixed a bug where
debug_state,debug_probs, anddebug_expvalall mutated the circuit they participated in, leading to incorrect results. (#9344) -
MultiControlledXis now compatible withqjit. An issue withjax.jittracing for controlled single-qubit unitary decompositions inpennylane.ops.op_math.decompositions.controlled_decompositionshas been fixed by ensuring consistent return types across conditional branches and casting wires to JAX-friendly types during tracing. (#9306) -
Fixed a warning of casting complex values to reals within
qp.math.givens_decomposition. (#9155) -
Fixed a bug with program capture when a transform is applied to a QNode with a dynamic number of shots and return
qp.sample. (#9342) -
Fixed wire overlap validation in
QROMandSelectto support JAX-traced wires, enablingqp.QROMto be used withqjitwhen wires are passed as dynamic arguments. (#9282) -
Fixed an issue with Catalyst and
qp.for_loopandqp.while_loop, where it was defaulting toallow_array_resizing=Trueinstead ofallow_array_resizing=False. (#9251) -
Fixed a bug where the
work_wire_typeargument ofctrlwas silently dropped when the call was delegated to the active compiler (qjit). The parameter is now forwarded to the compiler'sctrlimplementation. (#9328) -
Workflows with program capture that involve dynamic device wires will now raise a
NotImplementedErrorrather than providing incorrect results. (#9248) -
Fixed a bug in
estimatorwhere theResourcesUndefinedErrorwas being returned as a class type rather than an instance, preventing the intended diagnostic message from being displayed. (#9229) -
Fixed a bug where the data file
transforms/sign_expand/sign_expand_data.jsonwas not included in the source distribution, causing errors when usingqp.transforms.sign_expandin a production environment. (#9210) -
Fixed a bug where
qp.math.givens_decompositionmodified the input in place when usingqjit. (#9155) -
Fixed a bug where the hashable parameters of a
CompressedResourceOpin the graph-based decomposition system were sensitive to the insertion order of keyword arguments/hyperparameters. (#9137) -
Jacobian-level caching is now unconditionally enabled for the
autogradinterface, preventing redundant derivative tape executions during the backward pass. (#9081) -
Fixed a bug where
qp.transforms.transpilewould fail whenqp.GlobalPhasegates were present in a circuit. (#9041) -
Fixed a bug where
LinearCombinationdid not correctly de-queue the constituents of an operator product via the dunder method__matmul__. (#9029) -
Fixed :attr:
~.ops.Controlled.map_wiresandequalwithControlledinstances to handle thework_wire_typecorrectly withinmap_wires. Also fixedControlled.map_wiresto preservework_wires. (#9010) -
The tolerance used in determining whether the norm of the probabilities is sufficiently close to 1 in
default.qubithas been increased to1e-6. (#9014) -
A bug was fixed that prevents decompositions in the graph-based system from using nested operator products while reporting their resources accurately. (#8773)
-
decomp_int_to_powers_of_twonow adheres to standard 64-bit C integer bounds, fixing runtime warnings associated with integer overflow. (#8993) -
CompilePipelineno longer automatically pushes final transforms to the end of the pipeline as it's being built. (#8995) -
The error message was improved for when the number of inputs to a
qp.for_loop-decorated function is not one greater than the number of outputs. (#8984) -
Fixed a bug that
qp.QubitDensityMatrixwas applied indefault.mixeddevice usingqp.math.partial_traceincorrectly. This would cause wrong results as described in this issue. (#8933) -
Fixed an issue when binding a transform when the first positional arg is a
Sequence, but not aSequenceof tapes. (#8920) -
Fixed a bug with
qp.estimator.templates.QSVTwhich allows users to instantiate the class without providing wires. This is now consistent with the standard in the estimator module. (#8949) -
Fixed a bug where decomposition raises an error for
Powoperators when the exponent is batched. (#8969) -
Fixed a bug where the
DecomposeInterpretercannot be applied on a QNode with the new graph-based decomposition system enabled. (#8965) -
Fixed a bug where
qp.equalraises an error forSProdwith abstract scalar parameters andExpwith abstract coefficients. (#8965) -
Fixed various issues found with decomposition rules for
QubitUnitary,BasisRotation,StronglyEntanglingLayers. (#8965) -
The
decomposetransform no longer warns about being unable to decomposeBarrierandSnapshot. (#9001) -
When the new graph-based decomposition system is enabled,
Expno longer decomposes to nothing when the exponent is the identity. Instead, aPauliRotis always produced, which in this case decomposes to aGlobalPhase. (#9001) -
Fixed a bug where the graph-based decomposition system was unable to find a decomposition for a
ControlledQubitUnitarywith more than two target wires. (#9036) -
Fixed a discontinuity in the gradient of the single-qubit unitary decompositions. (#9036)
-
Fixed a
MemoryErrorindefault.cliffordwhen preparing aBasisStatewith a large number of wires. (#9018) -
Fixed a bug where a controlled
ChangeOpBasiswas sometimes decomposed inefficiently when the graph-based system is enabled. (#9161) -
Fixed a bug where the decomposition graph was unable to find trivial decompositions of
qp.X(0) ** 1andqp.X(0) ** 0. (#9152) -
Fixed various small bugs within
pennylane.estimator, including: (#9194)- The resource decompositions for
estimator.QubitUnitaryandestimator.OutMultiplierso that they match the results from literature - Incorrect wire mapping when convertingQuantumPhaseEstimationtoestimator.QPE- Added support for mappingBarrierandSnapShottoestimator.Identity
- The resource decompositions for
-
Fixed a bug in the
C(SemiAdder)decomposition where incorrect results were produced for a specific wire configuration. (#9270) -
Fixed a bug where the
DecompositionGraphwould underestimate the minimum number of work wires required to solve for a particular operator. This occurred when rules with lower wire requirements existed but were unreachable within the provided gate set. (#9298) -
Fixed a bug that prevented instances of controlled composite operators such as
Controlled(2.0 * qp.X(0))from being unpickled. (#9366)
Contributors ✍️
This release contains contributions from (in alphabetical order):
Guillermo Alonso, Ali Asadi, Astral Cai, Yushao Chen, Isaac De Vlugt, Diksha Dhawan, Olivia Di Matteo, Marcus Edwards, Lillian Frederiksen, Diego Guala, Sengthai Heng, Jacob Kitchen, Korbinian Kottmann, Christina Lee, Joseph Lee, Anton Naim Ibrahim, Oumarou Oumarou, Mudit Pandey, Andrija Paurevic, Alex Preciado, David D.W. Ren, Gabriela Sanchez Diaz, Omkar Sarkar, Jay Soni, Nate Stemen, David Wierichs, Fuyuan Xia, Jake Zaia, Hong-Sheng Zheng.