Releases: PennyLaneAI/pennylane
Release 0.32.0-post1
This release changes doc/requirements.txt
to upgrade jax
, jaxlib
, and pin ml-dtypes
.
Release 0.32.0
New features since last release
Encode matrices using a linear combination of unitaries ⛓️️
-
It is now possible to encode an operator
A
into a quantum circuit by decomposing it into a linear combination of unitaries using PREP (qml.StatePrep) and SELECT (qml.Select) routines. (#4431) (#4437) (#4444) (#4450) (#4506) (#4526)Consider an operator
A
composed of a linear combination of Pauli terms:>>> A = qml.PauliX(2) + 2 * qml.PauliY(2) + 3 * qml.PauliZ(2)
A decomposable block-encoding circuit can be created:
def block_encode(A, control_wires): probs = A.coeffs / np.sum(A.coeffs) state = np.pad(np.sqrt(probs, dtype=complex), (0, 1)) unitaries = A.ops qml.StatePrep(state, wires=control_wires) qml.Select(unitaries, control=control_wires) qml.adjoint(qml.StatePrep)(state, wires=control_wires)
>>> print(qml.draw(block_encode, show_matrices=False)(A, control_wires=[0, 1])) 0: ─╭|Ψ⟩─╭Select─╭|Ψ⟩†─┤ 1: ─╰|Ψ⟩─├Select─╰|Ψ⟩†─┤ 2: ──────╰Select───────┤
This circuit can be used as a building block within a larger QNode to perform algorithms such as QSVT and Hamiltonian simulation.
-
Decomposing a Hermitian matrix into a linear combination of Pauli words via
qml.pauli_decompose
is now faster and differentiable. (#4395) (#4479) (#4493)def find_coeffs(p): mat = np.array([[3, p], [p, 3]]) A = qml.pauli_decompose(mat) return A.coeffs
>>> import jax >>> from jax import numpy as np >>> jax.jacobian(find_coeffs)(np.array(2.)) Array([0., 1.], dtype=float32, weak_type=True)
Monitor PennyLane's inner workings with logging 📃
-
Python-native logging can now be enabled with
qml.logging.enable_logging()
. (#4377) (#4383)Consider the following code that is contained in
my_code.py
:import pennylane as qml qml.logging.enable_logging() # enables logging dev = qml.device("default.qubit", wires=2) @qml.qnode(dev) def f(x): qml.RX(x, wires=0) return qml.state() f(0.5)
Executing
my_code.py
with logging enabled will detail every step in PennyLane's pipeline that gets used to run your code.$ python my_code.py [1967-02-13 15:18:38,591][DEBUG][<PID 8881:MainProcess>] - pennylane.qnode.__init__()::"Creating QNode(func=<function f at 0x7faf2a6fbaf0>, device=<DefaultQubit device (wires=2, shots=None) at 0x7faf2a689b50>, interface=auto, diff_method=best, expansion_strategy=gradient, max_expansion=10, grad_on_execution=best, mode=None, cache=True, cachesize=10000, max_diff=1, gradient_kwargs={}" ...
Additional logging configuration settings can be specified by modifying the contents of the logging configuration file, which can be located by running
qml.logging.config_path()
. Follow our logging docs page for more details!
More input states for quantum chemistry calculations ⚛️
-
Input states obtained from advanced quantum chemistry calculations can be used in a circuit. (#4427) (#4433) (#4461) (#4476) (#4505)
Quantum chemistry calculations rely on an initial state that is typically selected to be the trivial Hartree-Fock state. For molecules with a complicated electronic structure, using initial states obtained from affordable post-Hartree-Fock calculations helps to improve the efficiency of the quantum simulations. These calculations can be done with external quantum chemistry libraries such as PySCF.
It is now possible to import a PySCF solver object in PennyLane and extract the corresponding wave function in the form of a state vector that can be directly used in a circuit. First, perform your classical quantum chemistry calculations and then use the qml.qchem.import_state function to import the solver object and return a state vector.
>>> from pyscf import gto, scf, ci >>> mol = gto.M(atom=[['H', (0, 0, 0)], ['H', (0,0,0.71)]], basis='sto6g') >>> myhf = scf.UHF(mol).run() >>> myci = ci.UCISD(myhf).run() >>> wf_cisd = qml.qchem.import_state(myci, tol=1e-1) >>> print(wf_cisd) [ 0. +0.j 0. +0.j 0. +0.j 0.1066467 +0.j 1. +0.j 0. +0.j 0. +0.j 0. +0.j 2. +0.j 0. +0.j 0. +0.j 0. +0.j -0.99429698+0.j 0. +0.j 0. +0.j 0. +0.j]
The state vector can be implemented in a circuit using
qml.StatePrep
.>>> dev = qml.device('default.qubit', wires=4) >>> @qml.qnode(dev) ... def circuit(): ... qml.StatePrep(wf_cisd, wires=range(4)) ... return qml.state() >>> print(circuit()) [ 0. +0.j 0. +0.j 0. +0.j 0.1066467 +0.j 1. +0.j 0. +0.j 0. +0.j 0. +0.j 2. +0.j 0. +0.j 0. +0.j 0. +0.j -0.99429698+0.j 0. +0.j 0. +0.j 0. +0.j]
The currently supported post-Hartree-Fock methods are RCISD, UCISD, RCCSD, and UCCSD which denote restricted (R) and unrestricted (U) configuration interaction (CI) and coupled cluster (CC) calculations with single and double (SD) excitations.
Reuse and reset qubits after mid-circuit measurements ♻️
-
PennyLane now allows you to define circuits that reuse a qubit after a mid-circuit measurement has taken place. Optionally, the wire can also be reset to the
$|0\rangle$ state. (#4402) (#4432)Post-measurement reset can be activated by setting
reset=True
when calling qml.measure. In this version of PennyLane, executing circuits with qubit reuse will result in the defer_measurements transform being applied. This transform replaces each reused wire with an additional qubit. However, future releases of PennyLane will explore device-level support for qubit reuse without consuming additional qubits.Qubit reuse and reset is also fully differentiable:
dev = qml.device("default.qubit", wires=4) @qml.qnode(dev) def circuit(p): qml.RX(p, wires=0) m = qml.measure(0, reset=True) qml.cond(m, qml.Hadamard)(1) qml.RX(p, wires=0) m = qml.measure(0) qml.cond(m, qml.Hadamard)(1) return qml.expval(qml.PauliZ(1))
>>> jax.grad(circuit)(0.4) Array(-0.35867804, dtype=float32, weak_type=True)
You can read more about mid-circuit measurements in the documentation, and stay tuned for more mid-circuit measurement features in the next few releases!
Improvements 🛠
A new PennyLane drawing style
-
Circuit drawings and plots can now be created following a PennyLane style. (#3950)
The
qml.draw_mpl
function accepts astyle='pennylane'
argument to create PennyLane themed circuit diagrams:def circuit(x, z): qml.QFT(wires=(0,1,2,3)) qml.Toffoli(wires=(0,1,2)) qml.CSWAP(wires=(0,2,3)) qml.RX(x, wires=0) qml.CRZ(z, wires=(3,0)) return qml.expval(qml.PauliZ(0)) qml.draw_mpl(circuit, style="pennylane")(1, 1)
PennyLane-styled plots can also be drawn by passing
"pennylane.drawer.plot"
to Matplotlib'splt.style.use
function:import matplotlib.pyplot as plt plt.style.use("pennylane.drawer.plot") for i in range(3): plt.plot(np.random.rand(10))
If the font Quicksand Bold isn't available, an available default font is used instead.
Making operators immutable and PyTrees
-
Any class inheriting from
Operator
is now automatically registered as a pytree with JAX. This unlocks the ability to jit functions ofOperator
. (#4458)>>> op = qml.adjoint(qml.RX(1.0, wires=0)) >>> jax.jit(qml.matrix)(op) Array([[0.87758255-0.j , 0. +0.47942555j], [0. +0.47942555j, 0.87758255-0.j ]], dtype=complex64, weak_type=True) >>> jax.tree_util.tree_map(lambd...
Release 0.31.1
Improvements 🛠
-
data.Dataset
now uses HDF5 instead of dill for serialization. (#4097) -
The
qchem
functionsprimitive_norm
andcontracted_norm
are modified to be compatible with higher versions of scipy. (#4321)
Bug Fixes 🐛
- Dataset URLs are now properly escaped when fetching from S3. (#4412)
Contributors ✍️
This release contains contributions from (in alphabetical order):
Utkarsh Azad, Jack Brown, Diego Guala, Soran Jahangiri, Matthew Silverman
Release 0.31.0
New features since last release
Seamlessly create and combine fermionic operators 🔬
-
Fermionic operators and arithmetic are now available. (#4191) (#4195) (#4200) (#4201) (#4209) (#4229) (#4253) (#4255) (#4262) (#4278)
There are a couple of ways to create fermionic operators with this new feature:
-
qml.FermiC
andqml.FermiA
: the fermionic creation and annihilation operators, respectively. These operators are defined by passing the index of the orbital that the fermionic operator acts on. For instance, the operatorsa⁺(0)
anda(3)
are respectively constructed as>>> qml.FermiC(0) a⁺(0) >>> qml.FermiA(3) a(3)
These operators can be composed with (
*
) and linearly combined with (+
and-
) other Fermi operators to create arbitrary fermionic Hamiltonians. Multiplying several Fermi operators together creates an operator that we call a Fermi word:>>> word = qml.FermiC(0) * qml.FermiA(0) * qml.FermiC(3) * qml.FermiA(3) >>> word a⁺(0) a(0) a⁺(3) a(3)
Fermi words can be linearly combined to create a fermionic operator that we call a Fermi sentence:
>>> sentence = 1.2 * word - 0.345 * qml.FermiC(3) * qml.FermiA(3) >>> sentence 1.2 * a⁺(0) a(0) a⁺(3) a(3) - 0.345 * a⁺(3) a(3)
-
via qml.fermi.from_string: create a fermionic operator that represents multiple creation and annihilation operators being multiplied by each other (a Fermi word).
>>> qml.fermi.from_string('0+ 1- 0+ 1-') a⁺(0) a(1) a⁺(0) a(1) >>> qml.fermi.from_string('0^ 1 0^ 1') a⁺(0) a(1) a⁺(0) a(1)
Fermi words created with
from_string
can also be linearly combined to create a Fermi sentence:>>> word1 = qml.fermi.from_string('0+ 0- 3+ 3-') >>> word2 = qml.fermi.from_string('3+ 3-') >>> sentence = 1.2 * word1 + 0.345 * word2 >>> sentence 1.2 * a⁺(0) a(0) a⁺(3) a(3) + 0.345 * a⁺(3) a(3)
Additionally, any fermionic operator, be it a single fermionic creation/annihilation operator, a Fermi word, or a Fermi sentence, can be mapped to the qubit basis by using qml.jordan_wigner:
>>> qml.jordan_wigner(sentence) ((0.4725+0j)*(Identity(wires=[0]))) + ((-0.4725+0j)*(PauliZ(wires=[3]))) + ((-0.3+0j)*(PauliZ(wires=[0]))) + ((0.3+0j)*(PauliZ(wires=[0]) @ PauliZ(wires=[3])))
Learn how to create fermionic Hamiltonians describing some simple chemical systems by checking out our fermionic operators demo!
-
Workflow-level resource estimation 🧮
-
PennyLane's Tracker now monitors the resource requirements of circuits being executed by the device. (#4045) (#4110)
Suppose we have a workflow that involves executing circuits with different qubit numbers. We can obtain the resource requirements as a function of the number of qubits by executing the workflow with the
Tracker
context:dev = qml.device("default.qubit", wires=4) @qml.qnode(dev) def circuit(n_wires): for i in range(n_wires): qml.Hadamard(i) return qml.probs(range(n_wires)) with qml.Tracker(dev) as tracker: for i in range(1, 5): circuit(i)
The resource requirements of individual circuits can then be inspected as follows:
>>> resources = tracker.history["resources"] >>> resources[0] wires: 1 gates: 1 depth: 1 shots: Shots(total=None) gate_types: {'Hadamard': 1} gate_sizes: {1: 1} >>> [r.num_wires for r in resources] [1, 2, 3, 4]
Moreover, it is possible to predict the resource requirements without evaluating circuits using the
null.qubit
device, which follows the standard execution pipeline but returns numeric zeros. Consider the following workflow that takes the gradient of a50
-qubit circuit:n_wires = 50 dev = qml.device("null.qubit", wires=n_wires) weight_shape = qml.StronglyEntanglingLayers.shape(2, n_wires) weights = np.random.random(weight_shape, requires_grad=True) @qml.qnode(dev, diff_method="parameter-shift") def circuit(weights): qml.StronglyEntanglingLayers(weights, wires=range(n_wires)) return qml.expval(qml.PauliZ(0)) with qml.Tracker(dev) as tracker: qml.grad(circuit)(weights)
The tracker can be inspected to extract resource requirements without requiring a 50-qubit circuit run:
>>> tracker.totals {'executions': 451, 'batches': 2, 'batch_len': 451} >>> tracker.history["resources"][0] wires: 50 gates: 200 depth: 77 shots: Shots(total=None) gate_types: {'Rot': 100, 'CNOT': 100} gate_sizes: {1: 100, 2: 100}
-
Custom operations can now be constructed that solely define resource requirements — an explicit decomposition or matrix representation is not needed. (#4033)
PennyLane is now able to estimate the total resource requirements of circuits that include one or more of these operations, allowing you to estimate requirements for high-level algorithms composed of abstract subroutines.
These operations can be defined by inheriting from ResourcesOperation and overriding the
resources()
method to return an appropriate Resources object:class CustomOp(qml.resource.ResourcesOperation): def resources(self): n = len(self.wires) r = qml.resource.Resources( num_wires=n, num_gates=n ** 2, depth=5, ) return r
>>> wires = [0, 1, 2] >>> c = CustomOp(wires) >>> c.resources() wires: 3 gates: 9 depth: 5 shots: Shots(total=None) gate_types: {} gate_sizes: {}
A quantum circuit that contains
CustomOp
can be created and inspected using qml.specs:dev = qml.device("default.qubit", wires=wires) @qml.qnode(dev) def circ(): qml.PauliZ(wires=0) CustomOp(wires) return qml.state()
>>> specs = qml.specs(circ)() >>> specs["resources"].depth 6
Community contributions from UnitaryHack 🤝
-
ParametrizedHamiltonian now has an improved string representation. (#4176)
>>> def f1(p, t): return p[0] * jnp.sin(p[1] * t) >>> def f2(p, t): return p * t >>> coeffs = [2., f1, f2] >>> observables = [qml.PauliX(0), qml.PauliY(0), qml.PauliZ(0)] >>> qml.dot(coeffs, observables) (2.0*(PauliX(wires=[0]))) + (f1(params_0, t)*(PauliY(wires=[0]))) + (f2(params_1, t)*(PauliZ(wires=[0])))
-
The quantum information module now supports trace distance. (#4181)
Two cases are enabled for calculating the trace distance:
-
A QNode transform via qml.qinfo.trace_distance:
dev = qml.device('default.qubit', wires=2) @qml.qnode(dev) def circuit(param): qml.RY(param, wires=0) qml.CNOT(wires=[0, 1]) return qml.state()
>>> trace_distance_circuit = qml.qinfo.trace_distance(circuit, circuit, wires0=[0], wires1=[0]) >>> x, y = np.array(0.4), np.array(0.6) >>> trace_distance_circuit((x,), (y,)) 0.047862689546603415
-
Flexible post-processing via qml.math.trace_distance:
>>> rho = np.array([[0.3, 0], [0, 0.7]]) >>> sigma = np.array([[0.5, 0], [0, 0.5]]) >>> qml.math.trace_distance(rho, sigma) 0.19999999999999998
-
-
It is now possible to prepare qutrit basis states with qml.QutritBasisState. (#4185)
wires = range(2) dev = qml.device("default.qutrit", wires=wires) @qml.qnode(dev) def qutrit_circuit(): qml.QutritBasisState([1, 1], wires=wires) qml.TAdd(wires=wires) return qml.probs(wires=1)
>>> qutrit_circuit() array([0., 0., 1.])
-
A new tra...
Release 0.30.0
New features since last release
Pulse programming on hardware ⚛️🔬
-
Support for loading time-dependent Hamiltonians that are compatible with quantum hardware has been added, making it possible to load a Hamiltonian that describes an ensemble of Rydberg atoms or a collection of transmon qubits. (#3749) (#3911) (#3930) (#3936) (#3966) (#3987) (#4021) (#4040)
Rydberg atoms are the foundational unit for neutral atom quantum computing. A Rydberg-system Hamiltonian can be constructed from a drive term
qml.pulse.rydberg_drive
— and an
interaction termqml.pulse.rydberg_interaction
:
from jax import numpy as jnp atom_coordinates = [[0, 0], [0, 4], [4, 0], [4, 4]] wires = [0, 1, 2, 3] amplitude = lambda p, t: p * jnp.sin(jnp.pi * t) phase = jnp.pi / 2 detuning = 3 * jnp.pi / 4 H_d = qml.pulse.rydberg_drive(amplitude, phase, detuning, wires) H_i = qml.pulse.rydberg_interaction(atom_coordinates, wires) H = H_d + H_i
The time-dependent Hamiltonian
H
can be used in a PennyLane pulse-level differentiable circuit:dev = qml.device("default.qubit.jax", wires=wires) @qml.qnode(dev, interface="jax") def circuit(params): qml.evolve(H)(params, t=[0, 10]) return qml.expval(qml.PauliZ(0))
>>> params = jnp.array([2.4]) >>> circuit(params) Array(0.6316659, dtype=float32) >>> import jax >>> jax.grad(circuit)(params) Array([1.3116529], dtype=float32)
The qml.pulse page contains additional details. Check out our release blog post for demonstration of how to perform the execution on actual hardware!
-
A pulse-level circuit can now be differentiated using a stochastic parameter-shift method. (#3780) (#3900) (#4000) (#4004)
The new qml.gradient.stoch_pulse_grad differentiation method unlocks stochastic-parameter-shift differentiation for pulse-level circuits. The current version of this new method is restricted to Hamiltonians composed of parametrized Pauli words, but future updates to extend to parametrized Pauli sentences can allow this method to be compatible with hardware-based systems such as an ensemble of Rydberg atoms.
This method can be activated by setting
diff_method
to qml.gradient.stoch_pulse_grad:>>> dev = qml.device("default.qubit.jax", wires=2) >>> sin = lambda p, t: jax.numpy.sin(p * t) >>> ZZ = qml.PauliZ(0) @ qml.PauliZ(1) >>> H = 0.5 * qml.PauliX(0) + qml.pulse.constant * ZZ + sin * qml.PauliX(1) >>> @qml.qnode(dev, interface="jax", diff_method=qml.gradients.stoch_pulse_grad) >>> def ansatz(params): ... qml.evolve(H)(params, (0.2, 1.)) ... return qml.expval(qml.PauliY(1)) >>> params = [jax.numpy.array(0.4), jax.numpy.array(1.3)] >>> jax.grad(ansatz)(params) [Array(0.16921353, dtype=float32, weak_type=True), Array(-0.2537478, dtype=float32, weak_type=True)]
Quantum singular value transformation 🐛➡️🦋
-
PennyLane now supports the quantum singular value transformation (QSVT), which describes how a quantum circuit can be constructed to apply a polynomial transformation to the singular values of an input matrix. (#3756) (#3757) (#3758) (#3905) (#3909) (#3926) (#4023)
Consider a matrix
A
along with a vectorangles
that describes the target polynomial transformation. Theqml.qsvt
function creates a corresponding circuit:dev = qml.device("default.qubit", wires=2) A = np.array([[0.1, 0.2], [0.3, 0.4]]) angles = np.array([0.1, 0.2, 0.3]) @qml.qnode(dev) def example_circuit(A): qml.qsvt(A, angles, wires=[0, 1]) return qml.expval(qml.PauliZ(wires=0))
This circuit is composed of
qml.BlockEncode
andqml.PCPhase
operations.>>> example_circuit(A) tensor(0.97777078, requires_grad=True) >>> print(example_circuit.qtape.expand(depth=1).draw(decimals=2)) 0: ─╭∏_ϕ(0.30)─╭BlockEncode(M0)─╭∏_ϕ(0.20)─╭BlockEncode(M0)†─╭∏_ϕ(0.10)─┤ <Z> 1: ─╰∏_ϕ(0.30)─╰BlockEncode(M0)─╰∏_ϕ(0.20)─╰BlockEncode(M0)†─╰∏_ϕ(0.10)─┤
The qml.qsvt function creates a circuit that is targeted at simulators due to the use of matrix-based operations. For advanced users, you can use the operation-based
qml.QSVT
template to perform the transformation with a custom choice of unitary and projector operations, which may be hardware compatible if a decomposition is provided.The QSVT is a complex but powerful transformation capable of generalizing important algorithms like amplitude amplification. Stay tuned for a demo in the coming few weeks to learn more!
Intuitive QNode returns ↩️
-
An updated QNode return system has been introduced. PennyLane QNodes now return exactly what you tell them to! 🎉 (#3957) (#3969) (#3946) (#3913) (#3914) (#3934)
This was an experimental feature introduced in version 0.25 of PennyLane that was enabled via
qml.enable_return()
. Now, it's the default return system. Let's see how it works.Consider the following circuit:
import pennylane as qml dev = qml.device("default.qubit", wires=1) @qml.qnode(dev) def circuit(x): qml.RX(x, wires=0) return qml.expval(qml.PauliZ(0)), qml.probs(0)
In version 0.29 and earlier of PennyLane,
circuit()
would return a single length-3 array:>>> circuit(0.5) tensor([0.87758256, 0.93879128, 0.06120872], requires_grad=True)
In versions 0.30 and above,
circuit()
returns a length-2 tuple containing the expectation value and probabilities separately:>>> circuit(0.5) (tensor(0.87758256, requires_grad=True), tensor([0.93879128, 0.06120872], requires_grad=True))
You can find more details about this change, along with help and troubleshooting tips to solve any issues. If you still have questions, comments, or concerns, we encourage you to post on the PennyLane discussion forum.
A bunch of performance tweaks 🏃💨
-
Single-qubit operations that have multi-qubit control can now be decomposed more efficiently using fewer CNOT gates. (#3851)
Three decompositions from arXiv:2302.06377 are provided and compare favourably to the already-available
qml.ops.ctrl_decomp_zyz
:wires = [0, 1, 2, 3, 4, 5] control_wires = wires[1:] @qml.qnode(qml.device('default.qubit', wires=6)) def circuit(): with qml.QueuingManager.stop_recording(): # the decomposition does not un-queue the target target = qml.RX(np.pi/2, wires=0) qml.ops.ctrl_decomp_bisect(target, (1,2,3,4,5)) return qml.state() print(qml.draw(circuit, expansion_strategy="device")())
0: ──H─╭X──U(M0)─╭X──U(M0)†─╭X──U(M0)─╭X──U(M0)†──H─┤ State 1: ────├●────────│──────────├●────────│─────────────┤ State 2: ────├●────────│──────────├●────────│─────────────┤ State 3: ────╰●────────│──────────╰●────────│─────────────┤ State 4: ──────────────├●───────────────────├●────────────┤ State 5: ──────────────╰●───────────────────╰●────────────┤ State
-
A new decomposition to
qml.SingleExcitation
has been added that halves the number of CNOTs required. (3976)>>> qml.SingleExcitation.compute_decomposition(1.23, wires=(0,1)) [Adjoint(T(wires=[0])), Hadamard(wires=[0]), S(wires=[0]),...
Release v0.29.1
Bug fixes
- Defines
qml.math.ndim
andqml.math.shape
for builtins and autograd. Accommodates changes made by Autograd v0.6.1.
Contributors
This release contains contributions from (in alphabetical order):
Christina Lee
Release 0.29.0
New features since last release
Pulse programming 🔊
-
Support for creating pulse-based circuits that describe evolution under a time-dependent Hamiltonian has now been added, as well as the ability to execute and differentiate these pulse-based circuits on simulator.
(#3586)(#3617)(#3645)(#3652)(#3665)(#3673)(#3706)(#3730)A time-dependent Hamiltonian can be created using
qml.pulse.ParametrizedHamiltonian
, which holds information representing a linear combination of operators with parametrized coefficents and can be constructed as follows:from jax import numpy as jnp f1 = lambda p, t: p * jnp.sin(t) * (t - 1) f2 = lambda p, t: p[0] * jnp.cos(p[1]* t ** 2) XX = qml.PauliX(0) @ qml.PauliX(1) YY = qml.PauliY(0) @ qml.PauliY(1) ZZ = qml.PauliZ(0) @ qml.PauliZ(1) H = 2 * ZZ + f1 * XX + f2 * YY
>>> H ParametrizedHamiltonian: terms=3 >>> p1 = jnp.array(1.2) >>> p2 = jnp.array([2.3, 3.4]) >>> H((p1, p2), t=0.5) (2*(PauliZ(wires=[0]) @ PauliZ(wires=[1]))) + ((-0.2876553231625218*(PauliX(wires=[0]) @ PauliX(wires=[1]))) + (1.517961235535459*(PauliY(wires=[0]) @ PauliY(wires=[1]))))
The time-dependent Hamiltonian can be used within a circuit with
qml.evolve
:def pulse_circuit(params, time): qml.evolve(H)(params, time) return qml.expval(qml.PauliX(0) @ qml.PauliY(1))
Pulse-based circuits can be executed and differentiated on the
default.qubit.jax
simulator using JAX as an interface:>>> dev = qml.device("default.qubit.jax", wires=2) >>> qnode = qml.QNode(pulse_circuit, dev, interface="jax") >>> params = (p1, p2) >>> qnode(params, time=0.5) Array(0.72153819, dtype=float64) >>> jax.grad(qnode)(params, time=0.5) (Array(-0.11324919, dtype=float64), Array([-0.64399616, 0.06326374], dtype=float64))
Check out the qml.pulse documentation page for more details!
Special unitary operation 🌞
-
A new operation
qml.SpecialUnitary
has been added, providing access to an arbitrary unitary gate via a parametrization in the Pauli basis.
(#3650) (#3651) (#3674)qml.SpecialUnitary
creates a unitary that exponentiates a linear combination of all possible Pauli words in lexicographical order — except for the identity operator — fornum_wires
wires, of which there are4**num_wires - 1
. As its first argument,qml.SpecialUnitary
takes a list of the4**num_wires - 1
parameters that are the coefficients of the linear combination.To see all possible Pauli words for
num_wires
wires, you can use theqml.ops.qubit.special_unitary.pauli_basis_strings
function:>>> qml.ops.qubit.special_unitary.pauli_basis_strings(1) # 4**1-1 = 3 Pauli words ['X', 'Y', 'Z'] >>> qml.ops.qubit.special_unitary.pauli_basis_strings(2) # 4**2-1 = 15 Pauli words ['IX', 'IY', 'IZ', 'XI', 'XX', 'XY', 'XZ', 'YI', 'YX', 'YY', 'YZ', 'ZI', 'ZX', 'ZY', 'ZZ']
To use
qml.SpecialUnitary
, for example, on a single qubit, we may define>>> thetas = np.array([0.2, 0.1, -0.5]) >>> U = qml.SpecialUnitary(thetas, 0) >>> qml.matrix(U) array([[ 0.8537127 -0.47537233j, 0.09507447+0.19014893j], [-0.09507447+0.19014893j, 0.8537127 +0.47537233j]])
A single non-zero entry in the parameters will create a Pauli rotation:
>>> x = 0.412 >>> theta = x * np.array([1, 0, 0]) # The first entry belongs to the Pauli word "X" >>> su = qml.SpecialUnitary(theta, wires=0) >>> rx = qml.RX(-2 * x, 0) # RX introduces a prefactor -0.5 that has to be compensated >>> qml.math.allclose(qml.matrix(su), qml.matrix(rx)) True
This operation can be differentiated with hardware-compatible methods like parameter shifts and it supports parameter broadcasting/batching, but not both at the same time. Learn more by visiting the qml.SpecialUnitary documentation.
Always differentiable 📈
-
The Hadamard test gradient transform is now available via
qml.gradients.hadamard_grad
. This transform is also available as a differentiation method withinQNode
s. (#3625) (#3736)qml.gradients.hadamard_grad
is a hardware-compatible transform that calculates the gradient of a quantum circuit using the Hadamard test. Note that the device requires an auxiliary wire to calculate the gradient.>>> dev = qml.device("default.qubit", wires=2) >>> @qml.qnode(dev) ... def circuit(params): ... qml.RX(params[0], wires=0) ... qml.RY(params[1], wires=0) ... qml.RX(params[2], wires=0) ... return qml.expval(qml.PauliZ(0)) >>> params = np.array([0.1, 0.2, 0.3], requires_grad=True) >>> qml.gradients.hadamard_grad(circuit)(params) (tensor(-0.3875172, requires_grad=True), tensor(-0.18884787, requires_grad=True), tensor(-0.38355704, requires_grad=True))
This transform can be registered directly as the quantum gradient transform to use during autodifferentiation:
>>> dev = qml.device("default.qubit", wires=2) >>> @qml.qnode(dev, interface="jax", diff_method="hadamard") ... def circuit(params): ... qml.RX(params[0], wires=0) ... qml.RY(params[1], wires=0) ... qml.RX(params[2], wires=0) ... return qml.expval(qml.PauliZ(0)) >>> params = jax.numpy.array([0.1, 0.2, 0.3]) >>> jax.jacobian(circuit)(params) Array([-0.3875172 , -0.18884787, -0.38355705], dtype=float32)
-
The gradient transform
qml.gradients.spsa_grad
is now registered as a differentiation method for QNodes.
(#3440)The SPSA gradient transform can now be used implicitly by marking a QNode as differentiable with SPSA. It can be selected via
>>> dev = qml.device("default.qubit", wires=1) >>> @qml.qnode(dev, interface="jax", diff_method="spsa", h=0.05, num_directions=20) ... def circuit(x): ... qml.RX(x, 0) ... return qml.expval(qml.PauliZ(0)) >>> jax.jacobian(circuit)(jax.numpy.array(0.5)) Array(-0.4792258, dtype=float32, weak_type=True)
The argument
num_directions
determines how many directions of simultaneous perturbation are used and therefore the number of circuit evaluations, up to a prefactor. See the SPSA gradient transform documentation for details. Note: The full SPSA optimization method is already available asqml.SPSAOptimizer
. -
The default interface is now
auto
. There is no need to specify the interface anymore; it is automatically determined by checking your QNode parameters.
(#3677)(#3752) (#3829)import jax import jax.numpy as jnp qml.enable_return() a = jnp.array(0.1) b = jnp.array(0.2) dev = qml.device("default.qubit", wires=2) @qml.qnode(dev) def circuit(a, b): qml.RY(a, wires=0) qml.RX(b, wires=1) qml.CNOT(wires=[0, 1]) return qml.expval(qml.PauliZ(0)), qml.expval(qml.PauliY(1))
>>> circuit(a, b) (Array(0.9950042, dtype=float32), Array(-0.19767681, dtype=float32)) >>> jac = jax.jacobian(circuit)(a, b) >>> jac (Array(-0.09983341, dtype=float32, weak_type=True), Array(0.01983384, dtype=float32, weak_type=True))
-
The JAX-JIT interface now supports higher-order gradient computation with the new return types system.
(#3498)Here is an example of using JAX-JIT to compute the Hessian of a circuit:
import pennylane as qml import jax from jax import numpy as jnp jax.config.update("jax_enable_x64", True) qml.enable_return() dev = qml.device("default.qubit", wires=2) @jax.jit @qml.qnode(dev, interface="jax-jit", diff_method="parameter-shift", max_diff=2) def circuit(a, b): qml.RY(a, wires=0) qml.RX(b, wires=1) return qml.expval(qml.PauliZ(0)), qml.expval(qml.PauliZ(1)) a, b = jnp.array(1.0), jnp.array(2.0)
>>> jax.hessian(circuit, argnums=[0, 1])(a, b) (((Array(-0.54030231, dtype=float64, weak_type=True), Array(0., dtype=float64, weak_type=True)), (Array(-1.76002563e-17, dtype=float64, weak_type=True), Array(0., dtype=float64, weak_type=True))), ((Array(0., dtype=float64, weak_type=True), Array(-1.00700085e-17, dtype=float64, weak_type=True)), (Array(0., dtype=float64, weak_type=True), Array(0.41614684, dtype=float64, weak_type=True))))
-
The
qchem
workflow has been modified to support both Autograd and JAX frameworks.
(#3458) (#3462) (#3495)Th...
Release 0.28.0
New features since last release
Custom measurement processes 📐
-
Custom measurements can now be facilitated with the addition of the
qml.measurements
module. (#3286) (#3343) (#3288) (#3312) (#3287) (#3292) (#3287) (#3326) (#3327) (#3388) (#3439) (#3466)Within
qml.measurements
are new subclasses that allow for the possibility to create custom measurements:SampleMeasurement
: represents a sample-based measurementStateMeasurement
: represents a state-based measurementMeasurementTransform
: represents a measurement process that requires the application of a batch transform
Creating a custom measurement involves making a class that inherits from one of the classes above. An example is given below. Here, the measurement computes the number of samples obtained of a given state:
from pennylane.measurements import SampleMeasurement
class CountState(SampleMeasurement):
def __init__(self, state: str):
self.state = state # string identifying the state, e.g. "0101"
wires = list(range(len(state)))
super().__init__(wires=wires)
def process_samples(self, samples, wire_order, shot_range, bin_size):
counts_mp = qml.counts(wires=self._wires)
counts = counts_mp.process_samples(samples, wire_order, shot_range, bin_size)
return counts.get(self.state, 0)
def __copy__(self):
return CountState(state=self.state)
We can now execute the new measurement in a QNode as follows.
dev = qml.device("default.qubit", wires=1, shots=10000)
@qml.qnode(dev)
def circuit(x):
qml.RX(x, wires=0)
return CountState(state="1")
>>> circuit(1.23)
tensor(3303., requires_grad=True)
Differentiability is also supported for this new measurement process:
>>> x = qml.numpy.array(1.23, requires_grad=True)
>>> qml.grad(circuit)(x)
4715.000000000001
For more information about these new features, see the documentation for qml.measurements
.
ZX Calculus 🧮
- QNodes can now be converted into ZX diagrams via the PyZX framework. (#3446)
ZX diagrams are the medium for which we can envision a quantum circuit as a graph in the ZX-calculus language, showing properties of quantum protocols in a visually compact and logically complete fashion.
QNodes decorated with @qml.transforms.to_zx
will return a PyZX graph that represents the computation in the ZX-calculus language.
dev = qml.device("default.qubit", wires=2)
@qml.transforms.to_zx
@qml.qnode(device=dev)
def circuit(p):
qml.RZ(p[0], wires=1),
qml.RZ(p[1], wires=1),
qml.RX(p[2], wires=0),
qml.PauliZ(wires=0),
qml.RZ(p[3], wires=1),
qml.PauliX(wires=1),
qml.CNOT(wires=[0, 1]),
qml.CNOT(wires=[1, 0]),
qml.SWAP(wires=[0, 1]),
return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1))
>>> params = [5 / 4 * np.pi, 3 / 4 * np.pi, 0.1, 0.3]
>>> circuit(params)
Graph(20 vertices, 23 edges)
Information about PyZX graphs can be found in the PyZX Graphs API.
QChem databases and basis sets ⚛️
-
The symbols and geometry of a compound from the PubChem database can now be accessed via
qchem.mol_data()
. (#3289) (#3378)>>> import pennylane as qml >>> from pennylane.qchem import mol_data >>> mol_data("BeH2") (['Be', 'H', 'H'], tensor([[ 4.79404621, 0.29290755, 0. ], [ 3.77945225, -0.29290755, 0. ], [ 5.80882913, -0.29290755, 0. ]], requires_grad=True)) >>> mol_data(223, "CID") (['N', 'H', 'H', 'H', 'H'], tensor([[ 0. , 0. , 0. ], [ 1.82264085, 0.52836742, 0.40402345], [ 0.01417295, -1.67429735, -0.98038991], [-0.98927163, -0.22714508, 1.65369933], [-0.84773114, 1.373075 , -1.07733286]], requires_grad=True))
-
Perform quantum chemistry calculations with two new basis sets:
6-311g
andCC-PVDZ
. (#3279)>>> symbols = ["H", "He"] >>> geometry = np.array([[1.0, 0.0, 0.0], [0.0, 0.0, 0.0]], requires_grad=False) >>> charge = 1 >>> basis_names = ["6-311G", "CC-PVDZ"] >>> for basis_name in basis_names: ... mol = qml.qchem.Molecule(symbols, geometry, charge=charge, basis_name=basis_name) ... print(qml.qchem.hf_energy(mol)()) [-2.84429531] [-2.84061284]
A bunch of new operators 👀
-
The controlled CZ gate and controlled Hadamard gate are now available via
qml.CCZ
andqml.CH
, respectively. (#3408)>>> ccz = qml.CCZ(wires=[0, 1, 2]) >>> qml.matrix(ccz) [[ 1 0 0 0 0 0 0 0] [ 0 1 0 0 0 0 0 0] [ 0 0 1 0 0 0 0 0] [ 0 0 0 1 0 0 0 0] [ 0 0 0 0 1 0 0 0] [ 0 0 0 0 0 1 0 0] [ 0 0 0 0 0 0 1 0] [ 0 0 0 0 0 0 0 -1]] >>> ch = qml.CH(wires=[0, 1]) >>> qml.matrix(ch) [[ 1. 0. 0. 0. ] [ 0. 1. 0. 0. ] [ 0. 0. 0.70710678 0.70710678] [ 0. 0. 0.70710678 -0.70710678]]
-
Three new parametric operators,
qml.CPhaseShift00
,qml.CPhaseShift01
, andqml.CPhaseShift10
, are now available. Each of these operators performs a phase shift akin toqml.ControlledPhaseShift
but on different positions of the state vector. (#2715)>>> dev = qml.device("default.qubit", wires=2) >>> @qml.qnode(dev) >>> def circuit(): ... qml.PauliX(wires=1) ... qml.CPhaseShift01(phi=1.23, wires=[0,1]) ... return qml.state() ... >>> circuit() tensor([0. +0.j , 0.33423773+0.9424888j, 1. +0.j , 0. +0.j ], requires_grad=True)
-
A new gate operation called
qml.FermionicSWAP
has been added. This implements the exchange of spin orbitals representing fermionic-modes while maintaining proper anti-symmetrization. (#3380)dev = qml.device('default.qubit', wires=2) @qml.qnode(dev) def circuit(phi): qml.BasisState(np.array([0, 1]), wires=[0, 1]) qml.FermionicSWAP(phi, wires=[0, 1]) return qml.state()
>>> circuit(0.1) tensor([0. +0.j , 0.99750208+0.04991671j, 0.00249792-0.04991671j, 0. +0.j ], requires_grad=True)
-
Create operators defined from a generator via
qml.ops.op_math.Evolution
. (#3375)
qml.ops.op_math.Evolution
defines the exponential of an operator qml.gradients.param_shift
to find the gradient with respect to the parameter
dev = qml.device('default.qubit', wires=2)
@qml.qnode(dev, diff_method=qml.gradients.param_shift)
def circuit(phi):
qml.ops.op_math.Evolution(qml.PauliX(0), -.5 * phi)
return qml.expval(qml.PauliZ(0))
>>> phi = np.array(1.2)
>>> circuit(phi)
tensor(0.36235775, requires_grad=True)
>>> qml.grad(circuit)(phi)
-0.9320390495504149
- The qutrit Hadamard gate,
qml.THadamard
, is now available. (#3340)
The operation accepts a subspace
keyword argument which determines which variant of the qutrit Hadamard to use.
>>> th = qml.THadamard(wires=0, subspace=[0, 1])
>>> qml.matrix(th)
array([[ 0.70710678+0.j, 0.70710678+0.j, 0. +0.j],
[ 0.70710678+0.j, -0.70710678+0.j, 0. +0.j],
[ 0. +0.j, 0. +0.j, 1. +0.j]])
New transforms, functions, and more 😯
- Calculating the purity of arbitrary quantum states is now supported. (#3290)
The purity can be calculated in an analogous fashion to, say, the Von Neumann entropy:
-
qml.math.purity
can be used as an in-line function:>>> x = [1, 0, 0, 1] / np.sqrt(2) >>> qml.math.purity(x, [0, 1]) 1.0 >>> qml.math.purity(x, [0]) 0.5 >>> x = [[1 / 2, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 1 / 2]] >>> qml.math.purity(x, [0, 1]) 0.5
-
qml.qinfo.transforms.purity
can transform a QNode returning a state to a
function that returns the purity:dev = qml.device("default.mixed", wires=2) @qml.qnode(dev) de...
Release 0.27.0
New features since last release
An all-new data module 💾
-
The
qml.data
module is now available, allowing users to download, load, and create quantum datasets. (#3156)Datasets are hosted on Xanadu Cloud and can be downloaded by using
qml.data.load()
:>>> H2_datasets = qml.data.load( ... data_name="qchem", molname="H2", basis="STO-3G", bondlength=1.1 ... ) >>> H2data = H2_datasets[0] >>> H2data <Dataset = description: qchem/H2/STO-3G/1.1, attributes: ['molecule', 'hamiltonian', ...]>
-
Datasets available to be downloaded can be listed with
qml.data.list_datasets()
. -
To download or load only specific properties of a dataset, we can specify the desired properties in
qml.data.load
with theattributes
keyword argument:>>> H2_hamiltonian = qml.data.load( ... data_name="qchem", molname="H2", basis="STO-3G", bondlength=1.1, ... attributes=["molecule", "hamiltonian"] ... )[0] >>> H2_hamiltonian.hamiltonian <Hamiltonian: terms=15, wires=[0, 1, 2, 3]>
The available attributes can be found using
qml.data.list_attributes()
: -
To select data interactively, we can use
qml.data.load_interactive()
:>>> qml.data.load_interactive() Please select a data name: 1) qspin 2) qchem Choice [1-2]: 1 Please select a sysname: ... Please select a periodicity: ... Please select a lattice: ... Please select a layout: ... Please select attributes: ... Force download files? (Default is no) [y/N]: N Folder to download to? (Default is pwd, will download to /datasets subdirectory): Please confirm your choices: dataset: qspin/Ising/open/rectangular/4x4 attributes: ['parameters', 'ground_states'] force: False dest folder: datasets Would you like to continue? (Default is yes) [Y/n]: <Dataset = description: qspin/Ising/open/rectangular/4x4, attributes: ['parameters', 'ground_states']>
-
Once a dataset is loaded, its properties can be accessed as follows:
>>> dev = qml.device("default.qubit",wires=4) >>> @qml.qnode(dev) ... def circuit(): ... qml.BasisState(H2data.hf_state, wires = [0, 1, 2, 3]) ... for op in H2data.vqe_gates: ... qml.apply(op) ... return qml.expval(H2data.hamiltonian) >>> print(circuit()) -1.0791430411076344
It's also possible to create custom datasets with
qml.data.Dataset
:>>> example_hamiltonian = qml.Hamiltonian(coeffs=[1,0.5], observables=[qml.PauliZ(wires=0),qml.PauliX(wires=1)]) >>> example_energies, _ = np.linalg.eigh(qml.matrix(example_hamiltonian)) >>> example_dataset = qml.data.Dataset( ... data_name = 'Example', hamiltonian=example_hamiltonian, energies=example_energies ... ) >>> example_dataset.data_name 'Example' >>> example_dataset.hamiltonian (0.5) [X1] + (1) [Z0] >>> example_dataset.energies array([-1.5, -0.5, 0.5, 1.5])
Custom datasets can be saved and read with the
qml.data.Dataset.write()
andqml.data.Dataset.read()
methods, respectively.>>> example_dataset.write('./path/to/dataset.dat') >>> read_dataset = qml.data.Dataset() >>> read_dataset.read('./path/to/dataset.dat') >>> read_dataset.data_name 'Example' >>> read_dataset.hamiltonian (0.5) [X1] + (1) [Z0] >>> read_dataset.energies array([-1.5, -0.5, 0.5, 1.5])
We will continue to work on adding more datasets and features for
qml.data
in future releases. -
Adaptive optimization 🏃🏋️🏊
-
Optimizing quantum circuits can now be done adaptively with
qml.AdaptiveOptimizer
. (#3192)The
qml.AdaptiveOptimizer
takes an initial circuit and a collection of operators as input and adds a selected gate to the circuit at each optimization step. The process of growing the circuit can be repeated until the circuit gradients converge to zero within a given threshold. The adaptive optimizer can be used to implement algorithms such as ADAPT-VQE as shown in the following example.Firstly, we define some preliminary variables needed for VQE:
symbols = ["H", "H", "H"] geometry = np.array([[0.01076341, 0.04449877, 0.0], [0.98729513, 1.63059094, 0.0], [1.87262415, -0.00815842, 0.0]], requires_grad=False) H, qubits = qml.qchem.molecular_hamiltonian(symbols, geometry, charge = 1)
The collection of gates to grow the circuit is built to contain all single and double excitations:
n_electrons = 2 singles, doubles = qml.qchem.excitations(n_electrons, qubits) singles_excitations = [qml.SingleExcitation(0.0, x) for x in singles] doubles_excitations = [qml.DoubleExcitation(0.0, x) for x in doubles] operator_pool = doubles_excitations + singles_excitations
Next, an initial circuit that prepares a Hartree-Fock state and returns the expectation value of the Hamiltonian is defined:
hf_state = qml.qchem.hf_state(n_electrons, qubits) dev = qml.device("default.qubit", wires=qubits) @qml.qnode(dev) def circuit(): qml.BasisState(hf_state, wires=range(qubits)) return qml.expval(H)
Finally, the optimizer is instantiated and then the circuit is created and optimized adaptively:
opt = qml.optimize.AdaptiveOptimizer() for i in range(len(operator_pool)): circuit, energy, gradient = opt.step_and_cost(circuit, operator_pool, drain_pool=True) print('Energy:', energy) print(qml.draw(circuit)()) print('Largest Gradient:', gradient) print() if gradient < 1e-3: break
Energy: -1.246549938420637 0: ─╭BasisState(M0)─╭G²(0.20)─┤ ╭<𝓗> 1: ─├BasisState(M0)─├G²(0.20)─┤ ├<𝓗> 2: ─├BasisState(M0)─│─────────┤ ├<𝓗> 3: ─├BasisState(M0)─│─────────┤ ├<𝓗> 4: ─├BasisState(M0)─├G²(0.20)─┤ ├<𝓗> 5: ─╰BasisState(M0)─╰G²(0.20)─┤ ╰<𝓗> Largest Gradient: 0.14399872776755085 Energy: -1.2613740231529604 0: ─╭BasisState(M0)─╭G²(0.20)─╭G²(0.19)─┤ ╭<𝓗> 1: ─├BasisState(M0)─├G²(0.20)─├G²(0.19)─┤ ├<𝓗> 2: ─├BasisState(M0)─│─────────├G²(0.19)─┤ ├<𝓗> 3: ─├BasisState(M0)─│─────────╰G²(0.19)─┤ ├<𝓗> 4: ─├BasisState(M0)─├G²(0.20)───────────┤ ├<𝓗> 5: ─╰BasisState(M0)─╰G²(0.20)───────────┤ ╰<𝓗> Largest Gradient: 0.1349349562423238 Energy: -1.2743971719780331 0: ─╭BasisState(M0)─╭G²(0.20)─╭G²(0.19)──────────┤ ╭<𝓗> 1: ─├BasisState(M0)─├G²(0.20)─├G²(0.19)─╭G(0.00)─┤ ├<𝓗> 2: ─├BasisState(M0)─│─────────├G²(0.19)─│────────┤ ├<𝓗> 3: ─├BasisState(M0)─│─────────╰G²(0.19)─╰G(0.00)─┤ ├<𝓗> 4: ─├BasisState(M0)─├G²(0.20)────────────────────┤ ├<𝓗> 5: ─╰BasisState(M0)─╰G²(0.20)────────────────────┤ ╰<𝓗> Largest Gradient: 0.00040841755397108586
For a detailed breakdown of its implementation, check out the Adaptive circuits for quantum chemistry demo.
Automatic interface detection 🧩
-
QNodes now accept an
auto
interface argument which automatically detects the machine learning library to use. (#3132)from pennylane import numpy as np import torch import tensorflow as tf from jax import numpy as jnp dev = qml.device("default.qubit", wires=2) @qml.qnode(dev, interface="auto") def circuit(weight): qml.RX(weight[0], wires=0) qml.RY(weight[1], wires=1) return qml.expval(qml.PauliZ(0)) interface_tensors = [[0, 1], np.array([0, 1]), torch.Tensor([0, 1]), tf.Variable([0, 1], dtype=float), jnp.array([0, 1])] for tensor in interface_tensors: res = circuit(weight=tensor) print(f"Result value: {res:.2f}; Result type: {type(res)}")
Result value: 1.00; Result type: <class 'pennylane.numpy.tensor.tensor'> Result value: 1.00; Result type: <class 'pennylane.numpy.tensor.tensor'> Result value: 1.00; Result type: <class 'torch.Tensor'> Result value: 1.00; Result type: <class 'tensorflow.python.framework.ops.EagerTensor'> Result value: 1.00; Result type: <class 'jaxlib.xla_extension.DeviceArray'>
Upgraded JAX-JIT gradient support 🏎
-
JAX-JIT support for computing the gradient of QNodes that return a single vector of probabilities or multiple expectation values is now available. (#3244) (#3261)
import jax from jax import numpy as jnp from jax.config import config config.update("jax_enable_x64", True) dev = qml.device("lightning.qubit", wires=2) @jax.jit @qml.qnode(dev, diff_method="parameter-shift", interface="jax") def circuit(x, y): qml.RY(x, wires=0) qml.RY(y, wires=1) qml.CNOT(wires=[0, 1]) return qml.expval(qml.PauliZ(0)), qml.expval(qml.PauliZ(1)) x = jnp.array(1.0) y = jnp.array(2.0)
>>> jax.jacobian(circuit, argnums=[0, 1])(x, y) (DeviceArray([-0.84147098, 0.35017549], dtype=float64, weak_type=True), DeviceArray([ 4.47445479e-18, -4.91295496e-01], dtype=float64, weak_type=True))
Note that this change depends on
jax.pure_callback
, which requiresjax>=0.3.17
.
Construct Pauli words and sentences 🔤
- We've reorganized and grouped everything in PennyLane responsible for manipulating Pauli operators into a
pauli
module. Thegrouping
module has been deprecated as a result, and logic was moved frompennylane/grouping
to `pe...
Release 0.26.0-postfix1
A minor postfix release to update the documentation styling.