Releases: PennyLaneAI/pennylane
Release 0.43.0
New features since last release
A brand new resource estimation module 📖
A new toolkit dedicated to resource estimation is now available in the pennylane.estimator module! The functionality therein is designed to rapidly and flexibly estimate the quantum resources required to execute programs written at different levels of abstraction.   This new module includes the following features:
- 
A new
pennylane.estimator.estimate.estimatefunction allows users to estimate the quantum resources required to execute a circuit or operation with respect to a given gate set and configuration. (#8203) (#8205) (#8275) (#8227) (#8279) (#8288) (#8311) (#8313) (#8360)The
pennylane.estimator.estimate.estimatefunction can be used on circuits written at different levels of detail to get high-level estimates of gate counts and additional wires fast. For workflows that are already defined in detail, like executable QNodes, thepennylane.estimator.estimate.estimatefunction works as follows:import pennylane as qml import pennylane.estimator as qre dev = qml.device("null.qubit") @qml.qnode(dev) def circ(): for w in range(2): qml.Hadamard(wires=w) qml.CNOT(wires=[0,1]) qml.RX(1.23*np.pi, wires=0) qml.RY(1.23*np.pi, wires=1) qml.QFT(wires=[0, 1, 2]) return qml.state()
>>> res = qre.estimate(circ)() >>> print(res) --- Resources: --- Total wires: 3 algorithmic wires: 3 allocated wires: 0 zero state: 0 any state: 0 Total gates : 408 'T': 396, 'CNOT': 9, 'Hadamard': 3
If exact argument values and other details to operators are unknown or not available,
pennylane.estimator.estimate.estimatecan also be used on new lightweight representations of PennyLane operations that require minimal information to obtain high-level estimates. As part of this release, many operations in PennyLane now have a corresponding lightweight version that inherits from a new class calledpennylane.estimator.resource_operator.ResourceOperator, which can be found in thepennylane.estimatormodule.For example, the lightweight representation of
QFTisqre.QFT. By simply specifying the number of wires it acts on, we can obtain resource estimates:>>> qft = qre.QFT(num_wires=3) >>> res = qre.estimate(qft) >>> print(res) --- Resources: --- Total wires: 3 algorithmic wires: 3 allocated wires: 0 zero state: 0 any state: 0 Total gates : 408 'T': 396, 'CNOT': 9, 'Hadamard': 3
One can create a circuit comprising these operations with similar syntax as defining a QNode, but with far less detail. Here is an example of a circuit with 50 (logical) algorithmic qubits, which includes a
pennylane.estimator.templates.QROMStatePreparationacting on 48 qubits. Defining this state preparation for execution would require a state vector of length$2^{48}$ (seeqml.QROMStatePreparation), but we are able to estimate the required resources with only metadata, bypassing this computational barrier. Even at this scale, the resource estimate is computed in a fraction of a second!def my_circuit(): qre.QROMStatePreparation(num_state_qubits=48) for w in range(2): qre.Hadamard(wires=w) qre.QROM(num_bitstrings=32, size_bitstring=8, restored=False) qre.CNOT(wires=[0,1]) qre.RX(wires=0) qre.RY(wires=1) qre.QFT(num_wires=30) return
>>> res = qre.estimate(my_circuit)() >>> print(res) --- Resources: --- Total wires: 129 algorithmic wires: 50 allocated wires: 79 zero state: 71 any state: 8 Total gates : 2.702E+16 'Toffoli': 1.126E+15, 'T': 5.751E+4, 'CNOT': 2.027E+16, 'X': 2.252E+15, 'Z': 32, 'S': 64, 'Hadamard': 3.378E+15
Here is a summary of the lightweight operations made available in this release. A complete list can be found in the
pennylane.estimatormodule.- 
pennylane.estimator.ops.Identity,pennylane.estimator.ops.GlobalPhase, and various non-parametric operators and single-qubit parametric operators. (#8240) (#8242) (#8302) - Various controlled single and multi qubit operators. (#8243)
 - 
pennylane.estimator.ops.Controlled, andpennylane.estimator.ops.Adjointas symbolic operators. (#8252) (#8349) - 
pennylane.estimator.ops.Pow,pennylane.estimator.ops.Prod,pennylane.estimator.ops.ChangeOpBasis, and parametric multi-qubit operators. (#8255) - Templates including 
pennylane.estimator.templates.SemiAdder,pennylane.estimator.templates.QFT,pennylane.estimator.templates.AQFT,pennylane.estimator.templates.BasisRotation,pennylane.estimator.templates.Select,pennylane.estimator.templates.QROM,pennylane.estimator.templates.SelectPauliRot,pennylane.estimator.templates.QubitUnitary,pennylane.estimator.templates.ControlledSequence,pennylane.estimator.templates.QPE,pennylane.estimator.templates.IterativeQPE,pennylane.estimator.templates.MPSPrep,pennylane.estimator.templates.QROMStatePreparation,pennylane.estimator.templates.UniformStatePrep,pennylane.estimator.templates.AliasSampling,pennylane.estimator.templates.IntegerComparator,pennylane.estimator.templates.SingleQubitComparator,pennylane.estimator.templates.TwoQubitComparator,pennylane.estimator.templates.RegisterComparator,pennylane.estimator.templates.SelectTHC,pennylane.estimator.templates.PrepTHC, andpennylane.estimator.templates.QubitizeTHC. (#8300) (#8305) (#8309) 
For defining your own customized lightweight resource operations that integrate with features in the
pennylane.estimatormodule, check out the documentation forpennylane.estimator.resource_operator.ResourceOperator. - 
 - 
Users can define customized configurations to be used during resource estimation using the new
pennylane.estimator.resource_config.ResourceConfigclass. This enables the seamless analysis of tradeoffs between resources required and quantities like individual gate precisions or different gate decompositions. (#8259)In the following example, a
pennylane.estimator.resource_config.ResourceConfigis used to modify the default precision of single qubit rotations, andTcounts are compared between different configurations.def my_circuit(): qre.RX(wires=0) qre.RY(wires=1) qre.RZ(wires=2) return my_rc = qre.ResourceConfig() res1 = qre.estimate(my_circuit, config=my_rc)() my_rc.set_single_qubit_rot_precision(1e-2) res2 = qre.estimate(my_circuit, config=my_rc)()
>>> t1 = res1.gate_counts['T'] >>> t2 = res2.gate_counts['T'] >>> print(t1, t2) 132 51
 - 
Hamiltonians are often both expensive to compute and to analyze, but the amount of information required to estimate the resources of Hamiltonian simulation can be surprisingly small in comparison. The
pennylane.estimator.compact_hamiltonian.CDFHamiltonian,pennylane.estimator.compact_hamiltonian.THCHamiltonian,pennylane.estimator.compact_hamiltonian.VibronicHamiltonian, andpennylane.estimator.compact_hamiltonian.VibrationalHamiltonianclasses were added to store the metadata of the Hamiltonian of a quantum system pertaining to resource estimation. In addition, several resource templates were added that are related to the Suzuki-Trotter method for Hamiltonian simulation, includingpennylane.estimator.templates.TrotterProduct,pennylane.estimator.templates.TrotterCDF,pennylane.estimator.templates.TrotterTHC,pennylane.estimator.templates.TrotterVibronic, andpennylane.estimator.templates.TrotterVibrational. (#8303)Here's a simple example of resource estimation for the simulation of a
pennylane.estimator.compact_hamiltonian.CDFHamiltonian, where we only need to specify two integer arguments (num_orbitalsandnum_fragments) to get resource estimates:>>> cdf_ham = qre.CDFHamiltonian(num_orbitals=4, num_fragments=4) >>> res = qre.estimate(qre.TrotterCDF(cdf_ham, num_steps=1, order=2)) >>> print(res) --- Resources: --- Total wires: 8 algorithmic wires: 8 allocated wires: 0 zero state: 0 any state: 0 Total gates : 2.238E+4 'T': 2.075E+4, 'CNOT': 448, 'Z': 336, 'S': 504, 'Hadamard': 336
 - 
In addition to the
pennylane.estimator.resource_operator.ResourceOperatorclass mentioned above, the scalability of the resource estimation functionality in this release is owed to the following new internal classes:*...
 
Release 0.42.3
Bug fixes 🐛
- Set 
autoraypackage upper-bound inpyproject.tomldue to breaking changes introduced inv0.8.0.
(#8111) 
Contributors ✍️
This release contains contributions from (in alphabetical order):
Andrija Paurevic.
Release 0.42.2
Bug fixes 🐛
- 
Fixed a recursion error when simplifying operators that are raised to integer powers. For example,
>>> class DummyOp(qml.operation.Operator): ... pass >>> (DummyOp(0) ** 2).simplify() DummyOp(0) @ DummyOp(0)
Previously, this would fail with a recursion error.
(#8061)
(#8064) 
Contributors ✍️
This release contains contributions from (in alphabetical order):
Christina Lee,
Andrija Paurevic.
Release 0.42.1
Bug fixes 🐛
- A warning is raised if PennyLane is imported and a version of JAX greater than 0.6.2 is installed.
(#7949) 
Contributors ✍️
This release contains contributions from (in alphabetical order):
Andrija Paurevic.
Release 0.42.0
New features since last release
State-of-the-art templates and decompositions 🐝
- 
A new decomposition using unary iteration has been added to
Select. This state-of-the-art decomposition reduces theT-count significantly, and uses$c-1$ auxiliary wires, where$c$ is the number of control wires of theSelectoperator. (#7623) (#7744) (#7842)Unary iteration leverages auxiliary wires to store intermediate values for reuse among the different multi-controlled operators, avoiding unnecessary recomputation and leading to more efficient decompositions to elementary gates. This decomposition uses a template called
TemporaryAND, which was also added in this release (see the next changelog entry).This decomposition rule for
Selectis available when the graph-based decomposition system is enabled viadecomposition.enable_graph:import pennylane as qml from functools import partial qml.decomposition.enable_graph()
To demonstrate the resource-efficiency of this new decomposition, let's use
transforms.decomposeto decompose an instance ofSelectusing the new unary iterator decomposition rule, and further decompose these gates into the Clifford+T gate set usingclifford_t_decompositionso that we can count the number ofTgates required:reg = qml.registers({"targ": 2, "control": 2, "work": 1}) targ, control, work = (reg[k] for k in reg.keys()) dev = qml.device('default.qubit') ops = [qml.X(targ[0]), qml.X(targ[1]), qml.Y(targ[0]), qml.SWAP(targ)] @qml.clifford_t_decomposition @partial(qml.transforms.decompose, gate_set={ qml.X, qml.CNOT, qml.TemporaryAND, "Adjoint(TemporaryAND)", "CY", "CSWAP" } ) @qml.qnode(dev) def circuit(): qml.Select(ops, control=control, work_wires=work) return qml.state()
>>> unary_specs = qml.specs(circuit)() >>> print(unary_specs['resources'].gate_types["T"]) 16 >>> print(unary_specs['resources'].gate_types["Adjoint(T)"]) 13
Go check out the Unary iterator decomposition section in the
Selectdocumentation for more information! - 
A new template called
TemporaryANDhas been added.TemporaryANDenables more efficient circuit decompositions, such as the newest decomposition of theSelecttemplate. (#7472)The
TemporaryANDoperation is a three-qubit gate equivalent to a logicalANDoperation (or a reversibleToffoli): it assumes that the target qubit is initialized in the|0〉state, whileAdjoint(TemporaryAND)assumes the target qubit will be output into the|0〉state. For more details, see Fig. 4 in arXiv:1805.03662.from functools import partial dev = qml.device("default.qubit") @partial(qml.set_shots, shots=1) @qml.qnode(dev) def circuit(): # |0000⟩ qml.X(0) # |1000⟩ qml.X(1) # |1100⟩ # The target wire is in state |0>, so we can apply TemporaryAND qml.TemporaryAND([0, 1, 2]) # |1110⟩ qml.CNOT([2, 3]) # |1111⟩ # The target wire will be in state |0> after adjoint(TemporaryAND) gate is applied # so we can apply adjoint(TemporaryAND) qml.adjoint(qml.TemporaryAND([0, 1, 2])) # |1101⟩ return qml.sample(wires=[0, 1, 2, 3])
>>> print(circuit()) [1 1 0 1] - 
A new template called
SemiAdderhas been added, which provides state-of-the-art resource-efficiency (fewerTgates) when performing addition on a quantum computer. (#7494)Based on arXiv:1709.06648,
SemiAdderperforms the plain addition of two integers in the computational basis. Here is an example of performing3 + 4 = 7with 5 additional work wires:from functools import partial x = 3 y = 4 wires = qml.registers({"x": 3, "y": 6, "work": 5}) dev = qml.device("default.qubit") @partial(qml.set_shots, shots=1) @qml.qnode(dev) def circuit(): qml.BasisEmbedding(x, wires=wires["x"]) qml.BasisEmbedding(y, wires=wires["y"]) qml.SemiAdder(wires["x"], wires["y"], wires["work"]) return qml.sample(wires=wires["y"])
>>> print(circuit()) [0 0 0 1 1 1]The result
[0 0 0 1 1 1]is the binary representation of7. - 
A new template called
SelectPauliRotis available, which applies a sequence of uniformly controlled rotations on a target qubit. This operator appears frequently in unitary decompositions and block-encoding techniques. (#7206) (#7617)As input,
SelectPauliRotrequires theanglesof rotation to be applied to the target qubit for each control register configuration, as well as thecontrol_wires, thetarget_wire, and the axis of rotation (rot_axis) for which each rotation is performed (the default is the"Z"axis).import numpy as np angles = np.array([1.0, 2.0, 3.0, 4.0]) wires = qml.registers({"control": 2, "target": 1}) dev = qml.device("default.qubit", wires=3) @qml.qnode(dev) def circuit(): qml.SelectPauliRot( angles, control_wires=wires["control"], target_wire=wires["target"], rot_axis="Z" ) return qml.state()
>>> print(qml.draw(circuit, level="device")()) 0: ─────────────────────────╭●───────────────╭●─┤ ╭State 1: ───────────╭●────────────│──╭●────────────│──┤ ├State 2: ──RZ(2.50)─╰X──RZ(-0.50)─╰X─╰X──RZ(-1.00)─╰X─┤ ╰State
 - 
The decompositions of
SingleExcitation,SingleExcitationMinusandSingleExcitationPlushave been made more efficient by reducing the number of rotations gates andCNOT,CZ, andCYgates (where applicable). This leads to lower circuit depth when decomposing these gates. (#7771) 
QSVT & QSP angle solver for large polynomials 🕸️
Effortlessly perform QSVT and QSP with polynomials of large degrees, using our new iterative angle solver.
- 
A new iterative angle solver for QSVT and QSP is available in the
poly_to_anglesfunction, designed for angle computation for polynomials with degrees larger than 1000. (6694)Simply set
angle_solver="iterative"in thepoly_to_anglesfunction to use it.import numpy as np # P(x) = x - 0.5 x^3 + 0.25 x^5 poly = np.array([0, 1.0, 0, -1/2, 0, 1/4]) qsvt_angles = qml.poly_to_angles(poly, "QSVT", angle_solver="iterative")
>>> print(qsvt_angles) [-4.72195208 1.59759022 1.12953398 1.12953403 1.59759046 -0.00956271]This functionality can also be accessed directly from
qml.qsvtwith the same keyword argument:# P(x) = -x + 0.5 x^3 + 0.5 x^5 poly = np.array([0, -1, 0, 0.5, 0, 0.5]) hamiltonian = qml.dot([0.3, 0.7], [qml.Z(1), qml.X(1) @ qml.Z(2)]) dev = qml.device("default.qubit") @qml.qnode(dev) def circuit(): qml.qsvt( hamiltonian, poly, encoding_wires=[0], block_encoding="prepselprep", angle_solver="iterative" ) return qml.state() matrix = qml.matrix(circuit, wire_order=[0, 1, 2])()
>>> print(matrix[:4, :4].real) [[-0.16253996 0. -0.37925991 0. ] [ 0. -0.16253996 0. 0.37925991] [-0.37925991 0. 0.16253996 0. ] [ 0. 0.37925991 0. 0.16253996]]
 
Qualtran integration 🔗
- 
It's now possible to convert PennyLane circuits and operators to Qualtran circuits and Bloqs with the new
qml.to_bloqfunction. This function translates PennyLane circuits (qfuncs or QNodes) and operations into equivalent Qualtran bloqs, enabling a new way to estimate the resource requirements of PennyLane quantum circuits via Qualtran's abstractions and tools. (#7197) (#7604) (#7536) (#7814)qml.to_bloqcan be used in the following ways:- 
Wrap PennyLane circuits and operations to give them Qualtran features, like obtaining bloq_counts and drawing a call_graph, but preserve PennyLane's definition of the circuit/operator. This is done by setting
map_opstoFalse, which instead wraps operations as aToBloq:>>> def circuit(): ... qml.X(0) ... qml.Y(1) ... qml.Z(2) ... >>> cbloq = qml.to_bloq(circuit, map_ops=False) >>> type(cbloq) pennylane.io.qualtran_io.ToBloq >>> cbloq.bloq_counts() {XGate(): 1, ZGate(): 1, YGate(): 1}
 - 
Use smart default mapping of PennyLane circuits and operations to Qualtran Bloqs by setting
map_ops=True(the default value):>>> PL_op = qml.X(0) >>> qualtran_op = qml.to_bloq(PL_op) >>> type(qualtran_op) qualtran.bloqs.basic_gates.x_basis.XGate
 - 
Use cus...
 
 - 
 
Release 0.41.1
Bug fixes 🐛
- A warning is raised if PennyLane is imported and a version of JAX greater than 0.4.28 is installed.
(#7369) 
Contributors ✍️
This release contains contributions from (in alphabetical order):
Pietropaolo Frisoni
Release 0.41.0
New features since last release
Resource-efficient Decompositions 🔎
A new, experimental graph-based decomposition system is now available in PennyLane, offering more resource-efficiency and versatility. (#6950) (#6951) (#6952) (#7028) (#7045) (#7058) (#7064) (#7149) (#7184) (#7223) (#7263)
PennyLane's new experimental graph decomposition system offers a resource-efficient and versatile alternative to the current system. This is done by traversing an internal graph structure that is weighted by the resources (e.g., gate counts) required to decompose down to a given set of gates.
The graph-based system is experimental and is disabled by default, but it can be enabled by adding  qml.decomposition.enable_graph() to the top of your  program. Conversely, qml.decomposition.disable_graph()  disables the new system from being active.
With qml.decomposition.enable_graph(), the following  new features are available:
- 
Operators in PennyLane can now accommodate multiple decompositions, which can be queried with the new
qml.list_decompsfunction:>>> import pennylane as qml >>> qml.decomposition.enable_graph() >>> qml.list_decomps(qml.CRX) [<pennylane.decomposition.decomposition_rule.DecompositionRule at 0x136da9de0>, <pennylane.decomposition.decomposition_rule.DecompositionRule at 0x136da9db0>, <pennylane.decomposition.decomposition_rule.DecompositionRule at 0x136da9f00>] >>> decomp_rule = qml.list_decomps(qml.CRX)[0] >>> print(decomp_rule, "\n") @register_resources(_crx_to_rx_cz_resources) def _crx_to_rx_cz(phi: TensorLike, wires: WiresLike, **__): qml.RX(phi / 2, wires=wires[1]) qml.CZ(wires=wires) qml.RX(-phi / 2, wires=wires[1]) qml.CZ(wires=wires) >>> print(qml.draw(decomp_rule)(0.5, wires=[0, 1])) 0: ───────────╭●────────────╭●─┤ 1: ──RX(0.25)─╰Z──RX(-0.25)─╰Z─┤
When an operator within a circuit needs to be decomposed (e.g., when
qml.transforms.decomposeis present), the chosen decomposition rule is that which is most resource efficient (results in the smallest gate count). - 
New decomposition rules can be globally added to operators in PennyLane with the new
qml.add_decompsfunction. Creating a valid decomposition rule requires:- Defining a quantum function that represents the decomposition.
 - Adding resource requirements (gate counts) to the above quantum function by decorating it with the  new 
qml.register_resourcesfunction, which requires a dictionary mapping operator types present in the quantum function to their number of occurrences. 
qml.decomposition.enable_graph() @qml.register_resources({qml.H: 2, qml.CZ: 1}) def my_cnot(wires): qml.H(wires=wires[1]) qml.CZ(wires=wires) qml.H(wires=wires[1]) qml.add_decomps(qml.CNOT, my_cnot)
This newly added rule for
qml.CNOTcan be verified as being available to use:>>> my_new_rule = qml.list_decomps(qml.CNOT)[-1] >>> print(my_new_rule) @qml.register_resources({qml.H: 2, qml.CZ: 1}) def my_cnot(wires): qml.H(wires=wires[1]) qml.CZ(wires=wires) qml.H(wires=wires[1])
Operators with dynamic resource requirements must be declared in a resource estimate using the new
qml.resource_repfunction. For each operator class, the set of parameters that affects the type of gates and their number of occurrences in its decompositions is given by theresource_keysattribute.>>> qml.MultiRZ.resource_keys {'num_wires'}The output of
resource_keysindicates that custom decompositions for the operator should be registered to a resource function (as opposed to a static dictionary) that accepts those exact arguments and returns a dictionary. Consider this dummy example of a fictitious decomposition rule comprising threeqml.MultiRZgates:qml.decomposition.enable_graph() def resource_fn(num_wires): return { qml.resource_rep(qml.MultiRZ, num_wires=num_wires - 1): 1, qml.resource_rep(qml.MultiRZ, num_wires=3): 2 } @qml.register_resources(resource_fn) def my_decomp(theta, wires): qml.MultiRZ(theta, wires=wires[:3]) qml.MultiRZ(theta, wires=wires[1:]) qml.MultiRZ(theta, wires=wires[:3])
More information for defining complex decomposition rules can be found in the documentation for
qml.register_resources. - 
The
qml.transforms.decomposetransform works when the new decompositions system is enabled and offers the ability to inject new decomposition rules for operators in QNodes. (#6966)With the graph-based system enabled, the
qml.transforms.decomposetransform offers the ability to inject new decomposition rules via two new keyword arguments:fixed_decomps: decomposition rules provided to this keyword argument will be used by the new system, bypassing all other decomposition rules that may exist for the relevant operators.alt_decomps: decomposition rules provided to this keyword argument are alternative decomposition rules that the new system may choose if they're the most resource efficient.
Each keyword argument must be assigned a dictionary that maps operator types to decomposition rules. Here is an example of both keyword arguments in use:
qml.decomposition.enable_graph() @qml.register_resources({qml.CNOT: 2, qml.RX: 1}) def my_isingxx(phi, wires, **__): qml.CNOT(wires=wires) qml.RX(phi, wires=[wires[0]]) qml.CNOT(wires=wires) @qml.register_resources({qml.H: 2, qml.CZ: 1}) def my_cnot(wires, **__): qml.H(wires=wires[1]) qml.CZ(wires=wires) qml.H(wires=wires[1]) @partial( qml.transforms.decompose, gate_set={"RX", "RZ", "CZ", "GlobalPhase"}, alt_decomps={qml.CNOT: my_cnot}, fixed_decomps={qml.IsingXX: my_isingxx}, ) @qml.qnode(qml.device("default.qubit")) def circuit(): qml.CNOT(wires=[0, 1]) qml.IsingXX(0.5, wires=[0, 1]) return qml.state()
>>> circuit() array([ 9.68912422e-01+2.66934210e-16j, -1.57009246e-16+3.14018492e-16j, 8.83177008e-17-2.94392336e-17j, 5.44955495e-18-2.47403959e-01j])More details about what
fixed_decompsandalt_decompsdo can be found in the usage details section in theqml.transforms.decomposedocumentation. 
Capturing and Representing Hybrid Programs 📥
Quantum operations, classical processing, structured control flow, and dynamicism can be efficiently  expressed with program capture (enabled with qml.capture.enable).
In the last several releases of PennyLane, we have been working on a new and experimental feature called
program capture, which allows for compactly expressing details about hybrid workflows while  also providing a smoother integration with just-in-time compilation frameworks like  Catalyst (via the ~.pennylane.qjit  decorator) and JAX-jit.
Internally, program capture is supported by representing hybrid programs via a new intermediate representation  (IR) called plxpr, rather than a quantum tape. The plxpr IR is an adaptation of JAX's jaxpr  IR.
There are some quirks and restrictions to be aware of, which are outlined in the :doc:/news/program_capture_sharp_bits  page. But with this release, many of the core features of PennyLane—and more!—are available with program  capture enabled by adding qml.capture.enable() to the top of your program:
- 
QNodes can now contain mid-circuit measurements (MCMs) and classical processing on MCMs with
mcm_method = "deferred"when program capture is enabled. (#6838) (#6937) (#6961)With
mcm_method = "deferred", workflows with mid-circuit measurements can be executed with program capture enabled. Additionally, program capture unlocks the ability to classically process MCM values and use MCM values as gate parameters.import jax import jax.numpy as jnp jax.config.update("jax_enable_x64", True) qml.capture.enable() @qml.qnode(qml.device("default.qubit", wires=3), mcm_method="deferred") def f(x): m0 = qml.measure(0) m1 = qml.measure(0) # classical processing on m0 and m1 a = jnp.sin(0.5 * jnp.pi * m0) phi = a - (m1 + 1) ** 4 qml.s_prod(x, qml.RX(phi, 0)) return qml.expval(qml.Z(0))
>>> f(0.1) Array(0.00540302, dtype=float64)Note that with capture enabled, automatic qubit management is not supported on devices; the number of wires given to the device must coincide with how many MCMs are present in the circuit, since deferred measurements add one auxiliary qubit per MCM.
 - 
Quantum circuits can now be differentiated on
default.qubitandlightning.qubitwithdiff_method="finite-diff","adjoint", and"backprop"when program capture is enabled. [(#6853)](https://github.com/PennyLaneAI/pennylane/pul... 
Release 0.40.0
New features since last release
Efficient state preparation methods 🦾
- 
State preparation tailored for matrix product states (MPS) is now supported with :class:
qml.MPSPrep <pennylane.MPSPrep>on thelightning.tensordevice. (#6431)Given a list of
$n$ tensors that represents an MPS,$[A^{(0)}, ..., A^{(n-1)}]$ , :class:qml.MPSPrep <pennylane.MPSPrep>lets you directly inject the MPS into a QNode as the initial state of the circuit without any need for pre-processing. The first and last tensors in the list must be rank-2, while all intermediate tensors should be rank-3.import pennylane as qml import numpy as np mps = [ np.array([[0.0, 0.107], [0.994, 0.0]]), np.array( [ [[0.0, 0.0, 0.0, -0.0], [1.0, 0.0, 0.0, -0.0]], [[0.0, 1.0, 0.0, -0.0], [0.0, 0.0, 0.0, -0.0]], ] ), np.array( [ [[-1.0, 0.0], [0.0, 0.0]], [[0.0, 0.0], [0.0, 1.0]], [[0.0, -1.0], [0.0, 0.0]], [[0.0, 0.0], [1.0, 0.0]], ] ), np.array([[-1.0, -0.0], [-0.0, -1.0]]), ] dev = qml.device("lightning.tensor", wires = [0, 1, 2, 3]) @qml.qnode(dev) def circuit(): qml.MPSPrep(mps, wires = [0,1,2]) return qml.state()
>>> print(circuit()) [ 0. +0.j 0. +0.j 0. +0.j -0.1066+0.j 0. +0.j 0. +0.j 0. +0.j 0. +0.j 0. +0.j 0. +0.j 0. +0.j 0. +0.j 0.9943+0.j 0. +0.j 0. +0.j 0. +0.j]At this time, :class:
qml.MPSPrep <pennylane.MPSPrep>is only supported on thelightning.tensordevice. - 
Custom-made state preparation for linear combinations of quantum states is now available with :class:
qml.Superposition <pennylane.Superposition>. (#6670)Given a list of
$m$ coefficients$c_i$ and basic states$|b_i\rangle$ , :class:qml.Superposition <pennylane.Superposition>prepares$|\phi\rangle = \sum_i^m c_i |b_i\rangle$ . Here is a simple example showing how to use :class:qml.Superposition <pennylane.Superposition>to prepare$\tfrac{1}{\sqrt{2}} |00\rangle + \tfrac{1}{\sqrt{2}} |10\rangle$ .coeffs = np.array([0.70710678, 0.70710678]) basis = np.array([[0, 0], [1, 0]]) @qml.qnode(qml.device('default.qubit')) def circuit(): qml.Superposition(coeffs, basis, wires=[0, 1], work_wire=[2]) return qml.state()
>>> circuit() Array([0.7071068 +0.j, 0. +0.j, 0. +0.j, 0. +0.j, 0.70710677+0.j, 0. +0.j, 0. +0.j, 0. +0.j], dtype=complex64)Note that specification of one
work_wireis required. 
Enhanced QSVT functionality 🤩
- 
New functionality to calculate and convert phase angles for QSP and QSVT has been added with :func:
qml.poly_to_angles <pennylane.poly_to_angles>and :func:qml.transform_angles <pennylane.transform_angles>. (#6483)The :func:
qml.poly_to_angles <pennylane.poly_to_angles>function calculates phase angles directly given polynomial coefficients and the routine in which the angles will be used ("QSVT","QSP", or"GQSP"):>>> poly = [0, 1.0, 0, -1/2, 0, 1/3] >>> qsvt_angles = qml.poly_to_angles(poly, "QSVT") >>> print(qsvt_angles) [-5.49778714 1.57079633 1.57079633 0.5833829 1.61095884 0.74753829]
The :func:
qml.transform_angles <pennylane.transform_angles>function can be used to convert angles from one routine to another:>>> qsp_angles = np.array([0.2, 0.3, 0.5]) >>> qsvt_angles = qml.transform_angles(qsp_angles, "QSP", "QSVT") >>> print(qsvt_angles) [-6.86858347 1.87079633 -0.28539816]
 - 
The :func:
qml.qsvt <pennylane.qsvt>function has been improved to be more user-friendly, with enhanced capabilities. (#6520) (#6693)Block encoding and phase angle computation are now handled automatically, given a matrix to encode, polynomial coefficients, and a block encoding method (
"prepselprep","qubitization","embedding", or"fable", all implemented with their corresponding operators in PennyLane).# P(x) = -x + 0.5 x^3 + 0.5 x^5 poly = np.array([0, -1, 0, 0.5, 0, 0.5]) hamiltonian = qml.dot([0.3, 0.7], [qml.Z(1), qml.X(1) @ qml.Z(2)]) dev = qml.device("default.qubit") @qml.qnode(dev) def circuit(): qml.qsvt(hamiltonian, poly, encoding_wires=[0], block_encoding="prepselprep") return qml.state() matrix = qml.matrix(circuit, wire_order=[0, 1, 2])()
>>> print(matrix[:4, :4].real) [[-0.1625 0. -0.3793 0. ] [ 0. -0.1625 0. 0.3793] [-0.3793 0. 0.1625 0. ] [ 0. 0.3793 0. 0.1625]]
The old functionality can still be accessed with :func:
qml.qsvt_legacy <pennylane.qsvt_legacy>. - 
A new :class:
qml.GQSP <pennylane.GQSP>template has been added to perform Generalized Quantum Signal Processing (GQSP). (#6565) Similar to QSVT, GQSP is an algorithm that polynomially transforms an input unitary operator, but with fewer restrictions on the chosen polynomial.You can also use :func:
qml.poly_to_angles <pennylane.poly_to_angles>to obtain angles for GQSP!# P(x) = 0.1 + 0.2j x + 0.3 x^2 poly = [0.1, 0.2j, 0.3] angles = qml.poly_to_angles(poly, "GQSP") @qml.prod # transforms the qfunc into an Operator def unitary(wires): qml.RX(0.3, wires) dev = qml.device("default.qubit") @qml.qnode(dev) def circuit(angles): qml.GQSP(unitary(wires = 1), angles, control = 0) return qml.state() matrix = qml.matrix(circuit, wire_order=[0, 1])(angles) ``` ```pycon >>> print(np.round(matrix,3)[:2, :2]) [[0.387+0.198j 0.03 -0.089j] [0.03 -0.089j 0.387+0.198j]]
 
Generalized Trotter products 🐖
- 
Trotter products that work on exponentiated operators directly instead of full system hamiltonians can now be encoded into circuits with the addition of :class:
qml.TrotterizedQfunc <pennylane.TrotterizedQfunc>and :func:qml.trotterize <pennylane.trotterize>. This allows for custom specification of the first-order expansion of the Suzuki-Trotter product formula and extrapolating it to the$n^{\text{th}}$ order. (#6627)If the first-order of the Suzuki-Trotter product formula for a given problem is known, :class:
qml.TrotterizedQfunc <pennylane.TrotterizedQfunc>and :func:qml.trotterize <pennylane.trotterize>let you implement the$n^{\text{th}}$ -order product formula while only specifying the first-order term as a quantum function.def my_custom_first_order_expansion(time, theta, phi, wires, flip): qml.RX(time * theta, wires[0]) qml.RY(time * phi, wires[1]) if flip: qml.CNOT(wires=wires[:2])
:func:
qml.trotterize <pennylane.trotterize>requires the quantum function representing the first-order product formula, the number of Trotter steps, and the desired order. It returns a function with the same call signature as the first-order product formula quantum function:@qml.qnode(qml.device("default.qubit")) def my_circuit(time, theta, phi, num_trotter_steps): qml.trotterize( first_order_expansion, n=num_trotter_steps, order=2, )(time, theta, phi, wires=['a', 'b'], flip=True) return qml.state()
Alternatively, :class:
qml.TrotterizedQfunc <pennylane.TrotterizedQfunc>can be used as follows:@qml.qnode(qml.device("default.qubit")) def my_circuit(time, theta, phi, num_trotter_steps): qml.TrotterizedQfunc( time, theta, phi, qfunc=my_custom_first_order_expansion, n=num_trotter_steps, order=2, wires=['a', 'b'], flip=True, ) return qml.state()
>>> time = 0.1 >>> theta, phi = (0.12, -3.45) >>> print(qml.draw(my_circuit, level="device")(time, theta, phi, num_trotter_steps=1)) a: ──RX(0.01)──╭●─╭●──RX(0.01)──┤ State b: ──RY(-0.17)─╰X─╰X──RY(-0.17)─┤ State
Both methods produce the same results, but offer different interfaces based on the application or overall preference.
 
Bosonic operators 🎈
A new module, :mod:qml.bose <pennylane.bose>, has been added to PennyLane that includes support  for constructing and manipulating Bosonic operators and converting between Bosonic operators and  qubit operators.
- 
Bosonic operators analogous to
qml.FermiWordandqml.FermiSentenceare now available with :class:qml.BoseWord <pennylane.BoseWord>and :class:qml.BoseSentence <pennylane.BoseSentence>. (#6518):class:
qml.BoseWord <pennylane.BoseWord>and :class:qml.BoseSentence <pennylane.BoseSentence>operate similarly to their fermionic counterparts. To create a Bose word, a dictionary is required as input, where the keys are tuples of boson indices and values are'+/-'(denoting the bosonic creation/annihilation operators). For example, the$b^{\dagger}_0 b_1$ can be constructed as follows.>>> w = qml.BoseWord({(0, 0) : '+', (1, 1) : '-'}) >>> print(w) b⁺(0) b(1)
Multiple Bose words can then be combined to form a Bose sentence:
>>> w1 = qml.BoseWord({(0, 0) : '+', (1, 1) : '-'}) >>> w2 = qml.BoseWo...
 
Release 0.39.0
New features since last release
Creating spin Hamiltonians on lattices 💞
- 
Functionality for creating custom Hamiltonians on arbitrary lattices has been added. (#6226) (#6237)
Hamiltonians beyond the available boiler-plate ones in the
qml.spinmodule can be created with the addition of three new functions:- 
qml.spin.Lattice: a new object for instantiating customized lattices via primitive translation vectors and unit cell parameters, - 
qml.spin.generate_lattice: a utility function for creating standardLatticeobjects, including'chain','square','rectangle','triangle','honeycomb','kagome','lieb','cubic','bcc','fcc', and'diamond', - 
qml.spin.spin_hamiltonian: generates a spinHamiltonianobject given aLatticeobject with custom edges/nodes. 
An example is shown below for a
$3 \times 3$ triangular lattice with open boundary conditions.lattice = qml.spin.Lattice( n_cells=[3, 3], vectors=[[1, 0], [np.cos(np.pi/3), np.sin(np.pi/3)]], positions=[[0, 0]], boundary_condition=False )
We can validate this
latticeagainstqml.spin.generate_lattice('triangle', ...)by checking thelattice_points(the$(x, y)$ coordinates of all sites in the lattice):>>> lp = lattice.lattice_points >>> triangular_lattice = qml.spin.generate_lattice('triangle', n_cells=[3, 3]) >>> np.allclose(lp, triangular_lattice.lattice_points) True
The
edgesof theLatticeobject are nearest-neighbour by default, where we can add edges by using itsadd_edgemethod.Optionally, a
Latticeobject can have interactions and fields endowed to it by specifying values for itscustom_edgesandcustom_nodeskeyword arguments. The Hamiltonian can then be extracted with theqml.spin.spin_hamiltonianfunction. An example is shown below for the transverse-field Ising model Hamiltonian on a$3 \times 3$ triangular lattice. Note that thecustom_edgesandcustom_nodeskeyword arguments only need to be defined for one unit cell repetition.edges = [ (0, 1), (0, 3), (1, 3) ] lattice = qml.spin.Lattice( n_cells=[3, 3], vectors=[[1, 0], [np.cos(np.pi/3), np.sin(np.pi/3)]], positions=[[0, 0]], boundary_condition=False, custom_edges=[[edge, ("ZZ", -1.0)] for edge in edges], custom_nodes=[[i, ("X", -0.5)] for i in range(3*3)], )
>>> tfim_ham = qml.spin.transverse_ising('triangle', [3, 3], coupling=1.0, h=0.5) >>> tfim_ham == qml.spin.spin_hamiltonian(lattice=lattice) True
 - 
 - 
More industry-standard spin Hamiltonians have been added in the
qml.spinmodule. (#6174) (#6201)Three new industry-standard spin Hamiltonians are now available with PennyLane v0.39:
- 
qml.spin.emery: the Emery model - 
qml.spin.haldane: the Haldane model - 
qml.spin.kitaev: the Kitaev model 
These additions accompany
qml.spin.heisenberg,qml.spin.transverse_ising, andqml.spin.fermi_hubbard, which were introduced in v0.38. - 
 
Calculating Polynomials 🔢
- 
Polynomial functions can now be easily encoded into quantum circuits with
qml.OutPoly. (#6320)A new template called
qml.OutPolyis available, which provides the ability to encode a polynomial function in a quantum circuit. Given a polynomial function$f(x_1, x_2, \cdots, x_N)$ ,qml.OutPolyrequires:- 
f: a standard Python function that represents$f(x_1, x_2, \cdots, x_N)$ , - 
input_registers($\vert x_1 \rangle$ ,$\vert x_2 \rangle$ , ...,$\vert x_N \rangle$ ) : a list/tuple containingWiresobjects that correspond to the embedded numeric values of$x_1, x_2, \cdots, x_N$ , - 
output_wires: theWiresfor which the numeric value of$f(x_1, x_2, \cdots, x_N)$ is stored. 
Here is an example of using
qml.OutPolyto calculate$f(x_1, x_2) = 3x_1^2 - x_1x_2$ for$f(1, 2) = 1$ .wires = qml.registers({"x1": 1, "x2": 2, "output": 2}) def f(x1, x2): return 3 * x1 ** 2 - x1 * x2 @qml.qnode(qml.device("default.qubit", shots = 1)) def circuit(): # load values of x1 and x2 qml.BasisEmbedding(1, wires=wires["x1"]) qml.BasisEmbedding(2, wires=wires["x2"]) # apply the polynomial qml.OutPoly( f, input_registers = [wires["x1"], wires["x2"]], output_wires = wires["output"]) return qml.sample(wires=wires["output"])
>>> circuit() array([0, 1])
The result,
[0, 1], is the binary representation of$1$ . By default, the result is calculated modulo$2^\text{len(output wires)}$ but can be overridden with themodkeyword argument. - 
 
Readout Noise 📠
- 
Readout errors can now be included in
qml.NoiseModelandqml.add_noisewith the newqml.noise.meas_eqfunction. (#6321)Measurement/readout errors can be specified in a similar fashion to regular gate noise in PennyLane: a newly added Boolean function called
qml.noise.meas_eqthat accepts a measurement function (e.g.,qml.expval,qml.sample, or any other function that can be returned from a QNode) that, when present in the QNode, inserts a noisy operation viaqml.noise.partial_wiresor a custom noise function. Readout noise in PennyLane also follows the insertion convention, where the specified noise is inserted before the measurement.Here is an example of adding
qml.PhaseFlipnoise to anyqml.expvalmeasurement:c0 = qml.noise.meas_eq(qml.expval) n0 = qml.noise.partial_wires(qml.PhaseFlip, 0.2)
To include this in a
qml.NoiseModel, use itsmeas_mapkeyword argument:# gate-based noise c1 = qml.noise.wires_in([0, 2]) n1 = qml.noise.partial_wires(qml.RY, -0.42) noise_model = qml.NoiseModel({c1: n1}, meas_map={c0: n0})
>>> noise_model NoiseModel({ WiresIn([0, 2]): RY(phi=-0.42) }, meas_map = { MeasEq(expval): PhaseFlip(p=0.2) })qml.noise.meas_eqcan also be combined with other Boolean functions inqml.noisevia bitwise operators for more versatility.To add this
noise_modelto a circuit, use theqml.add_noisetransform as per usual. For example,@qml.qnode(qml.device("default.mixed", wires=3)) def circuit(): qml.RX(0.1967, wires=0) for i in range(3): qml.Hadamard(i) return qml.expval(qml.X(0) @ qml.X(1))
>>> noisy_circuit = qml.add_noise(circuit, noise_model) >>> print(qml.draw(noisy_circuit)()) 0: ──RX(0.20)──RY(-0.42)────────H──RY(-0.42)──PhaseFlip(0.20)─┤ ╭<X@X> 1: ──H─────────PhaseFlip(0.20)────────────────────────────────┤ ╰<X@X> 2: ──H─────────RY(-0.42)──────────────────────────────────────┤ >>> print(circuit(), noisy_circuit()) 0.9807168489852615 0.35305806563469433
 
User-friendly decompositions 📠
- 
A new transform called
qml.transforms.decomposehas been added to better facilitate the custom decomposition of operators in PennyLane circuits. (#6334)Previous to the addition of
qml.transforms.decompose, decomposing operators in PennyLane had to be done by specifying astopping_conditioninqml.device.preprocess.decompose. Withqml.transforms.decompose, the user-interface for specifying decompositions is much simpler and more versatile.Decomposing gates in a circuit can be done a few ways:
- 
Specifying a
gate_setcomprising PennyLaneOperators to decompose into:from functools import partial dev = qml.device('default.qubit') allowed_gates = {qml.Toffoli, qml.RX, qml.RZ} @partial(qml.transforms.decompose, gate_set=allowed_gates) @qml.qnode(dev) def circuit(): qml.Hadamard(wires=[0]) qml.Toffoli(wires=[0, 1, 2]) return qml.expval(qml.Z(0))
>>> print(qml.draw(circuit)()) 0: ──RZ(1.57)──RX(1.57)──RZ(1.57)─╭●─┤ <Z> 1: ───────────────────────────────├●─┤ 2: ───────────────────────────────╰X─┤ - 
Specifying a
gate_setthat is defined by a rule (Boolean function). For example, one can specify an arbitrary gate set to decompose into, so long as the resulting gates only act on one or two qubits:@partial(qml.transforms.decompose, gate_set = lambda op: len(op.wires) <= 2) @qml.qnode(dev) def circuit(): qml.Toffoli(wires=[0, 1, 2]) return qml.expval(qml.Z(0))
>>> print(qml.draw(circuit)()) 0: ───────────╭●───────────╭●────╭●──T──╭●─┤ <Z> 1: ────╭●─────│─────╭●─────│───T─╰X──T†─╰X─┤ 2: ──H─╰X──T†─╰X──T─╰X──T†─╰X──T──H────────┤ - 
Specifying a value for
max_expansion. By default, decomposition occurs recursively until the desired gate set is reached, but this can be overridden to control the number of passes.phase = 1.0 target_wires = [0] unitary = qml.RX(phase, wires=0).matrix() n_estimation_wires = 1 estimation_wires = range(1, n_estimation_wires + 1) def qfunc(): qml.QuantumPhaseEstimation( unitary, target_wires=target_wires, est...
 
 - 
 
Release 0.38.0
New features since last release
Registers of wires 🧸
- 
A new function called
qml.registershas been added that lets you seamlessly create registers of wires. (#5957) (#6102)Using registers, it is easier to build large algorithms and circuits by applying gates and operations to predefined collections of wires. With
qml.registers, you can create registers of wires by providing a dictionary whose keys are register names and whose values are the number of wires in each register.>>> wire_reg = qml.registers({"alice": 4, "bob": 3}) >>> wire_reg {'alice': Wires([0, 1, 2, 3]), 'bob': Wires([4, 5, 6])}
The resulting data structure of
qml.registersis a dictionary with the same register names as keys, but the values areqml.wires.Wiresinstances.Nesting registers within other registers can be done by providing a nested dictionary, where the ordering of wire labels is based on the order of appearance and nestedness.
>>> wire_reg = qml.registers({"alice": {"alice1": 1, "alice2": 2}, "bob": {"bob1": 2, "bob2": 1}}) >>> wire_reg {'alice1': Wires([0]), 'alice2': Wires([1, 2]), 'alice': Wires([0, 1, 2]), 'bob1': Wires([3, 4]), 'bob2': Wires([5]), 'bob': Wires([3, 4, 5])}
Since the values of the dictionary are
Wiresinstances, their use within quantum circuits is very similar to that of alistof integers.dev = qml.device("default.qubit") @qml.qnode(dev) def circuit(): for w in wire_reg["alice"]: qml.Hadamard(w) for w in wire_reg["bob1"]: qml.RX(0.1967, wires=w) qml.CNOT(wires=[wire_reg["alice1"][0], wire_reg["bob2"][0]]) return [qml.expval(qml.Y(w)) for w in wire_reg["bob1"]] print(qml.draw(circuit)())
0: ──H────────╭●─┤ 1: ──H────────│──┤ 2: ──H────────│──┤ 3: ──RX(0.20)─│──┤ <Y> 4: ──RX(0.20)─│──┤ <Y> 5: ───────────╰X─┤
In tandem with
qml.registers, we've also made the following improvements toqml.wires.Wires:- 
Wiresinstances now have a more copy-paste friendly representation when printed. (#5958)>>> from pennylane.wires import Wires >>> w = Wires([1, 2, 3]) >>> w Wires([1, 2, 3])
 - 
Python set-based combinations are now supported by
Wires. (#5983)This new feature unlocks the ability to combine
Wiresinstances in the following ways:- 
intersection with
&orintersection():>>> wires1 = Wires([1, 2, 3]) >>> wires2 = Wires([2, 3, 4]) >>> wires1.intersection(wires2) # or wires1 & wires2 Wires([2, 3])
 - 
symmetric difference with
^orsymmetric_difference():>>> wires1.symmetric_difference(wires2) # or wires1 ^ wires2 Wires([1, 4])
 - 
union with
|orunion():>>> wires1.union(wires2) # or wires1 | wires2 Wires([1, 2, 3, 4])
 - 
difference with
-ordifference():>>> wires1.difference(wires2) # or wires1 - wires2 Wires([1])
 
 - 
 
 - 
 
Quantum arithmetic operations 🧮
- 
Several new operator templates have been added to PennyLane that let you perform quantum arithmetic operations. (#6109) (#6112) (#6121)
- 
qml.Adderperforms in-place modular addition:$\text{Adder}(k, m)\vert x \rangle = \vert x + k ; \text{mod} ; m\rangle$ . - 
qml.PhaseAdderis similar toqml.Adder, but it performs in-place modular addition in the Fourier basis. - 
qml.Multiplierperforms in-place multiplication:$\text{Multiplier}(k, m)\vert x \rangle = \vert x \times k ; \text{mod} ; m \rangle$ . - 
qml.OutAdderperforms out-place modular addition:$\text{OutAdder}(m)\vert x \rangle \vert y \rangle \vert b \rangle = \vert x \rangle \vert y \rangle \vert b + x + y ; \text{mod} ; m \rangle$ . - 
qml.OutMultiplierperforms out-place modular multiplication:$\text{OutMultiplier}(m)\vert x \rangle \vert y \rangle \vert b \rangle = \vert x \rangle \vert y \rangle \vert b + x \times y ; \text{mod} ; m \rangle$ . - 
qml.ModExpperforms modular exponentiation:$\text{ModExp}(base, m) \vert x \rangle \vert k \rangle = \vert x \rangle \vert k \times base^x ; \text{mod} ; m \rangle$ . 
Here is a comprehensive example that performs the following calculation:
(2 + 1) * 3 mod 7 = 2(or010in binary).dev = qml.device("default.qubit", shots=1) wire_reg = qml.registers({ "x_wires": 2, # |x>: stores the result of 2 + 1 = 3 "y_wires": 2, # |y>: multiples x by 3 "output_wires": 3, # stores the result of (2 + 1) * 3 m 7 = 2 "work_wires": 2 # for qml.OutMultiplier }) @qml.qnode(dev) def circuit(): # In-place addition qml.BasisEmbedding(2, wires=wire_reg["x_wires"]) qml.Adder(1, x_wires=wire_reg["x_wires"]) # add 1 to wires [0, 1] # Out-place multiplication qml.BasisEmbedding(3, wires=wire_reg["y_wires"]) qml.OutMultiplier( wire_reg["x_wires"], wire_reg["y_wires"], wire_reg["output_wires"], work_wires=wire_reg["work_wires"], mod=7 ) return qml.sample(wires=wire_reg["output_wires"])
>>> circuit() array([0, 1, 0]) - 
 
Converting noise models from Qiskit ♻️
- 
Convert Qiskit noise models into a PennyLane
NoiseModelwithqml.from_qiskit_noise. (#5996)In the last few releases, we've added substantial improvements and new features to the Pennylane-Qiskit plugin. With this release, a new
qml.from_qiskit_noisefunction allows you to convert a Qiskit noise model into a PennyLaneNoiseModel. Here is a simple example with two quantum errors that add two different depolarizing errors based on the presence of different gates in the circuit:import pennylane as qml import qiskit_aer.noise as noise error_1 = noise.depolarizing_error(0.001, 1) # 1-qubit noise error_2 = noise.depolarizing_error(0.01, 2) # 2-qubit noise noise_model = noise.NoiseModel() noise_model.add_all_qubit_quantum_error(error_1, ['rz', 'ry']) noise_model.add_all_qubit_quantum_error(error_2, ['cx'])
>>> qml.from_qiskit_noise(noise_model) NoiseModel({ OpIn(['RZ', 'RY']): QubitChannel(num_kraus=4, num_wires=1) OpIn(['CNOT']): QubitChannel(num_kraus=16, num_wires=2) })Under the hood, PennyLane converts each quantum error in the Qiskit noise model into an equivalent
qml.QubitChanneloperator with the same canonical Kraus representation. Currently, noise models in PennyLane do not support readout errors. As such, those will be skipped during conversion if they are present in the Qiskit noise model.Make sure to
pip install pennylane-qiskitto access this new feature! 
Substantial upgrades to mid-circuit measurements using tree-traversal 🌳
- 
The
"tree-traversal"algorithm for mid-circuit measurements (MCMs) ondefault.qubithas been internally redesigned for better performance. (#5868)In the last release (v0.37), we introduced the tree-traversal MCM method, which was implemented in a recursive way for simplicity. However, this had the unintended consequence of very deep stack calls for circuits with many MCMs, resulting in stack overflows in some cases. With this release, we've refactored the implementation of the tree-traversal method into an iterative approach, which solves those inefficiencies when many MCMs are present in a circuit.
 - 
The
tree-traversalalgorithm is now compatible with analytic-mode execution (shots=None). (#5868)dev = qml.device("default.qubit") n_qubits = 5 @qml.qnode(dev, mcm_method="tree-traversal") def circuit(): for w in range(n_qubits): qml.Hadamard(w) for w in range(n_qubits - 1): qml.CNOT(wires=[w, w+1]) for w in range(n_qubits): m = qml.measure(w) qml.cond(m == 1, qml.RX)(0.1967 * (w + 1), w) return [qml.expval(qml.Z(w)) for w in range(n_qubits)]
>>> circuit() [tensor(0.00964158, requires_grad=True), tensor(0.03819446, requires_grad=True), tensor(0.08455748, requires_grad=True), tensor(0.14694258, requires_grad=True), tensor(0.2229438, requires_grad=True)]
 
Improvements 🛠
Creating spin Hamiltonians
- 
Three new functions are now available for creating commonly-used spin Hamiltonians in PennyLane: (#6106) (#6128)
qml.spin.transverse_isingcreates the transverse-field Ising model Hamiltonian.qml.spin.heisenbergcreates the Heisenberg model Hamiltonian.qml.spin.fermi_hubbardcreates the [Fermi...