From 27c117fa743d9c1a9f647a13a72467d81b6f506b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 8 Jul 2024 12:08:30 -0400 Subject: [PATCH] Daily rc sync to master (#5962) Automatic sync from the release candidate to master during a feature freeze. --------- Co-authored-by: Jay Soni Co-authored-by: Astral Cai Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Yushao Chen (Jerry) Co-authored-by: Christina Lee Co-authored-by: Mudit Pandey Co-authored-by: Thomas R. Bromley <49409390+trbromley@users.noreply.github.com> Co-authored-by: soranjh <40344468+soranjh@users.noreply.github.com> Co-authored-by: Pietropaolo Frisoni Co-authored-by: Isaac De Vlugt <34751083+isaacdevlugt@users.noreply.github.com> Co-authored-by: Vincent Michaud-Rioux Co-authored-by: lillian542 <38584660+lillian542@users.noreply.github.com> Co-authored-by: Christina Lee Co-authored-by: Diego <67476785+DSGuala@users.noreply.github.com> Co-authored-by: trbromley Co-authored-by: Korbinian Kottmann <43949391+Qottmann@users.noreply.github.com> Co-authored-by: Diego Co-authored-by: Cristian Emiliano Godinez Ramirez <57567043+EmilianoG-byte@users.noreply.github.com> Co-authored-by: Ahmed Darwish Co-authored-by: GitHub Actions Bot <> --- doc/code/qml_debugging.rst | 80 +-- doc/code/qml_noise.rst | 23 +- doc/code/qml_pytrees.rst | 13 + doc/code/qml_qchem.rst | 20 +- doc/index.rst | 1 + doc/introduction/data.rst | 2 - doc/introduction/dynamic_quantum_circuits.rst | 23 +- doc/introduction/inspecting_circuits.rst | 20 +- doc/introduction/operations.rst | 5 +- doc/releases/changelog-0.37.0.md | 674 ++++++++++++------ pennylane/boolean_fn.py | 55 +- pennylane/compiler/compiler.py | 4 +- pennylane/debugging/debugger.py | 85 +-- pennylane/devices/default_tensor.py | 17 +- pennylane/devices/tests/test_return_system.py | 7 + pennylane/drawer/draw.py | 55 +- pennylane/gradients/parameter_shift.py | 110 +-- pennylane/io.py | 51 +- pennylane/math/quantum.py | 7 +- pennylane/measurements/mid_measure.py | 2 +- pennylane/noise/conditionals.py | 61 +- pennylane/noise/noise_model.py | 10 +- pennylane/ops/functions/equal.py | 6 +- .../ops/op_math/controlled_decompositions.py | 4 +- .../decompositions/single_qubit_unitary.py | 4 +- pennylane/ops/op_math/pow.py | 20 + pennylane/pauli/conversion.py | 2 + pennylane/pytrees/pytrees.py | 12 +- pennylane/qaoa/layers.py | 1 + pennylane/qchem/__init__.py | 2 +- pennylane/qchem/convert_openfermion.py | 31 +- pennylane/qchem/hamiltonian.py | 31 +- pennylane/qchem/observable_hf.py | 7 +- pennylane/qnn/torch.py | 2 +- pennylane/queuing.py | 2 +- pennylane/resource/specs.py | 62 +- pennylane/templates/subroutines/qrom.py | 2 +- pennylane/transforms/add_noise.py | 49 +- pennylane/transforms/dynamic_one_shot.py | 10 +- pennylane/transforms/split_non_commuting.py | 2 + pennylane/transforms/transpile.py | 5 +- pennylane/typing.py | 6 +- pennylane/workflow/construct_batch.py | 20 +- pennylane/workflow/qnode.py | 2 +- tests/measurements/test_mid_measure.py | 10 +- .../op_math/test_controlled_decompositions.py | 5 +- tests/test_compiler.py | 8 + tests/test_debugging.py | 2 +- tests/transforms/test_split_non_commuting.py | 21 + 49 files changed, 1050 insertions(+), 603 deletions(-) create mode 100644 doc/code/qml_pytrees.rst diff --git a/doc/code/qml_debugging.rst b/doc/code/qml_debugging.rst index ed1539007dd..8d5daf0a743 100644 --- a/doc/code/qml_debugging.rst +++ b/doc/code/qml_debugging.rst @@ -31,6 +31,7 @@ the circuit operations are applied. The functionality is highlighted by the exam circuit below. .. code-block:: python + :linenos: import pennylane as qml @@ -50,7 +51,7 @@ circuit below. circuit(1.2345) -Running the above python script opens up the interactive ``[pldb]:`` prompt in the terminal. +Running the above python script opens up the interactive ``[pldb]`` prompt in the terminal. When this code reaches ``qml.breakpoint()`` it will pause and launch an interactive debugging prompt. The prompt specifies the path to the script and the next line to be executed after the breakpoint: @@ -59,29 +60,29 @@ executed after the breakpoint: > /Users/your/path/to/script.py(7)circuit() -> qml.Hadamard(wires=0) - [pldb]: + [pldb] Controlling Code Execution in the Debugging Context --------------------------------------------------- -The Pennylane Debugger (PLDB) is built from the native python debugger (Pdb), as such +The Pennylane Debugger (PLDB) is built on top of the native python debugger (PDB). As such, it shares a similar interface. We can interact with the debugger using the -builtin commands: ``list``, ``longlist``, ``next``, ``continue``, and ``quit``. Any -variables defined in the scope of the quantum function can also be accessed from the +built-in commands such as ``list``, ``longlist``, ``next``, ``continue``, and ``quit``. Any +variables defined within the scope of the quantum function can also be accessed from the debugger. .. code-block:: console - [pldb]: print(x) + [pldb] print(x) 1.2345 The ``list`` (and ``longlist``) command will print a section of code around the breakpoint, highlighting the next line to be executed. This can be used to determine -the location of execution in the circuit. +the location of the execution in the circuit. .. code-block:: console - [pldb]: longlist + [pldb] longlist 3 @qml.qnode(qml.device('default.qubit', wires=(0,1,2))) 4 def circuit(x): 5 qml.breakpoint() @@ -97,14 +98,14 @@ the location of execution in the circuit. 15 return qml.sample() The ``next`` command will execute the next line of code, and print the following -line to be executed, e.g., the next operation to execute is ``CNOT``. +line to be executed. In this example, the next operation to execute is the ``CNOT``. .. code-block:: console - [pldb]: next + [pldb] next > /Users/your/path/to/script.py(8)circuit() -> qml.CNOT(wires=(0,2)) - [pldb]: list + [pldb] list 3 @qml.qnode(qml.device('default.qubit', wires=(0,1,2))) 4 def circuit(x): 5 qml.breakpoint() @@ -117,16 +118,16 @@ line to be executed, e.g., the next operation to execute is ``CNOT``. 12 13 qml.breakpoint() -Alternative, the ``continue`` command allows for jumping between breakpoints. This command resumes -code execution until the next breakpoint is reached. Finally, the ``quit`` command -ends the debugging prompt. +Alternatively, the ``continue`` command allows for jumping between breakpoints. This command resumes +code execution until the next breakpoint is reached, or termination if there is none. Finally, +the ``quit`` command ends the debugging prompt and terminates the execution altogether. .. code-block:: console - [pldb]: continue + [pldb] continue > /Users/your/path/to/script.py(14)circuit() -> qml.RX(-x, wires=1) - [pldb]: list + [pldb] list 9 10 for w in (0, 1, 2): 11 qml.RX(2*x, wires=w) @@ -137,17 +138,16 @@ ends the debugging prompt. 16 17 circuit(1.2345) [EOF] - [pldb]: quit + [pldb] quit Extracting Circuit Information ------------------------------ -While in the debugging prompt, we can extract information and perform measurements -on the qunatum circuit. Specifically we can make measurements using -:func:`~pennylane.debug_expval`, :func:`~pennylane.debug_state`, -:func:`~pennylane.debug_probs`, and access the gates in the circuit using -:func:`~pennylane.debug_tape`. +While in the debugging prompt, we can extract information about the current contents +of the quantum tape using :func:`~pennylane.debug_tape`. We can also perform measurements dynamically +on the quantum circuit using :func:`~pennylane.debug_expval`, :func:`~pennylane.debug_state`, +and :func:`~pennylane.debug_probs`. Consider the circuit from above, @@ -155,7 +155,7 @@ Consider the circuit from above, > /Users/your/path/to/script.py(7)circuit() -> qml.Hadamard(wires=0) - [pldb]: longlist + [pldb] longlist 3 @qml.qnode(qml.device('default.qubit', wires=(0,1,2))) 4 def circuit(x): 5 qml.breakpoint() @@ -169,13 +169,13 @@ Consider the circuit from above, 13 qml.breakpoint() 14 qml.RX(-x, wires=1) 15 return qml.sample() - [pldb]: next + [pldb] next > /Users/your/path/to/script.py(8)circuit() -> qml.CNOT(wires=(0,2)) - [pldb]: next + [pldb] next > /Users/your/path/to/script.py(10)circuit() -> for w in (0, 1, 2): - [pldb]: + [pldb] All of the operations applied so far are tracked in the circuit's ``QuantumTape`` which is accessible using :func:`~pennylane.debug_tape`. This can be used to @@ -183,10 +183,10 @@ which is accessible using :func:`~pennylane.debug_tape`. This can be used to .. code-block:: console - [pldb]: qtape = qml.debug_tape() - [pldb]: qtape.operations + [pldb] qtape = qml.debug_tape() + [pldb] qtape.operations [Hadamard(wires=[0]), CNOT(wires=[0, 2])] - [pldb]: print(qtape.draw()) + [pldb] print(qtape.draw()) 0: ──H─╭●─┤ 2: ────╰X─┤ @@ -196,10 +196,10 @@ for the wires of interest can be probed using :func:`~pennylane.debug_probs`. .. code-block:: console - [pldb]: qml.debug_state() + [pldb] qml.debug_state() array([0.70710678+0.j, 0. +0.j, 0. +0.j, 0. +0.j, 0. +0.j, 0.70710678+0.j, 0. +0.j, 0. +0.j]) - [pldb]: qml.debug_probs(wires=(0,2)) + [pldb] qml.debug_probs(wires=(0,2)) array([0.5, 0. , 0. , 0.5]) Another method for probing the system is by measuring observables via @@ -207,9 +207,9 @@ Another method for probing the system is by measuring observables via .. code-block:: console - [pldb]: qml.debug_expval(qml.Z(0)) + [pldb] qml.debug_expval(qml.Z(0)) 0.0 - [pldb]: qml.debug_expval(qml.X(0)@qml.X(2)) + [pldb] qml.debug_expval(qml.X(0) @ qml.X(2)) 0.9999999999999996 Additionally, the quantum circuit can be dynamically updated by adding gates directly @@ -217,20 +217,20 @@ from the prompt. This allows users to modify the circuit *on-the-fly*! .. code-block:: console - [pldb]: continue + [pldb] continue > /Users/your/path/to/script.py(14)circuit() -> qml.RX(-x, wires=1) - [pldb]: qtape = qml.debug_tape() - [pldb]: print(qtape.draw(wire_order=(0,1,2))) + [pldb] qtape = qml.debug_tape() + [pldb] print(qtape.draw(wire_order=(0,1,2))) 0: ──H─╭●──RX─┤ 1: ────│───RX─┤ 2: ────╰X──RX─┤ - [pldb]: qml.RZ(0.5*x, wires=0) + [pldb] qml.RZ(0.5*x, wires=0) RZ(0.61725, wires=[0]) - [pldb]: qml.CZ(wires=(1,2)) + [pldb] qml.CZ(wires=(1,2)) CZ(wires=[1, 2]) - [pldb]: qtape = qml.debug_tape() - [pldb]: print(qtape.draw(wire_order=(0,1,2))) + [pldb] qtape = qml.debug_tape() + [pldb] print(qtape.draw(wire_order=(0,1,2))) 0: ──H─╭●──RX──RZ─┤ 1: ────│───RX─╭●──┤ 2: ────╰X──RX─╰Z──┤ diff --git a/doc/code/qml_noise.rst b/doc/code/qml_noise.rst index 29e73e17a63..badbf196ee9 100644 --- a/doc/code/qml_noise.rst +++ b/doc/code/qml_noise.rst @@ -50,16 +50,17 @@ quantum circuit. One can construct standard Boolean functions using the followin ~wires_eq ~wires_in -For example, a Boolean function that checks of an operation is on wire ``0`` can be created -like so: +For example, a Boolean function that checks if an operation is on wire ``0`` can be created +as follows: ->>> fn = wires_eq(0) +>>> fn = qml.noise.wires_eq(0) >>> op1, op2 = qml.PauliX(0), qml.PauliX(1) >>> fn(op1) -False ->>> fn(op2) True +>>> fn(op2) +False + Arbitrary Boolean functions can also be defined by wrapping the functional form of custom conditions with the following decorator: @@ -79,7 +80,7 @@ with a maximum parameter value: def rx_condition(op, **metadata): return isinstance(op, qml.RX) and op.parameters[0] < 1.0 -Boolean functions can be combined using standard bit-wise operators, such as +Boolean functions can be combined using standard bitwise operators, such as ``&``, ``|``, ``^``, or ``~``. The result will be another Boolean function. It is important to note that as Python will evaluate the expression in the order of their combination, i.e., left to right, the order of composition could matter, @@ -117,6 +118,7 @@ For example, a constant-valued over-rotation can be created using: >>> rx_constant = qml.noise.partial_wires(qml.RX(0.1, wires=[0])) >>> rx_constant(2) RX(0.1, 2) + >>> qml.NoiseModel({rx_condition: rx_constant}) NoiseModel({ BooleanFn(rx_condition): RX(phi=0.1) @@ -177,7 +179,7 @@ above, such as :func:`~.op_eq`. These objects do not need to be instantiated dir ~WiresEq ~WiresIn -Bitwise operations like `And` and `Or` are represented with the following classes in the +Bitwise operations like ``And`` and ``Or`` are represented with the following classes in the :mod:`boolean_fn` module: .. currentmodule:: pennylane.boolean_fn @@ -193,8 +195,15 @@ Bitwise operations like `And` and `Or` are represented with the following classe Class Inheritence Diagram ^^^^^^^^^^^^^^^^^^^^^^^^^ +Note all child classes inherit from the same parent :class:`~.BooleanFn`, +but are just located in different modules. + +**Noise Conditionals:** + .. inheritance-diagram:: pennylane.noise.conditionals :parts: 1 +**Boolean Fn conditionals:** + .. inheritance-diagram:: pennylane.boolean_fn :parts: 1 diff --git a/doc/code/qml_pytrees.rst b/doc/code/qml_pytrees.rst new file mode 100644 index 00000000000..2114c4d3094 --- /dev/null +++ b/doc/code/qml_pytrees.rst @@ -0,0 +1,13 @@ +qml.pytrees +=========== + +.. currentmodule:: pennylane.pytrees + +.. warning:: + + Unless you are a PennyLane or plugin developer, you likely do not need + to use these functions. + +.. automodapi:: pennylane.pytrees + :no-heading: + :include-all-objects: diff --git a/doc/code/qml_qchem.rst b/doc/code/qml_qchem.rst index c5e86986e90..9284c9f9e32 100644 --- a/doc/code/qml_qchem.rst +++ b/doc/code/qml_qchem.rst @@ -6,7 +6,9 @@ Overview The quantum chemistry module provides the functionality to perform Hartree-Fock calculations and construct observables such as molecular Hamiltonians as well as dipole moment, spin and particle -number observables. +number observables. It also includes functionalities to convert to and from Openfermion's +`QubitOperator `__ and +`FermionOperator `__. .. currentmodule:: pennylane.qchem @@ -63,21 +65,7 @@ We then construct the Hamiltonian. hamiltonian, qubits = qml.qchem.molecular_hamiltonian(molecule, args=args) >>> print(hamiltonian) - (-0.35968235922631075) [I0] -+ (-0.11496335836166222) [Z2] -+ (-0.11496335836166222) [Z3] -+ (0.13082414303722753) [Z1] -+ (0.13082414303722759) [Z0] -+ (0.1031689825163302) [Z0 Z2] -+ (0.1031689825163302) [Z1 Z3] -+ (0.15329959994281844) [Z0 Z3] -+ (0.15329959994281844) [Z1 Z2] -+ (0.15405495855815063) [Z0 Z1] -+ (0.1609686663987323) [Z2 Z3] -+ (-0.05013061742648825) [Y0 Y1 X2 X3] -+ (-0.05013061742648825) [X0 X1 Y2 Y3] -+ (0.05013061742648825) [Y0 X1 X2 Y3] -+ (0.05013061742648825) [X0 Y1 Y2 X3] +-0.3596823592263396 * I(0) + 0.1308241430372839 * Z(0) + -0.11496335836158356 * Z(2)+ 0.10316898251630022 * (Z(0) @ Z(2)) + 0.1308241430372839 * Z(1) + 0.15405495855812648 * (Z(0) @ Z(1)) + 0.050130617426473734 * (Y(0) @ X(1) @ X(2) @ Y(3)) + -0.050130617426473734 * (Y(0) @ Y(1) @ X(2) @ X(3)) + -0.050130617426473734 * (X(0) @ X(1) @ Y(2) @ Y(3)) + 0.050130617426473734 * (X(0) @ Y(1) @ Y(2) @ X(3)) + -0.11496335836158356 * Z(3) + 0.15329959994277395 * (Z(0) @ Z(3)) + 0.10316898251630022 * (Z(1) @ Z(3)) + 0.15329959994277395 * (Z(1) @ Z(2)) + 0.16096866639866414 * (Z(2) @ Z(3)) The generated Hamiltonian can be used in a circuit where the molecular geometry, the basis set parameters, and the circuit parameters are optimized simultaneously. Further information about diff --git a/doc/index.rst b/doc/index.rst index 04b52d62405..be37298567b 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -225,6 +225,7 @@ PennyLane is **free** and **open source**, released under the Apache License, Ve code/qml_capture code/qml_devices code/qml_measurements + code/qml_pytrees code/qml_operation code/qml_queuing code/qml_tape diff --git a/doc/introduction/data.rst b/doc/introduction/data.rst index 0a9f274e8ea..f283c43e48a 100644 --- a/doc/introduction/data.rst +++ b/doc/introduction/data.rst @@ -153,8 +153,6 @@ array([-1.5, -0.5, 0.5, 1.5]) For more details on reading and writing custom datasets, including metadata, please see the :mod:`~.data` module documentation. -:html:`
` - Quantum Datasets Functions and Classes -------------------------------------- diff --git a/doc/introduction/dynamic_quantum_circuits.rst b/doc/introduction/dynamic_quantum_circuits.rst index a1b4b07655c..2042e7d6e32 100644 --- a/doc/introduction/dynamic_quantum_circuits.rst +++ b/doc/introduction/dynamic_quantum_circuits.rst @@ -259,15 +259,16 @@ scalings with respect to the number of mid-circuit measurements (and shots) are .. rst-class:: tb -+--------------------------+-------------------------------------------+-----------------------------------------------------------+-------------------------------------------+--------------------+ -| **Simulation technique** | **Memory** | **Time** | **Differentiation** | **shots/analytic** | -+==========================+===========================================+===========================================================+===========================================+====================+ -| Deferred measurements | :rd:`\ ` :math:`\mathcal{O}(2^{n_{MCM}})` | :rd:`\ ` :math:`\mathcal{O}(2^{n_{MCM}})` | :gr:`\ ` yes \ :math:`{}^1` | :gr:`\ ` yes / yes | -+--------------------------+-------------------------------------------+-----------------------------------------------------------+-------------------------------------------+--------------------+ -| Dynamic one-shot | :gr:`\ ` :math:`\mathcal{O}(1)` | :rd:`\ ` :math:`\mathcal{O}(n_{shots})` | :or:`\ ` finite differences\ :math:`{}^2` | :or:`\ ` yes / no | -+--------------------------+-------------------------------------------+-----------------------------------------------------------+-------------------------------------------+--------------------+ -| Tree-traversal | :or:`\ ` :math:`\mathcal{O}(n_{MCM}+1)` | :or:`\ ` :math:`\mathcal{O}(min(n_{shots}, 2^{n_{MCM}}))` | :or:`\ ` finite differences\ :math:`{}^2` | :or:`\ ` yes / no | -+--------------------------+-------------------------------------------+-----------------------------------------------------------+-------------------------------------------+--------------------+ ++--------------------------+-------------------------------------------+-----------------------------------------------------------+-------------------------------------------+--------------+--------------+ +| **Simulation technique** | **Memory** | **Time** | **Differentiation** | **shots** | **analytic** | ++==========================+===========================================+===========================================================+===========================================+==============+==============+ +| Deferred measurements | :rd:`\ ` :math:`\mathcal{O}(2^{n_{MCM}})` | :rd:`\ ` :math:`\mathcal{O}(2^{n_{MCM}})` | :gr:`\ ` yes \ :math:`{}^1` | :gr:`\ ` yes | :gr:`\ ` yes | ++--------------------------+-------------------------------------------+-----------------------------------------------------------+-------------------------------------------+--------------+--------------+ +| Dynamic one-shot | :gr:`\ ` :math:`\mathcal{O}(1)` | :rd:`\ ` :math:`\mathcal{O}(n_{shots})` | :or:`\ ` finite differences\ :math:`{}^2` | :gr:`\ ` yes | :rd:`\ ` no | ++--------------------------+-------------------------------------------+-----------------------------------------------------------+-------------------------------------------+--------------+--------------+ +| Tree-traversal | :or:`\ ` :math:`\mathcal{O}(n_{MCM}+1)` | :or:`\ ` :math:`\mathcal{O}(min(n_{shots}, 2^{n_{MCM}}))` | :or:`\ ` finite differences\ :math:`{}^2` | :gr:`\ ` yes | :rd:`\ ` no | ++--------------------------+-------------------------------------------+-----------------------------------------------------------+-------------------------------------------+--------------+--------------+ + :math:`{}^1` Backpropagation and finite differences are fully supported. The adjoint method and the parameter-shift rule are supported if no postselection is used. @@ -323,11 +324,11 @@ before and after applying the transform: .. code-block:: pycon - >>> qml.draw(my_qnode)(*pars) + >>> print(qml.draw(my_qnode)(*pars)) 0: ──RY(0.64)─╭●───────RY(0.25)─┤ Probs 1: ───────────╰X──┤↗├──║────────┤ ╚═══╩════════╡ - >>> qml.draw(deferred_qnode)(*pars) + >>> print(qml.draw(deferred_qnode)(*pars)) 0: ──RY(0.64)─╭●────╭RY(0.25)─┤ Probs 1: ───────────╰X─╭●─│─────────┤ 2: ──────────────╰X─╰●────────┤ diff --git a/doc/introduction/inspecting_circuits.rst b/doc/introduction/inspecting_circuits.rst index 32fc92fbed0..5b20c467cbf 100644 --- a/doc/introduction/inspecting_circuits.rst +++ b/doc/introduction/inspecting_circuits.rst @@ -193,14 +193,14 @@ Consider the following python script containing the quantum circuit with breakpo circuit(1.23) -Running the circuit above launches an interactive :code:`[pldb]:` prompt. Here we can +Running the circuit above launches an interactive :code:`[pldb]` prompt. Here we can step through the circuit execution: .. code-block:: console > /Users/your/path/to/script.py(8)circuit() -> qml.RX(x, wires=0) - [pldb]: list + [pldb] list 3 4 @qml.qnode(dev) 5 def circuit(x): @@ -212,7 +212,7 @@ step through the circuit execution: 11 qml.breakpoint() 12 13 qml.CNOT(wires=[0, 1]) - [pldb]: next + [pldb] next > /Users/your/path/to/script.py(9)circuit() -> qml.Hadamard(wires=1) @@ -221,16 +221,16 @@ the circuit in execution: .. code-block:: console - [pldb]: qml.debug_state() + [pldb] qml.debug_state() array([0.81677345+0.j , 0. +0.j , 0. -0.57695852j, 0. +0.j ]) - [pldb]: continue + [pldb] continue > /Users/your/path/to/script.py(13)circuit() -> qml.CNOT(wires=[0, 1]) - [pldb]: next + [pldb] next > /Users/your/path/to/script.py(14)circuit() -> return qml.expval(qml.Z(0)) - [pldb]: list + [pldb] list 8 qml.RX(x, wires=0) 9 qml.Hadamard(wires=1) 10 @@ -246,12 +246,12 @@ We can also visualize the circuit and dynamically queue operations directly to t .. code-block:: console - [pldb]: print(qml.debug_tape().draw()) + [pldb] print(qml.debug_tape().draw()) 0: ──RX─╭●─┤ 1: ──H──╰X─┤ - [pldb]: qml.RZ(-4.56, 1) + [pldb] qml.RZ(-4.56, 1) RZ(-4.56, wires=[1]) - [pldb]: print(qml.debug_tape().draw()) + [pldb] print(qml.debug_tape().draw()) 0: ──RX─╭●─────┤ 1: ──H──╰X──RZ─┤ diff --git a/doc/introduction/operations.rst b/doc/introduction/operations.rst index bb978640dbe..7d223d1260d 100644 --- a/doc/introduction/operations.rst +++ b/doc/introduction/operations.rst @@ -60,7 +60,7 @@ Operator to Operator functions ~pennylane.map_wires ~pennylane.dot ~pennylane.evolve - ~pennylane.iterative_qpe + ~pennylane.simplify These operator functions act on operators to produce new operators. @@ -80,7 +80,8 @@ Operator to Other functions ~pennylane.is_commuting ~pennylane.is_hermitian ~pennylane.is_unitary - ~pennylane.simplify + ~pennylane.iterative_qpe + These operator functions act on operators and return other data types. All operator functions can be used on instantiated operators. diff --git a/doc/releases/changelog-0.37.0.md b/doc/releases/changelog-0.37.0.md index 126394dd242..046df5dcc63 100644 --- a/doc/releases/changelog-0.37.0.md +++ b/doc/releases/changelog-0.37.0.md @@ -6,104 +6,346 @@

Execute faster with Default Tensor 🔗

-* Added a quantum debugger (`PLDB`) which interfaces via `qml.breakpoint()` and provides tools for - debugging quantum circuits. Users can step through the quantum circuit operations, dynamically - queue operations and make measurements using (`qml.debug_state()`, `qml.debug_probs()`, - `qml.debug_expval()`, and `qml.debug_tape()`). - [(#5680)](https://github.com/PennyLaneAI/pennylane/pull/5680) - [(#5749)](https://github.com/PennyLaneAI/pennylane/pull/5749) - [(#5789)](https://github.com/PennyLaneAI/pennylane/pull/5789) +

Execute wide circuits with Default Tensor 🔗

-* The `default.tensor` device now supports the `tn` method to simulate quantum circuits using exact tensor networks. +* A new `default.tensor` device is now available for performing + [tensor network](https://en.wikipedia.org/wiki/Tensor_network) and + [matrix product state](https://en.wikipedia.org/wiki/Matrix_product_state) simulations + of quantum circuits using the + [quimb backend](https://quimb.readthedocs.io/en/latest/). + [(#5699)](https://github.com/PennyLaneAI/pennylane/pull/5699) + [(#5744)](https://github.com/PennyLaneAI/pennylane/pull/5744) [(#5786)](https://github.com/PennyLaneAI/pennylane/pull/5786) + [(#5795)](https://github.com/PennyLaneAI/pennylane/pull/5795) -* The `default.tensor` device is introduced to perform tensor network simulations of quantum circuits using the `mps` (Matrix Product State) method. - [(#5699)](https://github.com/PennyLaneAI/pennylane/pull/5699) + Either method can be selected when instantiating the `default.tensor` device by setting the + `method` keyword argument to `"tn"` (tensor network) or `"mps"` (matrix product state). This + device can simulate a large number of qubits. Take this example that + simulates 1000 qubits with a shallow circuit in a few seconds! -* The wires for the `default.tensor` device are selected at runtime if they are not provided by user. - [(#5744)](https://github.com/PennyLaneAI/pennylane/pull/5744) + ```python + import pennylane as qml -* Add operation and measurement specific routines in `default.tensor` to improve scalability. - [(#5795)](https://github.com/PennyLaneAI/pennylane/pull/5795) + num_qubits = 1000 + dev = qml.device("default.tensor", method="mps") + + @qml.qnode(dev) + def circuit(): + qml.Hadamard(0) + for i in range(num_qubits - 1): + qml.CNOT([i, i + 1]) + return qml.expval(qml.Z(num_qubits - 1)) + ``` + + ```pycon + >>> circuit() + tensor(0., requires_grad=True) + ``` + + For matrix product state simulations (`method="mps"`), we can make the execution be approximate by setting `max_bond_dim` (see the + [device's documentation](https://docs.pennylane.ai/en/stable/code/api/pennylane.devices.default_tensor.DefaultTensor.html) + for more details). + The maximum bond dimension has implications for the speed of the simulation and lets us control the degree of the approximation, as shown in the + following example. First, set up the circuit: + + ```python + import numpy as np + + n_layers = 10 + n_wires = 10 + + initial_shape, weights_shape = qml.SimplifiedTwoDesign.shape(n_layers, n_wires) + np.random.seed(1967) + initial_layer_weights = np.random.random(initial_shape) + weights = np.random.random(weights_shape) + + def f(): + qml.SimplifiedTwoDesign(initial_layer_weights, weights, range(n_wires)) + return qml.expval(qml.Z(0)) + ``` + + The `default.tensor` device is instantiated with a `max_bond_dim` value: + + ```python + dev_dq = qml.device("default.qubit") + value_dq = qml.QNode(f, dev_dq)() + + dev_mps = qml.device("default.tensor", max_bond_dim=5) + value_mps = qml.QNode(f, dev_mps)() + ``` + + With this bond dimension, the expectation values calculated for `default.qubit` and + `default.tensor` are different: + + ```pycon + >>> np.abs(value_dq - value_mps) + tensor(0.0253213, requires_grad=True) + ``` + + Learn more about `default.tensor` and how to configure it by + [visiting the how-to guide](https://pennylane.ai/qml/demos/tutorial_How_to_simulate_quantum_circuits_with_tensor_networks/).

Add noise models to your quantum circuits 📺

-* A new `qml.noise` module which contains utility function for building `NoiseModels` - and an `add_noise` tranform for addding it to quantum circuits. +* Support for building noise models and applying them to a quantum circuit has been added +via the `NoiseModel` class and an `add_noise` transform. [(#5674)](https://github.com/PennyLaneAI/pennylane/pull/5674) [(#5684)](https://github.com/PennyLaneAI/pennylane/pull/5684) [(#5718)](https://github.com/PennyLaneAI/pennylane/pull/5718) + Under the hood, PennyLane's approach to noise models is insertion-based, meaning that noise is included + by *inserting* additional operators (gates or channels) that describe the noise into the quantum circuit. + Creating a `NoiseModel` boils down to defining Boolean conditions under which specific noisy operations + are inserted. There are several ways to specify conditions for adding noisy operations: + + * `qml.noise.op_eq(op)`: if the operator `op` is encountered in the circuit, add noise. + * `qml.noise.op_in(ops)`: if any operators in `ops` are encountered in the circuit, add noise. + * `qml.noise.wires_eq(wires)`: if an operator is applied to `wires`, add noise. + * `qml.noise.wires_in(wires)`: if an operator is applied to any wire in `wires`, add noise. + * custom noise conditions: custom conditions can be defined as functions decorated with `qml.BooleanFn` that return a Boolean value. For example, the following function will insert noise if a `qml.RY` operator is encountered with an angle of rotation that is less than `0.5`: + + ```python + @qml.BooleanFn + def c0(op): + return isinstance(op, qml.RY) and op.parameters[0] < 0.5 + ``` + + Conditions can also be combined together with `&`, `and`, `|`, etc. + Once the conditions under which noise is to be inserted have been stated, we can specify exactly what + noise is inserted with the following: + + * `qml.noise.partial_wires(op)`: insert `op` on the wires that are specified by the condition that triggers adding this noise + * custom noise operations: custom noise can be specified by defining a standard quantum function like below. + + ```python + def n0(op, **kwargs): + qml.RY(op.parameters[0] * 0.05, wires=op.wires) + ``` + + With that, we can create a `qml.NoiseModel` object whose argument must be a dictionary mapping conditions + to noise: + + ```python + c1 = qml.noise.op_eq(qml.X) & qml.noise.wires_in([0, 1]) + n1 = qml.noise.partial_wires(qml.AmplitudeDamping, 0.4) + + noise_model = qml.NoiseModel({c0: n0, c1: n1}) + ``` + ```pycon - >>> fcond1 = qml.noise.op_eq(qml.RX) & qml.noise.wires_in([0, 1]) - >>> noise1 = qml.noise.partial_wires(qml.PhaseDamping, 0.4) - >>> fcond2 = qml.noise.op_in([qml.RY, qml.RZ]) - >>> def noise2(op, **kwargs): - ... qml.ThermalRelaxationError(op.parameters[0] * 0.05, kwargs["t1"], 0.2, 0.6, op.wires) - >>> noise_model = qml.NoiseModel({fcond1: noise1, fcond2: noise2}, t1=2.0) >>> noise_model NoiseModel({ - OpEq(RX) & WiresIn([0, 1]) = PhaseDamping(gamma=0.4) - OpIn(['RY', 'RZ']) = noise2 - }, t1 = 2.0) + BooleanFn(c0): n0 + OpEq(PauliX) | WiresIn([0, 1]): AmplitudeDamping(gamma=0.4) + }) + ``` + + The noise model created can then be added to a QNode with `qml.add_noise`: + + ```python + dev = qml.device("lightning.qubit", wires=3) + + @qml.qnode(dev) + def circuit(): + qml.Y(0) + qml.CNOT([0, 1]) + qml.RY(0.3, wires=2) # triggers c0 + qml.X(1) # triggers c1 + return qml.state() ``` ```pycon - >>> @partial(qml.transforms.add_noise, noise_model=noise_model) - ... @qml.qnode(dev) - ... def circuit(w, x, y, z): - ... qml.RX(w, wires=0) - ... qml.RY(x, wires=1) - ... qml.CNOT(wires=[0, 1]) - ... qml.RY(y, wires=0) - ... qml.RX(z, wires=1) - ... return qml.expval(qml.Z(0) @ qml.Z(1)) - >>> print(qml.draw(circuit)(0.9, 0.4, 0.5, 0.6)) - 0: ──RX(0.90)──PhaseDamping(0.40)──────────────────────────╭●──RY(0.50) - 1: ──RY(0.40)──ThermalRelaxationError(0.02,2.00,0.20,0.60)─╰X──RX(0.60) - ───ThermalRelaxationError(0.03,2.00,0.20,0.60)─┤ ╭ - ───PhaseDamping(0.40)──────────────────────────┤ ╰ + >>> print(qml.draw(circuit)()) + 0: ──Y────────╭●────┤ State + 1: ───────────╰X──X─┤ State + 2: ──RY(0.30)───────┤ State + >>> circuit = qml.add_noise(circuit, noise_model) + >>> print(qml.draw(circuit)()) + 0: ──Y────────╭●───────────────────────────────────┤ State + 1: ───────────╰X─────────X──AmplitudeDamping(0.40)─┤ State + 2: ──RY(0.30)──RY(0.01)────────────────────────────┤ State ``` -

Identify mistakes in your code with the PennyLane debugger 🚫🐞

+ If more than one transform is applied to a QNode, control over when/where the `add_noise` transform is applied + in relation to the other transforms can be specified with the `level` keyword argument. By default, `add_noise` is applied + after all the transforms that have been manually applied to the QNode until that point. + To learn more about this new functionality, check out our [noise module documentation](https://docs.pennylane.ai/en/stable/code/qml_noise.html) + and keep your eyes peeled for an in-depth demo! + +

Catch bugs with the PennyLane debugger 🚫🐞

-* Added a quantum debugger (`PLDB`) which interfaces via `qml.breakpoint()` and provides tools for - debugging quantum circuits. Users can step through the quantum circuit operations, dynamically - queue operations and make measurements using (`qml.debug_state()`, `qml.debug_probs()`, - `qml.debug_expval()`, and `qml.debug_tape()`). +* The new PennyLane quantum debugger allows pausing simulation via the `qml.breakpoint()` command and provides tools for + analyzing quantum circuits during execution. [(#5680)](https://github.com/PennyLaneAI/pennylane/pull/5680) [(#5749)](https://github.com/PennyLaneAI/pennylane/pull/5749) [(#5789)](https://github.com/PennyLaneAI/pennylane/pull/5789) + + This includes monitoring the circuit via measurements using `qml.debug_state()`, `qml.debug_probs()`, + `qml.debug_expval()`, and `qml.debug_tape()`, stepping through the operations in a quantum circuit, and interactively + adding operations during execution. + + Including `qml.breakpoint()` in a circuit will cause the simulation to pause during execution and bring up the interactive console. + For example, consider the following code in a Python file called `script.py`: + + ```python + @qml.qnode(qml.device('default.qubit', wires=(0,1,2))) + def circuit(x): + qml.Hadamard(wires=0) + qml.CNOT(wires=(0,2)) + qml.breakpoint() + + qml.RX(x, wires=1) + qml.RY(x, wires=2) + qml.breakpoint() + + return qml.sample() + + circuit(1.2345) + ``` + + Upon executing `script.py`, the simulation pauses at the first breakpoint: + ```pycon + > /Users/your/path/to/script.py(8)circuit() + -> qml.RX(x, wires=1) + (pldb): + ``` + + While debugging, we can access circuit information. + For example, `qml.debug_tape()` returns the tape of the circuit, giving access to its operations and drawing: + + ```pycon + (pldb): tape = qml.debug_tape() + (pldb): print(tape.draw(wire_order=[0,1,2])) + 0: ──H─╭●─┤ + 2: ────╰X─┤ + (pldb): tape.operations + [Hadamard(wires=[0]), CNOT(wires=[0, 2])] + ``` + + While `qml.debug_state()` is equivalent to `qml.state()` and gives the current state: + + ```pycon + (pldb): print(qml.debug_state()) + [0.70710678+0.j 0. +0.j 0. +0.j 0. +0.j + 1. +0.j 0.70710678+0.j 0. +0.j 0. +0.j] + ``` + + Other debugger functions like `qml.debug_probs()` and `qml.debug_expval()` also function like their simulation counterparts (`qml.probs` and `qml.expval`, respectively) and are described in more detail in the [debugger documentation](https://docs.pennylane.ai/en/stable/code/qml_debugging.html) + + Additionally, standard debugging commands are available to navigate through code, including `list`, `longlist`, `next`, `continue`, and `quit`, as described in [the debugging documentation](https://docs.pennylane.ai/en/stable/code/qml_debugging.html#controlling-code-execution-in-the-debugging-context). + + Finally, to modify a circuit mid-run, simply call the desired PennyLane operations: + + ```pycon + (pldb) qml.CNOT(wires=(0,2)) + CNOT(wires=[0, 2]) + (pldb): print(qml.debug_tape().draw(wire_order=[0,1,2])) + 0: ──H─╭●─╭●─┤ + 2: ────╰X─╰X─┤ + ``` + +Stay tuned for an in-depth demonstration on using this feature with real-world examples!

Convert between OpenFermion and PennyLane 🤝

-* The ``from_openfermion`` and ``to_openfermion`` functions are added to convert between - OpenFermion and PennyLane objects. +* Two new functions called `qml.from_openfermion` and `qml.to_openfermion` are now available to convert between + OpenFermion and PennyLane objects. This includes both fermionic and qubit operators. [(#5773)](https://github.com/PennyLaneAI/pennylane/pull/5773) [(#5808)](https://github.com/PennyLaneAI/pennylane/pull/5808) [(#5881)](https://github.com/PennyLaneAI/pennylane/pull/5881) + + For fermionic operators: - ```python - of_op = openfermion.FermionOperator('0^ 2') - pl_op = qml.from_openfermion(of_op) - of_op_new = qml.to_openfermion(pl_op) + ```pycon + >>> import openfermion + >>> of_fermionic = openfermion.FermionOperator('0^ 2') + >>> type(of_fermionic) + + >>> pl_fermionic = qml.from_openfermion(of_fermionic) + >>> type(pl_fermionic) + + >>> print(pl_fermionic) + a⁺(0) a(2) ``` + And for qubit operators: + ```pycon - >>> print(pl_op) - a⁺(0) a(2) - >>> print(of_op_new) - 1.0 [0^ 2] + >>> of_qubit = 0.5 * openfermion.QubitOperator('X0 X5') + >>> pl_qubit = qml.from_openfermion(of_qubit) + >>> print(pl_qubit) + 0.5 * (X(0) @ X(5)) ```

Better control over when drawing and specs take place 🎚️

-* Circuits can now be plotted at any specific point of the transform program through the `level` keyword argument in `draw()` and `draw_mpl()`. +* It is now possible to control the stage at which `qml.draw`, `qml.draw_mpl`, and `qml.specs` occur + within a QNode's + [transform](https://docs.pennylane.ai/en/stable/code/qml_transforms.html) program. [(#5855)](https://github.com/PennyLaneAI/pennylane/pull/5855) - -* `specs()` can now be requested at any specific point of the transform program through the `level` keyword argument. [(#5781)](https://github.com/PennyLaneAI/pennylane/pull/5781/) + Consider the following circuit which has multiple transforms applied: + + ```python + @qml.transforms.split_non_commuting + @qml.transforms.cancel_inverses + @qml.transforms.merge_rotations + @qml.qnode(qml.device("default.qubit")) + def f(): + qml.Hadamard(0) + qml.Y(0) + qml.RX(0.4, 0) + qml.RX(-0.4, 0) + qml.Y(0) + return qml.expval(qml.X(0) + 2 * qml.Y(0)) + ``` + + We can specify a `level` value when using `qml.draw()`: + + ```pycon + >>> print(qml.draw(f, level=0)()) # input program + 0: ──H──Y──RX(0.40)──RX(-0.40)──Y─┤ + >>> print(qml.draw(f, level=1)()) # rotations merged + 0: ──H──Y──Y─┤ + >>> print(qml.draw(f, level=2)()) # inverses cancelled + 0: ──H─┤ + >>> print(qml.draw(f, level=3)()) # Hamiltonian expanded + 0: ──H─┤ + + 0: ──H─┤ + ``` + + The + [qml.workflow.get_transform_program function](https://docs.pennylane.ai/en/latest/code/api/pennylane.workflow.get_transform_program.html) + can be used to see the full transform program. + + ```pycon + >>> qml.workflow.get_transform_program(f) + TransformProgram(merge_rotations, cancel_inverses, split_non_commuting, validate_device_wires, mid_circuit_measurements, decompose, validate_measurements, validate_observables, no_sampling) + ``` + + Note that additional transforms can be added automatically from device preprocessing or gradient + calculations. Rather than providing an integer value to `level`, it is possible to target + the `"user"`, `"gradient"` or `"device"` stages: + + ```python + n_wires = 3 + x = np.random.random((2, n_wires)) + + @qml.qnode(qml.device("default.qubit")) + def f(): + qml.BasicEntanglerLayers(x, range(n_wires)) + return qml.expval(qml.X(0)) + ``` + + ```pycon + >>> print(pl_op) + a⁺(0) a(2) + >>> print(of_op_new) + 1.0 [0^ 2] + ``` +

Improvements 🛠

Community contributions, including UnitaryHACK 💛

@@ -111,76 +353,76 @@ * `default.clifford` now supports arbitrary state-based measurements with `qml.Snapshot`. [(#5794)](https://github.com/PennyLaneAI/pennylane/pull/5794) -* Implemented kwargs (`check_interface`, `check_trainability`, `rtol` and `atol`) support in `qml.equal` for the operators `Pow`, `Adjoint`, `Exp`, and `SProd`. - [(#5668)](https://github.com/PennyLaneAI/pennylane/issues/5668) +* `qml.equal` now properly handles `Pow`, `Adjoint`, `Exp`, and `SProd` operators as arguments across + different interfaces and tolerances with the addition of four new keyword arguments: `check_interface`, + `check_trainability`, `atol` and `rtol`. + [(#5668)](https://github.com/PennyLaneAI/pennylane/pull/5668) -* `qml.QutritDepolarizingChannel` has been added, allowing for depolarizing noise to be simulated on the `default.qutrit.mixed` device. - [(#5502)](https://github.com/PennyLaneAI/pennylane/pull/5502) - -* Implement support in `assert_equal` for `Operator`, `Controlled`, `Adjoint`, `Pow`, `Exp`, `SProd`, `ControlledSequence`, `Prod`, `Sum`, `Tensor` and `Hamiltonian` + +* The implementation for `qml.assert_equal` has been updated for `Operator`, `Controlled`, `Adjoint`, + `Pow`, `Exp`, `SProd`, `ControlledSequence`, `Prod`, `Sum`, `Tensor` and `Hamiltonian` instances. [(#5780)](https://github.com/PennyLaneAI/pennylane/pull/5780) [(#5877)](https://github.com/PennyLaneAI/pennylane/pull/5877) + +* `qml.from_qasm` now supports the ability to convert mid-circuit measurements from `OpenQASM 2` code, + and it can now also take an optional argument to specify a list of measurements to be performed at + the end of the circuit, just like `qml.from_qiskit`. + [(#5818)](https://github.com/PennyLaneAI/pennylane/pull/5818) -* `qml.QutritChannel` has been added, enabling the specification of noise using a collection of (3x3) Kraus matrices on the `default.qutrit.mixed` device. +* Four new operators have been added for simulating noise on the `default.qutrit.mixed` device: + [(#5502)](https://github.com/PennyLaneAI/pennylane/pull/5502) [(#5793)](https://github.com/PennyLaneAI/pennylane/issues/5793) - -* `qml.QutritAmplitudeDamping` channel has been added, allowing for noise processes modelled by amplitude damping to be simulated on the `default.qutrit.mixed` device. [(#5503)](https://github.com/PennyLaneAI/pennylane/pull/5503) [(#5757)](https://github.com/PennyLaneAI/pennylane/pull/5757) [(#5799)](https://github.com/PennyLaneAI/pennylane/pull/5799) - -* `qml.TritFlip` has been added, allowing for trit flip errors, such as misclassification, - to be simulated on the `default.qutrit.mixed` device. [(#5784)](https://github.com/PennyLaneAI/pennylane/pull/5784) -* `qml.from_qasm` now supports the ability to convert mid-circuit measurements from `OpenQASM 2` code, and it can now also take an - optional argument to specify a list of measurements to be performed at the end of the circuit, just like `from_qiskit`. - [(#5818)](https://github.com/PennyLaneAI/pennylane/pull/5818) + * `qml.QutritDepolarizingChannel`: a channel that adds depolarizing noise. + * `qml.QutritChannel`: enables the specification of noise using a collection of (3x3) Kraus matrices. + * `qml.QutritAmplitudeDamping`: a channel that adds noise processes modelled by amplitude damping. + * `qml.TritFlip`: a channel that adds trit flip errors, such as misclassification. -

Faster and flexible mid-circuit measurements

+

Faster and more flexible mid-circuit measurements

-* The `default.qubit` device implements a depth-first tree-traversal algorithm to - accelerate native mid-circuit measurement execution. The new implementation - supports classical control, collecting statistics, and post-selection, along - with all measurements enabled with `qml.dynamic_one_shot`. +* The `default.qubit` device supports a depth-first tree-traversal algorithm to accelerate native mid-circuit + measurement execution. Accessible through the QNode argument `mcm_method="tree-traversal"`, + this new implementation supports classical control, collecting statistics, and + post-selection, along with all measurements enabled with `qml.dynamic_one_shot`. More information + about this new mid-circuit measurement method can be found on our + [measurement documentation page](https://docs.pennylane.ai/en/stable/introduction/dynamic_quantum_circuits.html#tree-traversal-algorithm). [(#5180)](https://github.com/PennyLaneAI/pennylane/pull/5180) -* `qml.QNode` and `qml.qnode` now accept two new keyword arguments: `postselect_mode` and `mcm_method`. - These keyword arguments can be used to configure how the device should behave when running circuits with - mid-circuit measurements. +* `qml.QNode` and the `@qml.qnode` decorator now accept two new keyword arguments: `postselect_mode` + and `mcm_method`. These keyword arguments can be used to configure how the device should behave when + running circuits with mid-circuit measurements. [(#5679)](https://github.com/PennyLaneAI/pennylane/pull/5679) [(#5833)](https://github.com/PennyLaneAI/pennylane/pull/5833) [(#5850)](https://github.com/PennyLaneAI/pennylane/pull/5850) - * `postselect_mode="hw-like"` will indicate to devices to discard invalid shots when postselecting + * `postselect_mode="hw-like"` indicates to devices to discard invalid shots when postselecting mid-circuit measurements. Use `postselect_mode="fill-shots"` to unconditionally sample the postselected value, thus making all samples valid. This is equivalent to sampling until the number of valid samples matches the total number of shots. * `mcm_method` will indicate which strategy to use for running circuits with mid-circuit measurements. Use `mcm_method="deferred"` to use the deferred measurements principle, or `mcm_method="one-shot"` - to execute once for each shot. If using `qml.jit` with the Catalyst compiler, `mcm_method="single-branch-statistics"` + to execute once for each shot. If `qml.qjit` is being used (the Catalyst compiler), `mcm_method="single-branch-statistics"` is also available. Using this method, a single branch of the execution tree will be randomly explored. -* The `dynamic_one_shot` transform is made compatible with the Catalyst compiler. - [(#5766)](https://github.com/PennyLaneAI/pennylane/pull/5766) - [(#5888)](https://github.com/PennyLaneAI/pennylane/pull/5888) - [(#5930)](https://github.com/PennyLaneAI/pennylane/pull/5930) +* The `dynamic_one_shot` transform received a few improvements: -* Rationalize MCM tests, removing most end-to-end tests from the native MCM test file, - but keeping one that validates multiple mid-circuit measurements with any allowed return - and interface end-to-end tests. - [(#5787)](https://github.com/PennyLaneAI/pennylane/pull/5787) - -* The `dynamic_one_shot` transform uses a single auxiliary tape with a shot vector and `default.qubit` implements the loop over shots with `jax.vmap`. - [(#5617)](https://github.com/PennyLaneAI/pennylane/pull/5617) - -* The `dynamic_one_shot` transform can be compiled with `jax.jit`. - [(#5557)](https://github.com/PennyLaneAI/pennylane/pull/5557) - -* When using `defer_measurements` with postselecting mid-circuit measurements, operations - that will never be active due to the postselected state are skipped in the transformed - quantum circuit. In addition, postselected controls are skipped, as they are evaluated - at transform time. This optimization feature can be turned off by setting `reduce_postselected=False` + * `dynamic_one_shot` is now compatible with `qml.qjit` (the Catalyst compiler). + [(#5766)](https://github.com/PennyLaneAI/pennylane/pull/5766) + [(#5888)](https://github.com/PennyLaneAI/pennylane/pull/5888) + * `dynamic_one_shot` now uses a single auxiliary tape with a shot vector and `default.qubit` implements + the loop over shots with `jax.vmap`. + [(#5617)](https://github.com/PennyLaneAI/pennylane/pull/5617) + * `dynamic_one_shot` is now compatible with `jax.jit`. + [(#5557)](https://github.com/PennyLaneAI/pennylane/pull/5557) + +* When using `defer_measurements` with postselection, operations that will never be active due to the + postselected state are skipped in the transformed quantum circuit. In addition, postselected controls + are skipped, as they are evaluated when the transform is applied. This optimization feature can be + turned off by setting `reduce_postselected=False`. [(#5558)](https://github.com/PennyLaneAI/pennylane/pull/5558) Consider a simple circuit with three mid-circuit measurements, two of which are postselecting, @@ -195,13 +437,13 @@ mcm0 = qml.measure(0, postselect=0, reset=False) mcm1 = qml.measure(1, postselect=None, reset=True) mcm2 = qml.measure(2, postselect=1, reset=False) - qml.cond(mcm0+mcm1+mcm2==1, qml.RX)(0.5, 3) + qml.cond(mcm0 + mcm1 + mcm2 == 1, qml.RX)(0.5, 3) return qml.expval(qml.Z(0) @ qml.Z(3)) ``` Without the new optimization, we obtain three gates, each controlled on the three measured qubits. They correspond to the combinations of controls that satisfy the condition - `mcm0+mcm1+mcm2==1`: + `mcm0 + mcm1 + mcm2 == 1`: ```pycon >>> print(qml.draw(qml.defer_measurements(node, reduce_postselected=False))(0.6)) @@ -227,21 +469,24 @@ There is only one controlled gate with only one control wire. +* Mid-circuit measurement tests have been streamlined and refactored, removing most end-to-end tests + from the native MCM test file, but keeping one that validates multiple mid-circuit measurements with + any allowed return and interface end-to-end tests. + [(#5787)](https://github.com/PennyLaneAI/pennylane/pull/5787) +

Access to QROM

-* QROM template is added. This template allows you to enter classic data in the form of bitstrings. +* [The QROM algorithm](https://arxiv.org/abs/1812.00954) is now available in PennyLane with `qml.QROM`. + This template allows you to enter classical data in the form of bitstrings. [(#5688)](https://github.com/PennyLaneAI/pennylane/pull/5688) ```python - # a list of bitstrings is defined bitstrings = ["010", "111", "110", "000"] dev = qml.device("default.qubit", shots = 1) @qml.qnode(dev) def circuit(): - - # the third index is encoded in the control wires [0, 1] qml.BasisEmbedding(2, wires = [0,1]) qml.QROM(bitstrings = bitstrings, @@ -251,17 +496,19 @@ return qml.sample(wires = [2,3,4]) ``` - ```pycon + + ```pycon >>> print(circuit()) [1 1 0] ```

Capturing and representing hybrid programs

-* A number of templates have been updated to be valid pytrees and PennyLane operations. +* A number of templates have been updated to be valid PyTrees and PennyLane operations. [(#5698)](https://github.com/PennyLaneAI/pennylane/pull/5698) -* PennyLane operators, measurements, and QNodes can now automatically be captured as instructions in JAXPR. +* PennyLane operators, measurements, and QNodes can now automatically be captured as instructions in + JAXPR. [(#5564)](https://github.com/PennyLaneAI/pennylane/pull/5564) [(#5511)](https://github.com/PennyLaneAI/pennylane/pull/5511) [(#5708)](https://github.com/PennyLaneAI/pennylane/pull/5708) @@ -269,7 +516,7 @@ [(#5686)](https://github.com/PennyLaneAI/pennylane/pull/5686) [(#5889)](https://github.com/PennyLaneAI/pennylane/pull/5889) -* The `qml.pytrees` module now has `flatten` and `unflatten` methods for serializing pytrees. +* The `qml.PyTrees` module now has `flatten` and `unflatten` methods for serializing PyTrees. [(#5701)](https://github.com/PennyLaneAI/pennylane/pull/5701) * `qml.sample` can now be used on Boolean values representing mid-circuit measurement results in @@ -279,56 +526,49 @@

Quantum chemistry

-* The `qml.qchem.Molecule` object is now the central object used by all qchem functions. - [(#5571)](https://github.com/PennyLaneAI/pennylane/pull/5571) - -* The `qml.qchem.Molecule` class now supports Angstrom as a unit. - [(#5694)](https://github.com/PennyLaneAI/pennylane/pull/5694) - -* The `qml.qchem.Molecule` class now supports open-shell systems. - [(#5655)](https://github.com/PennyLaneAI/pennylane/pull/5655) +* The `qml.qchem.Molecule` object received a few improvements: + * `qml.qchem.Molecule` is now the central object used by all qchem functions. + [(#5571)](https://github.com/PennyLaneAI/pennylane/pull/5571) + * `qml.qchem.Molecule` now supports Angstrom as a unit. + [(#5694)](https://github.com/PennyLaneAI/pennylane/pull/5694) + * `qml.qchem.Molecule` now supports open-shell systems. + [(#5655)](https://github.com/PennyLaneAI/pennylane/pull/5655) * The `qml.qchem.molecular_hamiltonian` function now supports parity and Bravyi-Kitaev mappings. [(#5657)](https://github.com/PennyLaneAI/pennylane/pull/5657/) -* The qchem docs are updated with the new qchem improvements. - [(#5758)](https://github.com/PennyLaneAI/pennylane/pull/5758/) - [(#5638)](https://github.com/PennyLaneAI/pennylane/pull/5638/) - -* `qml.qchem.molecular_dipole` function is added for calculating the dipole operator using "dhf" and "openfermion" backends. +* `qml.qchem.molecular_dipole` function has been added for calculating the dipole operator using the + `"dhf"` and `"openfermion"` backends. [(#5764)](https://github.com/PennyLaneAI/pennylane/pull/5764) -* The qchem module has dedicated functions for calling `pyscf` and `openfermion` backends. The - ``molecular_hamiltonian`` and ``molecular_dipole`` functions are moved to ``hamiltonian`` and - ``dipole`` modules. +* The qchem module now has dedicated functions for calling the `pyscf` and `openfermion` backends and + the `molecular_hamiltonian` and `molecular_dipole` functions have been moved to `hamiltonian` and + `dipole` modules. [(#5553)](https://github.com/PennyLaneAI/pennylane/pull/5553) [(#5863)](https://github.com/PennyLaneAI/pennylane/pull/5863) -* Add more fermionic-to-qubit tests to cover cases when the mapped operator is different for various mapping schemes. +* More fermionic-to-qubit tests have been added to cover cases when the mapped operator is different + for various mapping schemes. [(#5873)](https://github.com/PennyLaneAI/pennylane/pull/5873) -

Decompositions and global phase

- -* `MultiControlledX` can now be decomposed even when no `work_wires` are provided. The implementation returns $\mathcal{O}(\text{len(control\_wires)}^2)$ operations, and is applicable for any multi controlled unitary gate. - [(#5735)](https://github.com/PennyLaneAI/pennylane/pull/5735) - -* Single control unitary now includes the correct global phase. - [(#5735)](https://github.com/PennyLaneAI/pennylane/pull/5735) - -* Single control `GlobalPhase` has now a decomposition, i.e. relative phase on control wire. - [(#5735)](https://github.com/PennyLaneAI/pennylane/pull/5735) -

Easier development

-* Logging now allows for an easier opt-in across the stack, and also extends control support to `catalyst`. +* Logging now allows for an easier opt-in across the stack and support has been extended to Catalyst. [(#5528)](https://github.com/PennyLaneAI/pennylane/pull/5528) -* Add support for 3 new pytest markers: `unit`, `integration` and `system`. +* Three new Pytest markers have been added for easier management of our test suite: `unit`, `integration` + and `system`. [(#5517)](https://github.com/PennyLaneAI/pennylane/pull/5517)

Other improvements

-* `expectation_value` was added to `qml.math` to calculate the expectation value of a matrix for pure states. +* `qml.MultiControlledX` can now be decomposed even when no `work_wires` are provided. The implementation + returns :math:`\mathcal{O}(\mbox{len(control wires)}^2)` operations and is applicable for any multi-controlled + unitary gate. This decomposition is provided in [arXiv:quant-ph/9503016](https://arxiv.org/abs/quant-ph/9503016). + [(#5735)](https://github.com/PennyLaneAI/pennylane/pull/5735) + +* A new function called `expectation_value` has been added to `qml.math` to calculate the expectation + value of a matrix for pure states. [(#4484)](https://github.com/PennyLaneAI/pennylane/pull/4484) ```pycon @@ -342,82 +582,95 @@ [(#5667)](https://github.com/PennyLaneAI/pennylane/pull/5667) * `qml.TrotterProduct` is now compatible with resource tracking by inheriting from `ResourcesOperation`. - [(#5680)](https://github.com/PennyLaneAI/pennylane/pull/5680) + [(#5680)](https://github.com/PennyLaneAI/pennylane/pull/5680) -* Added `packaging` in the required list of packages. +* `packaging` is now a required package in PennyLane. [(#5769)](https://github.com/PennyLaneAI/pennylane/pull/5769) -* `ctrl` now works with tuple-valued `control_values` when applied to any already controlled operation. +* `qml.ctrl` now works with tuple-valued `control_values` when applied to any already controlled operation. [(#5725)](https://github.com/PennyLaneAI/pennylane/pull/5725) -* The sorting order of parameter-shift terms is now guaranteed to resolve ties in the absolute value with the sign of the shifts. +* The sorting order of parameter-shift terms is now guaranteed to resolve ties in the absolute value + with the sign of the shifts. [(#5582)](https://github.com/PennyLaneAI/pennylane/pull/5582) -* `qml.transforms.split_non_commuting` can now handle circuits containing measurements of multi-term observables. +* `qml.transforms.split_non_commuting` can now handle circuits containing measurements of multi-term + observables. [(#5729)](https://github.com/PennyLaneAI/pennylane/pull/5729) [(#5838)](https://github.com/PennyLaneAI/pennylane/pull/5838) [(#5828)](https://github.com/PennyLaneAI/pennylane/pull/5828) [(#5869)](https://github.com/PennyLaneAI/pennylane/pull/5869) [(#5939)](https://github.com/PennyLaneAI/pennylane/pull/5939) + [(#5945)](https://github.com/PennyLaneAI/pennylane/pull/5945) * `qml.devices.LegacyDevice` is now an alias for `qml.Device`, so it is easier to distinguish it from `qml.devices.Device`, which follows the new device API. [(#5581)](https://github.com/PennyLaneAI/pennylane/pull/5581) -* The `dtype` for `eigvals` of `X`, `Y`, `Z` and `Hadamard` is changed from `int` to `float`, making them - consistent with the other observables. The `dtype` of the returned values when sampling these observables - (e.g. `qml.sample(X(0))`) is also changed to `float`. +* The `dtype` for `eigvals` of `X`, `Y`, `Z` and `Hadamard` is changed from `int` to `float`, making + them consistent with the other observables. The `dtype` of the returned values when sampling these + observables (e.g. `qml.sample(X(0))`) is also changed to `float`. [(#5607)](https://github.com/PennyLaneAI/pennylane/pull/5607) -* Sets up the framework for the development of an `assert_equal` function for testing operator comparison. +* The framework for the development of an `assert_equal` function for testing operator comparison has been set up. [(#5634)](https://github.com/PennyLaneAI/pennylane/pull/5634) [(#5858)](https://github.com/PennyLaneAI/pennylane/pull/5858) -* The `decompose` transform has an `error` kwarg to specify the type of error that should be raised, - allowing error types to be more consistent with the context the `decompose` function is used in. +* The `decompose` transform has an `error` keyword argument to specify the type of error that should + be raised, allowing error types to be more consistent with the context the `decompose` function is + used in. [(#5669)](https://github.com/PennyLaneAI/pennylane/pull/5669) * Empty initialization of `PauliVSpace` is permitted. [(#5675)](https://github.com/PennyLaneAI/pennylane/pull/5675) -* `QuantumScript` properties are only calculated when needed, instead of on initialization. This decreases the classical overhead by >20%. - `par_info`, `obs_sharing_wires`, and `obs_sharing_wires_id` are now public attributes. +* `qml.tape.QuantumScript` properties are only calculated when needed, instead of on initialization. + This decreases the classical overhead by over 20%. Also, `par_info`, `obs_sharing_wires`, and `obs_sharing_wires_id` + are now public attributes. [(#5696)](https://github.com/PennyLaneAI/pennylane/pull/5696) * The `qml.data` module now supports PyTree data types as dataset attributes [(#5732)](https://github.com/PennyLaneAI/pennylane/pull/5732) -* `qml.ops.Conditional` now inherits from `qml.ops.SymbolicOp`, thus it inherits several useful common functionalities. Other properties such as adjoint and diagonalizing gates have been added using the `base` properties. +* `qml.ops.Conditional` now inherits from `qml.ops.SymbolicOp`, thus it inherits several useful common + functionalities. Other properties such as adjoint and diagonalizing gates have been added using the + `base` properties. [(##5772)](https://github.com/PennyLaneAI/pennylane/pull/5772) -* New dispatches for `qml.ops.Conditional` and `qml.MeasurementValue` have been added to `qml.equal`. - [(##5772)](https://github.com/PennyLaneAI/pennylane/pull/5772) +

Quantum chemistry

-* The `qml.snapshots` transform now supports arbitrary devices by running a separate tape for each snapshot for unsupported devices. +* The `qml.snapshots` transform now supports arbitrary devices by running a separate tape for each snapshot + for unsupported devices. [(#5805)](https://github.com/PennyLaneAI/pennylane/pull/5805) * The `qml.Snapshot` operator now accepts sample-based measurements for finite-shot devices. [(#5805)](https://github.com/PennyLaneAI/pennylane/pull/5805) -* Device preprocess transforms now happen inside the ml boundary. +* Device preprocess transforms now happen inside the ML boundary. [(#5791)](https://github.com/PennyLaneAI/pennylane/pull/5791) -* Transforms applied to callables now use `functools.wraps` to preserve the docstring and call signature of the original function. +* Transforms applied to callables now use `functools.wraps` to preserve the docstring and call signature + of the original function. [(#5857)](https://github.com/PennyLaneAI/pennylane/pull/5857) -* `qml.qsvt()` now supports jax arrays with angle conversions. +* `qml.qsvt()` now supports JAX arrays with angle conversions. [(#5853)](https://github.com/PennyLaneAI/pennylane/pull/5853) +* The sorting order of parameter-shift terms is now guaranteed to resolve ties in the absolute value with the sign of the shifts. + [(#5583)](https://github.com/PennyLaneAI/pennylane/pull/5583) +

Breaking changes 💔

-* Passing `shots` as a keyword argument to a `QNode` initialization now raises an error, instead of ignoring the input. +* Passing `shots` as a keyword argument to a `QNode` initialization now raises an error instead of ignoring + the input. [(#5748)](https://github.com/PennyLaneAI/pennylane/pull/5748) -* A custom decomposition can no longer be provided to `QDrift`. Instead, apply the operations in your custom - operation directly with `qml.apply`. +* A custom decomposition can no longer be provided to `qml.QDrift`. Instead, apply the operations in + your custom operation directly with `qml.apply`. [(#5698)](https://github.com/PennyLaneAI/pennylane/pull/5698) -* Sampling observables composed of `X`, `Y`, `Z` and `Hadamard` now returns values of type `float` instead of `int`. +* Sampling observables composed of `X`, `Y`, `Z` and `Hadamard` now returns values of type `float` instead + of `int`. [(#5607)](https://github.com/PennyLaneAI/pennylane/pull/5607) * `qml.is_commuting` no longer accepts the `wire_map` argument, which does not bring any functionality. @@ -439,23 +692,28 @@

Deprecations 👋

-* The `simplify` argument in `qml.Hamiltonian` and `qml.ops.LinearCombination` is deprecated. +* The `simplify` argument in `qml.Hamiltonian` and `qml.ops.LinearCombination` has been deprecated. Instead, `qml.simplify()` can be called on the constructed operator. [(#5677)](https://github.com/PennyLaneAI/pennylane/pull/5677) -* `qml.transforms.map_batch_transform` is deprecated, since a transform can be applied directly to a batch of tapes. +* `qml.transforms.map_batch_transform` has been deprecated, since a transform can be applied directly to a batch of tapes. [(#5676)](https://github.com/PennyLaneAI/pennylane/pull/5676) -* The default behaviour of `qml.from_qasm()` to remove measurements in the QASM code is deprecated. Use `measurements=[]` to keep this behaviour or `measurements=None` to keep the measurements from the QASM code. +* The default behaviour of `qml.from_qasm()` to remove measurements in the QASM code has been deprecated. + Use `measurements=[]` to keep this behaviour or `measurements=None` to keep the measurements from the QASM code. [(#5882)](https://github.com/PennyLaneAI/pennylane/pull/5882) [(#5904)](https://github.com/PennyLaneAI/pennylane/pull/5904)

Documentation 📝

+* The `qml.qchem` docs have been updated to showcase the new improvements. + [(#5758)](https://github.com/PennyLaneAI/pennylane/pull/5758/) + [(#5638)](https://github.com/PennyLaneAI/pennylane/pull/5638/) + * Several links to other functions in measurement process docstrings have been fixed. [(#5913)](https://github.com/PennyLaneAI/pennylane/pull/5913) -* Move information about mid-circuit measurements from the measurements quickstart page to its own +* Information about mid-circuit measurements has been moved from the measurements quickstart page to its own [mid-circuit measurements quickstart page](https://docs.pennylane.ai/en/stable/introduction/mid_circuit_measurements.html) [(#5870)](https://github.com/PennyLaneAI/pennylane/pull/5870) @@ -465,44 +723,55 @@ * A small typo was fixed in the docstring for `qml.sample`. [(#5685)](https://github.com/PennyLaneAI/pennylane/pull/5685) -* Typesetting for some of the documentation was fixed, (use of left/right delimiters, fractions, and fix of incorrectly set up commands) +* Typesetting for some of the documentation was fixed, (use of left/right delimiters, fractions, and fixing incorrectly set up commands) [(#5804)](https://github.com/PennyLaneAI/pennylane/pull/5804) -* The `qml.Tracker` examples are updated. +* The `qml.Tracker` examples have been updated. [(#5803)](https://github.com/PennyLaneAI/pennylane/pull/5803) -* The input types for `coupling_map` in `qml.transpile` are updated to reflect all the allowed input types by `nx.to_networkx_graph`. +* The input types for `coupling_map` in `qml.transpile` have been updated to reflect all the allowed + input types by `nx.to_networkx_graph`. [(#5864)](https://github.com/PennyLaneAI/pennylane/pull/5864) -* The text in the `qml.data` module and datasets quickstart have been slightly modified to lead to the quickstart first and highlight `list_datasets`. +* The text in the `qml.data` module and datasets quickstart has been slightly modified to lead to the + quickstart first and highlight `list_datasets`. [(5484)](https://github.com/PennyLaneAI/pennylane/pull/5484)

Bug fixes 🐛

+* `qml.compiler.active` first checks whether Catalyst is imported at all to avoid changing `jax_enable_x64` on module initialization. + [(#5960)](https://github.com/PennyLaneAI/pennylane/pull/5960) + +* The `__invert__` dunder method of the `MeasurementValue` class uses an array-valued function. + [(#5955)](https://github.com/PennyLaneAI/pennylane/pull/5955) + +* Skip `Projector`-measurement tests on devices that do not support it. + [(#5951)](https://github.com/PennyLaneAI/pennylane/pull/5951) + * The `default.tensor` device now preserves the order of wires if the initial MPS is created from a dense state vector. [(#5892)](https://github.com/PennyLaneAI/pennylane/pull/5892) -* Fixes a bug where `hadamard_grad` returned a wrong shape for `qml.probs()` without wires. +* Fixed a bug where `hadamard_grad` returned a wrong shape for `qml.probs()` without wires. [(#5860)](https://github.com/PennyLaneAI/pennylane/pull/5860) * An error is now raised on processing an `AnnotatedQueue` into a `QuantumScript` if the queue contains something other than an `Operator`, `MeasurementProcess`, or `QuantumScript`. [(#5866)](https://github.com/PennyLaneAI/pennylane/pull/5866) -* Fixes a bug in the wire handling on special controlled ops. +* Fixed a bug in the wire handling on special controlled ops. [(#5856)](https://github.com/PennyLaneAI/pennylane/pull/5856) -* Fixes a bug where `Sum`'s with repeated identical operations ended up with the same hash as +* Fixed a bug where `Sum`'s with repeated identical operations ended up with the same hash as `Sum`'s with different numbers of repeats. [(#5851)](https://github.com/PennyLaneAI/pennylane/pull/5851) * `qml.qaoa.cost_layer` and `qml.qaoa.mixer_layer` can now be used with `Sum` operators. [(#5846)](https://github.com/PennyLaneAI/pennylane/pull/5846) -* Fixes a bug where `MottonenStatePreparation` produces wrong derivatives at special parameter values. +* Fixed a bug where `qml.MottonenStatePreparation` produces wrong derivatives at special parameter values. [(#5774)](https://github.com/PennyLaneAI/pennylane/pull/5774) -* Fixes a bug where fractional powers and adjoints of operators were commuted, which is +* Fixed a bug where fractional powers and adjoints of operators were commuted, which is not well-defined/correct in general. Adjoints of fractional powers can no longer be evaluated. [(#5835)](https://github.com/PennyLaneAI/pennylane/pull/5835) @@ -515,7 +784,7 @@ * `qml.qnn.KerasLayer` and `qml.qnn.TorchLayer` no longer mutate the input `qml.QNode`'s interface. [(#5800)](https://github.com/PennyLaneAI/pennylane/pull/5800) -* Disable Docker builds on PR merge. +* Docker builds on PR merging has been disabled. [(#5777)](https://github.com/PennyLaneAI/pennylane/pull/5777) * The validation of the adjoint method in `DefaultQubit` correctly handles device wires now. @@ -524,7 +793,7 @@ * `QuantumPhaseEstimation.map_wires` on longer modifies the original operation instance. [(#5698)](https://github.com/PennyLaneAI/pennylane/pull/5698) -* The decomposition of `AmplitudeAmplification` now correctly queues all operations. +* The decomposition of `qml.AmplitudeAmplification` now correctly queues all operations. [(#5698)](https://github.com/PennyLaneAI/pennylane/pull/5698) * Replaced `semantic_version` with `packaging.version.Version`, since the former cannot @@ -543,20 +812,21 @@ * The legacy `Tensor` class can now handle a `Projector` with abstract tracer input. [(#5720)](https://github.com/PennyLaneAI/pennylane/pull/5720) -* Fixed a bug that raised an error regarding expected vs actual `dtype` when using `JAX-JIT` on a circuit that +* Fixed a bug that raised an error regarding expected versus actual `dtype` when using `JAX-JIT` on a circuit that returned samples of observables containing the `qml.Identity` operator. [(#5607)](https://github.com/PennyLaneAI/pennylane/pull/5607) * The signature of `CaptureMeta` objects (like `Operator`) now match the signature of the `__init__` call. [(#5727)](https://github.com/PennyLaneAI/pennylane/pull/5727) -* Use vanilla NumPy arrays in `test_projector_expectation` to avoid differentiating `qml.Projector` with respect to the state attribute. +* Vanilla NumPy arrays are now used in `test_projector_expectation` to avoid differentiating `qml.Projector` + with respect to the state attribute. [(#5683)](https://github.com/PennyLaneAI/pennylane/pull/5683) -* `qml.Projector` is now compatible with jax-jit. +* `qml.Projector` is now compatible with `jax.jit`. [(#5595)](https://github.com/PennyLaneAI/pennylane/pull/5595) -* Finite shot circuits with a `qml.probs` measurement, both with a `wires` or `op` argument, can now be compiled with `jax.jit`. +* Finite-shot circuits with a `qml.probs` measurement, both with a `wires` or `op` argument, can now be compiled with `jax.jit`. [(#5619)](https://github.com/PennyLaneAI/pennylane/pull/5619) * `param_shift`, `finite_diff`, `compile`, `insert`, `merge_rotations`, and `transpile` now @@ -564,47 +834,52 @@ [(#5424)](https://github.com/PennyLaneAI/pennylane/pull/5424) [(#5681)](https://github.com/PennyLaneAI/pennylane/pull/5681) -* A correction is added to `bravyi_kitaev` to call the correct function for a FermiSentence input. +* A correction has been added to `qml.bravyi_kitaev` to call the correct function for a `qml.FermiSentence` input. [(#5671)](https://github.com/PennyLaneAI/pennylane/pull/5671) -* Fixes a bug where `sum_expand` produces incorrect result dimensions when combining shot vectors, +* Fixed a bug where `sum_expand` produces incorrect result dimensions when combined with shot vectors, multiple measurements, and parameter broadcasting. [(#5702)](https://github.com/PennyLaneAI/pennylane/pull/5702) -* Fixes a bug in `qml.math.dot` that raises an error when only one of the operands is a scalar. +* Fixed a bug in `qml.math.dot` that raises an error when only one of the operands is a scalar. [(#5702)](https://github.com/PennyLaneAI/pennylane/pull/5702) -* `qml.matrix` is now compatible with qnodes compiled by catalyst.qjit. +* `qml.matrix` is now compatible with QNodes compiled by `qml.qjit`. [(#5753)](https://github.com/PennyLaneAI/pennylane/pull/5753) -* `qml.snapshots` raises an error when a measurement other than `qml.state` is requested from `default.qubit.legacy` instead of silently returning the statevector. +* `qml.snapshots` raises an error when a measurement other than `qml.state` is requested from `default.qubit.legacy` + instead of silently returning the statevector. [(#5805)](https://github.com/PennyLaneAI/pennylane/pull/5805) -* Fixes a bug where `default.qutrit` is falsely determined to be natively compatible with `qml.snapshots`. +* Fixed a bug where `default.qutrit` was falsely determined to be natively compatible with `qml.snapshots`. [(#5805)](https://github.com/PennyLaneAI/pennylane/pull/5805) -* Fixes a bug where the measurement of a `qml.Snapshot` instance is not passed on during the `qml.adjoint` and `qml.ctrl` operations. +* Fixed a bug where the measurement of a `qml.Snapshot` instance was not passed on during the `qml.adjoint` and `qml.ctrl` operations. [(#5805)](https://github.com/PennyLaneAI/pennylane/pull/5805) -* `CNOT` and `Toffoli` now have an `arithmetic_depth` of `1`, as they are controlled operations. +* `qml.CNOT` and `qml.Toffoli` now have an `arithmetic_depth` of `1`, as they are controlled operations. [(#5797)](https://github.com/PennyLaneAI/pennylane/pull/5797) -* Fixes a bug where the gradient of `ControlledSequence`, `Reflection`, `AmplitudeAmplification`, and `Qubitization` is incorrect on `default.qubit.legacy` with `parameter_shift`. +* Fixed a bug where the gradient of `ControlledSequence`, `Reflection`, `AmplitudeAmplification`, and `Qubitization` was incorrect on `default.qubit.legacy` with `parameter_shift`. [(#5806)](https://github.com/PennyLaneAI/pennylane/pull/5806) -* Fixed a bug where `split_non_commuting` raises an error when the circuit contains measurements of observables that are not pauli words. +* Fixed a bug where `split_non_commuting` raises an error when the circuit contains measurements of + observables that are not Pauli words. [(#5827)](https://github.com/PennyLaneAI/pennylane/pull/5827) -* Simplify method for `Exp` now returns an operator with the correct number of Trotter steps, i.e. equal to the one from the pre-simplified operator. +* The `simplify` method for `qml.Exp` now returns an operator with the correct number of Trotter steps, + i.e. equal to the one from the pre-simplified operator. [(#5831)](https://github.com/PennyLaneAI/pennylane/pull/5831) -* Fix bug where `CompositeOp.overlapping_ops` sometimes puts overlapping ops in different groups, leading to incorrect results returned by `LinearCombination.eigvals()` +* Fixed a bug where `CompositeOp.overlapping_ops` would put overlapping operators in different groups, + leading to incorrect results returned by `LinearCombination.eigvals()`. [(#5847)](https://github.com/PennyLaneAI/pennylane/pull/5847) -* Implement the correct decomposition for a `qml.PauliRot` with an identity as `pauli_word`, i.e. returns a `qml.GlobalPhase` with half the angle. +* The correct decomposition for a `qml.PauliRot` with an identity as `pauli_word` has been implemented, + i.e. returns a `qml.GlobalPhase` with half the angle. [(#5875)](https://github.com/PennyLaneAI/pennylane/pull/5875) -* `qml.pauli_decompose` now works in a jit-ted context, such as `jax.jit` and `catalyst.qjit`. +* `qml.pauli_decompose` now works in a jit-ted context, such as `jax.jit` and `qml.qjit`. [(#5878)](https://github.com/PennyLaneAI/pennylane/pull/5878)

Contributors ✍️

@@ -617,6 +892,7 @@ Utkarsh Azad, Lillian M. A. Frederiksen, Ludmila Botelho, Gabriel Bottrill, +Thomas Bromley, Jack Brown, Astral Cai, Ahmed Darwish, diff --git a/pennylane/boolean_fn.py b/pennylane/boolean_fn.py index 45b793b4290..a29fcbe35f0 100644 --- a/pennylane/boolean_fn.py +++ b/pennylane/boolean_fn.py @@ -22,7 +22,7 @@ # pylint: disable=unnecessary-lambda class BooleanFn: r"""Wrapper for simple callables with Boolean output that can be - manipulated and combined with bit-wise operators. + manipulated and combined with bitwise operators. Args: fn (callable): Function to be wrapped. It can accept any number @@ -30,34 +30,43 @@ class BooleanFn: **Example** - Consider functions that filter numbers to lie in a certain domain. + Consider functions that filter numbers to lie within a certain domain. We may wrap them using ``BooleanFn``: - >>> bigger_than_4 = qml.BooleanFn(lambda x: x > 4) - >>> smaller_than_10 = qml.BooleanFn(lambda x: x < 10) - >>> is_int = qml.BooleanFn(lambda x: isinstance(x, int)) + .. code-block:: python + + bigger_than_4 = qml.BooleanFn(lambda x: x > 4) + smaller_than_10 = qml.BooleanFn(lambda x: x < 10) + is_int = qml.BooleanFn(lambda x: isinstance(x, int)) + >>> bigger_than_4(5.2) True + >>> smaller_than_10(20.1) False + >>> is_int(2.3) False These can then be combined into a single callable using boolean operators, - such as ``&``, logical and: + such as ``&`` (logical and): >>> between_4_and_10 = bigger_than_4 & smaller_than_10 >>> between_4_and_10(-3.2) False + >>> between_4_and_10(9.9) True + >>> between_4_and_10(19.7) False - Other supported operators are ``|``, logical or, and ``~``, logical not: + Other supported operators are ``|`` (logical or) and ``~`` (logical not): + + .. code-block:: python - >>> smaller_equal_than_4 = ~bigger_than_4 - >>> smaller_than_10_or_int = smaller_than_10 | is_int + smaller_equal_than_4 = ~bigger_than_4 + smaller_than_10_or_int = smaller_than_10 | is_int .. warning:: @@ -71,8 +80,10 @@ class BooleanFn: >>> has_bit_length_3 = qml.BooleanFn(lambda x: x.bit_length()==3) >>> (is_int & has_bit_length_3)(4) True + >>> (is_int & has_bit_length_3)(2.3) False + >>> (has_bit_length_3 & is_int)(2.3) AttributeError: 'float' object has no attribute 'bit_length' @@ -103,24 +114,24 @@ def __repr__(self): @property def bitwise(self): - """Determine whether wrapped callable performs a bit-wise operation or not. + """Determine whether the wrapped callable performs a bitwise operation or not. This checks for the ``operands`` attribute that should be defined by it.""" return bool(getattr(self, "operands", tuple())) @property def conditional(self): - """Determine whether wrapped callable is for a conditional or not. + """Determine whether the wrapped callable is for a conditional or not. This checks for the ``condition`` attribute that should be defined by it.""" return bool(getattr(self, "condition", None)) class And(BooleanFn): - """Developer facing class for implemeting bit-wise ``AND`` for callables + """Developer facing class for implemeting bitwise ``AND`` for callables wrapped up with :class:`BooleanFn `. Args: - left (~.BooleanFn): Left operand in the bit-wise expression. - right (~.BooleanFn): Right operand in the bit-wise expression. + left (~.BooleanFn): Left operand in the bitwise expression. + right (~.BooleanFn): Right operand in the bitwise expression. """ def __init__(self, left, right): @@ -139,12 +150,12 @@ def __str__(self): class Or(BooleanFn): - """Developer facing class for implemeting bit-wise ``OR`` for callables + """Developer facing class for implemeting bitwise ``OR`` for callables wrapped up with :class:`BooleanFn `. Args: - left (~.BooleanFn): Left operand in the bit-wise expression. - right (~.BooleanFn): Right operand in the bit-wise expression. + left (~.BooleanFn): Left operand in the bitwise expression. + right (~.BooleanFn): Right operand in the bitwise expression. """ def __init__(self, left, right): @@ -163,12 +174,12 @@ def __str__(self): class Xor(BooleanFn): - """Developer facing class for implemeting bit-wise ``XOR`` for callables + """Developer facing class for implemeting bitwise ``XOR`` for callables wrapped up with :class:`BooleanFn `. Args: - left (~.BooleanFn): Left operand in the bit-wise expression. - right (~.BooleanFn): Right operand in the bit-wise expression. + left (~.BooleanFn): Left operand in the bitwise expression. + right (~.BooleanFn): Right operand in the bitwise expression. """ def __init__(self, left, right): @@ -187,11 +198,11 @@ def __str__(self): class Not(BooleanFn): - """Developer facing class for implemeting bit-wise ``NOT`` for callables + """Developer facing class for implemeting bitwise ``NOT`` for callables wrapped up with :class:`BooleanFn `. Args: - left (~.BooleanFn): Left operand in the bit-wise expression. + left (~.BooleanFn): Left operand in the bitwise expression. """ def __init__(self, left): diff --git a/pennylane/compiler/compiler.py b/pennylane/compiler/compiler.py index 95a08bf1be8..74b0e45b5d7 100644 --- a/pennylane/compiler/compiler.py +++ b/pennylane/compiler/compiler.py @@ -15,6 +15,7 @@ import dataclasses import re +import sys from collections import defaultdict from importlib import metadata, reload from sys import version_info @@ -206,6 +207,8 @@ def circuit(phi, theta): """ for name, eps in AvailableCompilers.names_entrypoints.items(): + if name not in sys.modules: + continue tracer_loader = eps["context"].load() if tracer_loader.is_tracing(): return name @@ -245,5 +248,4 @@ def circuit(phi, theta): >>> qml.qjit(circuit)(np.pi, np.pi / 2) -1.0 """ - return active_compiler() is not None diff --git a/pennylane/debugging/debugger.py b/pennylane/debugging/debugger.py index fdca7196994..a3ce35dd682 100644 --- a/pennylane/debugging/debugger.py +++ b/pennylane/debugging/debugger.py @@ -43,7 +43,7 @@ class PLDB(pdb.Pdb): def __init__(self, *args, **kwargs): """Initialize the debugger, and set custom prompt string.""" super().__init__(*args, **kwargs) - self.prompt = "[pldb]: " + self.prompt = "[pldb] " @classmethod def valid_context(cls): @@ -55,10 +55,10 @@ def valid_context(cls): """ if not qml.queuing.QueuingManager.recording() or not cls.has_active_dev(): - raise RuntimeError("Can't call breakpoint outside of a qnode execution") + raise RuntimeError("Can't call breakpoint outside of a qnode execution.") if cls.get_active_device().name not in ("default.qubit", "lightning.qubit"): - raise TypeError("Breakpoints not supported on this device") + raise TypeError("Breakpoints not supported on this device.") @classmethod def add_device(cls, dev): @@ -136,8 +136,10 @@ def breakpoint(): This function marks a location in a quantum circuit (QNode). When it is encountered during execution of the quantum circuit, an interactive debugging prompt is launched to step - throught the circuit execution using Pdb like commands (:code:`list`, :code:`next`, - :code:`continue`, :code:`quit`). + through the circuit execution. Since it is based on the `Python Debugger `_ (PDB), commands like + (:code:`list`, :code:`next`, :code:`continue`, :code:`quit`) can be used to navigate the code. + + .. seealso:: :doc:`/code/qml_debugging` .. seealso:: :doc:`/code/qml_debugging` @@ -146,6 +148,9 @@ def breakpoint(): Consider the following python script containing the quantum circuit with breakpoints. .. code-block:: python3 + :linenos: + + import pennylane as qml dev = qml.device("default.qubit", wires=2) @@ -163,21 +168,21 @@ def circuit(x): circuit(1.23) - Running the above python script opens up the interactive :code:`[pldb]:` prompt in the terminal. + Running the above python script opens up the interactive :code:`[pldb]` prompt in the terminal. The prompt specifies the path to the script along with the next line to be executed after the breakpoint. .. code-block:: console - > /Users/your/path/to/script.py(8)circuit() + > /Users/your/path/to/script.py(9)circuit() -> qml.RX(x, wires=0) - [pldb]: + [pldb] We can interact with the prompt using the commands: :code:`list` , :code:`next`, :code:`continue`, and :code:`quit`. Additionally, we can also access any variables defined in the function. .. code-block:: console - [pldb]: x + [pldb] x 1.23 The :code:`list` command will print a section of code around the breakpoint, highlighting the next line @@ -185,28 +190,28 @@ def circuit(x): .. code-block:: console - [pldb]: list - 3 - 4 @qml.qnode(dev) - 5 def circuit(x): - 6 qml.breakpoint() - 7 - 8 -> qml.RX(x, wires=0) - 9 qml.Hadamard(wires=1) - 10 - 11 qml.breakpoint() - 12 - 13 qml.CNOT(wires=[0, 1]) - [pldb]: + [pldb] list + 5 @qml.qnode(dev) + 6 def circuit(x): + 7 qml.breakpoint() + 8 + 9 -> qml.RX(x, wires=0) + 10 qml.Hadamard(wires=1) + 11 + 12 qml.breakpoint() + 13 + 14 qml.CNOT(wires=[0, 1]) + 15 return qml.expval(qml.Z(0)) + [pldb] The :code:`next` command will execute the next line of code, and print the new line to be executed. .. code-block:: console - [pldb]: next - > /Users/your/path/to/script.py(9)circuit() + [pldb] next + > /Users/your/path/to/script.py(10)circuit() -> qml.Hadamard(wires=1) - [pldb]: + [pldb] The :code:`continue` command will resume code execution until another breakpoint is reached. It will then print the new line to be executed. Finally, :code:`quit` will resume execution of the file and @@ -214,10 +219,10 @@ def circuit(x): .. code-block:: console - [pldb]: continue - > /Users/your/path/to/script.py(13)circuit() + [pldb] continue + > /Users/your/path/to/script.py(14)circuit() -> qml.CNOT(wires=[0, 1]) - [pldb]: quit + [pldb] quit """ PLDB.valid_context() # Ensure its being executed in a valid context @@ -255,12 +260,12 @@ def circuit(x): circuit(1.23) - Running the above python script opens up the interactive :code:`[pldb]:` prompt in the terminal. + Running the above python script opens up the interactive :code:`[pldb]` prompt in the terminal. We can query the state: .. code-block:: console - [pldb]: longlist + [pldb] longlist 4 @qml.qnode(dev) 5 def circuit(x): 6 qml.RX(x, wires=0) @@ -270,7 +275,7 @@ def circuit(x): 10 11 -> qml.CNOT(wires=[0, 1]) 12 return qml.expval(qml.Z(0)) - [pldb]: qml.debug_state() + [pldb] qml.debug_state() array([0.57754604+0.j , 0.57754604+0.j , 0. -0.40797128j, 0. -0.40797128j]) @@ -315,12 +320,12 @@ def circuit(x): circuit(1.23) - Running the above python script opens up the interactive :code:`[pldb]:` prompt in the terminal. + Running the above python script opens up the interactive :code:`[pldb]` prompt in the terminal. We can query the expectation value: .. code-block:: console - [pldb]: longlist + [pldb] longlist 4 @qml.qnode(dev) 5 def circuit(x): 6 qml.RX(x, wires=0) @@ -330,7 +335,7 @@ def circuit(x): 10 11 -> qml.CNOT(wires=[0, 1]) 12 return qml.state() - [pldb]: qml.debug_expval(qml.Z(0)) + [pldb] qml.debug_expval(qml.Z(0)) 0.33423772712450256 """ @@ -379,12 +384,12 @@ def circuit(x): circuit(1.23) - Running the above python script opens up the interactive :code:`[pldb]:` prompt in the terminal. + Running the above python script opens up the interactive :code:`[pldb]` prompt in the terminal. We can query the probability distribution: .. code-block:: console - [pldb]: longlist + [pldb] longlist 4 @qml.qnode(dev) 5 def circuit(x): 6 qml.RX(x, wires=0) @@ -394,7 +399,7 @@ def circuit(x): 10 11 -> qml.CNOT(wires=[0, 1]) 12 return qml.state() - [pldb]: qml.debug_probs() + [pldb] qml.debug_probs() array([0.33355943, 0.33355943, 0.16644057, 0.16644057]) """ @@ -457,13 +462,13 @@ def circuit(x): circuit(1.23) - Running the above python script opens up the interactive :code:`[pldb]:` prompt in the terminal. + Running the above python script opens up the interactive :code:`[pldb]` prompt in the terminal. We can access the tape and draw it as follows: .. code-block:: console - [pldb]: t = qml.debug_tape() - [pldb]: print(t.draw()) + [pldb] t = qml.debug_tape() + [pldb] print(t.draw()) 0: ──RX─╭●─┤ 1: ──H──╰X─┤ diff --git a/pennylane/devices/default_tensor.py b/pennylane/devices/default_tensor.py index 99f6a46b17f..9381ac62b49 100644 --- a/pennylane/devices/default_tensor.py +++ b/pennylane/devices/default_tensor.py @@ -204,16 +204,16 @@ class DefaultTensor(Device): The backend uses the ``quimb`` library to perform the tensor network operations, and different methods can be used to simulate the quantum circuit. The supported methods are Matrix Product State (MPS) and Tensor Network (TN). - This device does not currently support finite shots or differentiation. At present, the supported measurement types are expectation values, variances and state measurements. + This device does not currently support finite-shots or differentiation. At present, the supported measurement types are expectation values, variances and state measurements. Finally, ``UserWarnings`` from the ``cotengra`` package may appear when using this device. Args: wires (int, Iterable[Number, str]): Number of wires present on the device, or iterable that - contains unique labels for the wires as numbers (i.e., ``[-1, 0, 2]``) or strings - (``['aux_wire', 'q1', 'q2']``). + contains unique labels for the wires as numbers (e.g., ``[-1, 0, 2]``) or strings + (e.g., ``['aux_wire', 'q1', 'q2']``). method (str): Supported method. The supported methods are ``"mps"`` (Matrix Product State) and ``"tn"`` (Tensor Network). c_dtype (type): Complex data type for the tensor representation. Must be one of ``numpy.complex64`` or ``numpy.complex128``. - **kwargs: keyword arguments for the device, passed to the ``quimb`` backend. + **kwargs: Keyword arguments for the device, passed to the ``quimb`` backend. Keyword Args: max_bond_dim (int): Maximum bond dimension for the MPS method. @@ -269,8 +269,8 @@ def circuit(num_qubits): setting the maximum bond dimension to 100 and the cutoff to the machine epsilon. We set ``"auto-mps"`` as the contraction technique to apply gates. With this option, ``quimb`` turns 3-qubit gates and 4-qubit gates - into Matrix Product Operators (MPO) and applies them directly to the MPS. On the other hand, qubits in 2-qubit gates are possibly - swapped to be adjacent before applying the gate, then swapped back. + into Matrix Product Operators (MPO) and applies them directly to the MPS. On the other hand, qubits involved in 2-qubit gates may be + temporarily swapped to adjacent positions before applying the gate and then returned to their original positions. .. code-block:: python @@ -352,8 +352,9 @@ def circuit(phi, depth, num_qubits): The execution time for this circuit with the above parameters is around 0.8 seconds on a standard laptop. The tensor network method can be faster than MPS and state vector methods in some cases. - As a comparison, the time for the exact calculation of the same circuit with the MPS method and with the ``default.qubit`` - device is about three orders of magnitude slower. + As a comparison, the time for the exact calculation (i.e., with ``max_bond_dim = None``) of the same circuit + using the ``MPS`` method of the ``default.tensor`` device is approximately three orders of magnitude slower. + Similarly, using the ``default.qubit`` device results in a much slower simulation. """ # pylint: disable=too-many-instance-attributes diff --git a/pennylane/devices/tests/test_return_system.py b/pennylane/devices/tests/test_return_system.py index 16d863561ad..ed0aec26652 100644 --- a/pennylane/devices/tests/test_return_system.py +++ b/pennylane/devices/tests/test_return_system.py @@ -39,6 +39,9 @@ def test_multiple_expval(self, device): n_wires = 2 dev = device(n_wires) + if hasattr(dev, "observables") and "Projector" not in dev.observables: + pytest.skip("Skipped because device does not support the Projector observable.") + obs1 = qml.Projector([0], wires=0) obs2 = qml.Z(1) func = qubit_ansatz @@ -59,9 +62,13 @@ def circuit(x): def test_multiple_var(self, device): """Return multiple vars.""" + n_wires = 2 dev = device(n_wires) + if hasattr(dev, "observables") and "Projector" not in dev.observables: + pytest.skip("Skipped because device does not support the Projector observable.") + obs1 = qml.Projector([0], wires=0) obs2 = qml.Z(1) func = qubit_ansatz diff --git a/pennylane/drawer/draw.py b/pennylane/drawer/draw.py index ff6ab2a149d..50e692bc6b7 100644 --- a/pennylane/drawer/draw.py +++ b/pennylane/drawer/draw.py @@ -91,9 +91,9 @@ def draw( .. note:: At most, one of ``level`` or ``expansion_strategy`` needs to be provided. If neither is provided, - ``qnode.expansion_strategy`` would be used instead. Users are encouraged to predominantly use ``level``, - as it allows for the same values as ``expansion_strategy``, and allows for more flexibility choosing - the wanted transforms/expansions. + ``qnode.expansion_strategy`` will be used instead. Users are encouraged to predominantly use ``level``, + as it allows for the same values as ``expansion_strategy`` and offers more flexibility in choosing + the desired transforms/expansions. **Example** @@ -173,24 +173,20 @@ def longer_circuit(params): qml.StronglyEntanglingLayers(params, wires=range(3)) return [qml.expval(qml.Z(i)) for i in range(3)] - print(qml.draw(longer_circuit, max_length=60)(params)) - - .. code-block:: none - - 0: ──Rot(0.77,0.44,0.86)─╭●────╭X──Rot(0.45,0.37,0.93)─╭●─╭X - 1: ──Rot(0.70,0.09,0.98)─╰X─╭●─│───Rot(0.64,0.82,0.44)─│──╰● - 2: ──Rot(0.76,0.79,0.13)────╰X─╰●──Rot(0.23,0.55,0.06)─╰X─── - - ───Rot(0.83,0.63,0.76)──────────────────────╭●────╭X─┤ - ──╭X────────────────────Rot(0.35,0.97,0.89)─╰X─╭●─│──┤ - ──╰●────────────────────Rot(0.78,0.19,0.47)────╰X─╰●─┤ + >>> print(qml.draw(longer_circuit, max_length=60, expansion_strategy="device")(params)) + 0: ──Rot(0.77,0.44,0.86)─╭●────╭X──Rot(0.45,0.37,0.93)─╭●─╭X + 1: ──Rot(0.70,0.09,0.98)─╰X─╭●─│───Rot(0.64,0.82,0.44)─│──╰● + 2: ──Rot(0.76,0.79,0.13)────╰X─╰●──Rot(0.23,0.55,0.06)─╰X─── + ───Rot(0.83,0.63,0.76)──────────────────────╭●────╭X─┤ + ──╭X────────────────────Rot(0.35,0.97,0.89)─╰X─╭●─│──┤ + ──╰●────────────────────Rot(0.78,0.19,0.47)────╰X─╰●─┤ The ``wire_order`` keyword specifies the order of the wires from top to bottom: >>> print(qml.draw(circuit, wire_order=[1,0])(a=2.3, w=[1.2, 3.2, 0.7])) - 1: ────╭RX(2.30)──Rot(1.20,3.20,0.70)─╭RX(-2.30)─┤ ╭ - 0: ──H─╰●─────────────────────────────╰●─────────┤ ╰ + 1: ────╭RX(2.30)──Rot(1.20,3.20,0.70,"arbitrary")─╭RX(-2.30)─┤ ╭ + 0: ──H─╰●─────────────────────────────────────────╰●─────────┤ ╰ If the device or ``wire_order`` has wires not used by operations, those wires are omitted unless requested with ``show_all_wires=True`` @@ -207,6 +203,7 @@ def longer_circuit(params): .. code-block:: python from functools import partial + from pennylane import numpy as pnp @partial(qml.gradients.param_shift, shifts=[(0.1,)]) @qml.qnode(qml.device('default.qubit', wires=1)) @@ -214,13 +211,9 @@ def transformed_circuit(x): qml.RX(x, wires=0) return qml.expval(qml.Z(0)) - print(qml.draw(transformed_circuit)(np.array(1.0, requires_grad=True))) - - .. code-block:: none - - 0: ──RX(1.10)─┤ - - 0: ──RX(0.90)─┤ + >>> print(qml.draw(transformed_circuit)(pnp.array(1.0, requires_grad=True))) + 0: ──RX(1.10)─┤ + 0: ──RX(0.90)─┤ The function also accepts quantum functions rather than QNodes. This can be especially helpful if you want to visualize only a part of a circuit that may not be convertible into @@ -236,7 +229,7 @@ def transformed_circuit(x): **Levels:** The ``level`` keyword argument allows one to select a subset of the transforms to apply on the ``QNode`` - before carrying out any drawing. Take for example this circuit: + before carrying out any drawing. Take, for example, this circuit: .. code-block:: python @@ -271,7 +264,7 @@ def circ(weights, order): 1: ─╰RandomLayers(M0)─├Permute─┤ 2: ───────────────────╰Permute─┤ - To apply all of the transforms, including those carried out by the differentitation method and the device, use ``level=None``: + To apply all of the transforms, including those carried out by the differentiation method and the device, use ``level=None``: >>> print(qml.draw(circ, level=None, show_matrices=False)(weights, order)) 0: ──RY(1.00)──╭SWAP─┤ @@ -446,9 +439,9 @@ def draw_mpl( .. note:: At most, one of ``level`` or ``expansion_strategy`` needs to be provided. If neither is provided, - ``qnode.expansion_strategy`` would be used instead. Users are encouraged to predominantly use ``level``, - as it allows for the same values as ``expansion_strategy``, and allows for more flexibility choosing - the wanted transforms/expansions. + ``qnode.expansion_strategy`` will be used instead. Users are encouraged to predominantly use ``level``, + as it allows for the same values as ``expansion_strategy`` and offers more flexibility in choosing + the desired transforms/expansions. .. warning:: @@ -622,7 +615,7 @@ def circuit2(x, y): **Levels:** The ``level`` keyword argument allows one to select a subset of the transforms to apply on the ``QNode`` - before carrying out any drawing. Take for example this circuit: + before carrying out any drawing. Take, for example, this circuit: .. code-block:: python @@ -655,14 +648,14 @@ def circ(): .. code-block:: python fig, ax = qml.draw_mpl(circ, level="user")() - fog.show() + fig.show() .. figure:: ../../_static/draw_mpl/level_user.png :align: center :width: 60% :target: javascript:void(0); - To apply all of the transforms, including those carried out by the differentitation method and the device, use ``level=None``: + To apply all of the transforms, including those carried out by the differentiation method and the device, use ``level=None``: .. code-block:: python diff --git a/pennylane/gradients/parameter_shift.py b/pennylane/gradients/parameter_shift.py index acdc7946ea0..63152ee3d0f 100644 --- a/pennylane/gradients/parameter_shift.py +++ b/pennylane/gradients/parameter_shift.py @@ -834,7 +834,7 @@ def param_shift( f0 (tensor_like[float] or None): Output of the evaluated input tape. If provided, and the gradient recipe contains an unshifted term, this value is used, saving a quantum evaluation. - broadcast (bool): Whether or not to use parameter broadcasting to create the + broadcast (bool): Whether or not to use parameter broadcasting to create a single broadcasted tape per operation instead of one tape per shift angle. Returns: @@ -905,14 +905,19 @@ def param_shift( This transform can be registered directly as the quantum gradient transform to use during autodifferentiation: - >>> dev = qml.device("default.qubit") - >>> @qml.qnode(dev, interface="autograd", diff_method="parameter-shift") - ... 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.Z(0)) - >>> params = np.array([0.1, 0.2, 0.3], requires_grad=True) + .. code-block:: python + + from pennylane import numpy as pnp + + dev = qml.device("default.qubit") + @qml.qnode(dev, interface="autograd", diff_method="parameter-shift") + 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.Z(0)) + + >>> params = pnp.array([0.1, 0.2, 0.3], requires_grad=True) >>> qml.jacobian(circuit)(params) array([-0.3875172 , -0.18884787, -0.38355704]) @@ -921,14 +926,18 @@ def param_shift( tensor outputs, instead of functions that output sequences. In contrast, Jax and Torch require no additional post-processing. - >>> import jax - >>> dev = qml.device("default.qubit") - >>> @qml.qnode(dev, interface="jax", diff_method="parameter-shift") - ... 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.Z(0)), qml.var(qml.Z(0)) + .. code-block:: python + + import jax + + dev = qml.device("default.qubit") + @qml.qnode(dev, interface="jax", diff_method="parameter-shift") + 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.Z(0)), qml.var(qml.Z(0)) + >>> params = jax.numpy.array([0.1, 0.2, 0.3]) >>> jax.jacobian(circuit)(params) (Array([-0.3875172 , -0.18884787, -0.38355704], dtype=float64), @@ -954,12 +963,10 @@ def param_shift( .. warning:: - Note that using parameter broadcasting via ``broadcast=True`` is not supported for tapes - with multiple return values or for evaluations with shot vectors. - As the option ``broadcast=True`` adds a broadcasting dimension, it is not compatible - with circuits that already are broadcasted. - Finally, operations with trainable parameters are required to support broadcasting. - One way of checking this is the `Attribute` `supports_broadcasting`: + Note that as the option ``broadcast=True`` adds a broadcasting dimension, it is not compatible + with circuits that are already broadcasted. + In addition, operations with trainable parameters are required to support broadcasting. + One way to check this is through the ``supports_broadcasting`` attribute: >>> qml.RX in qml.ops.qubit.attributes.supports_broadcasting True @@ -971,12 +978,15 @@ def param_shift( However, for performance reasons, we recommend providing the gradient transform as the ``diff_method`` argument of the QNode decorator, and differentiating with your preferred machine learning framework. - >>> @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.Z(0)), qml.var(qml.Z(0)) + .. code-block:: python + + @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.Z(0)), qml.var(qml.Z(0)) + >>> qml.gradients.param_shift(circuit)(params) (Array([-0.3875172 , -0.18884787, -0.38355704], dtype=float64), Array([0.69916862, 0.34072424, 0.69202359], dtype=float64)) @@ -1028,15 +1038,18 @@ def param_shift( This gradient transform is compatible with devices that use shot vectors for execution. - >>> shots = (10, 100, 1000) - >>> dev = qml.device("default.qubit", shots=shots) - >>> @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.Z(0)), qml.var(qml.Z(0)) - >>> params = np.array([0.1, 0.2, 0.3], requires_grad=True) + .. code-block:: python + + shots = (10, 100, 1000) + dev = qml.device("default.qubit", shots=shots) + @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.Z(0)), qml.var(qml.Z(0)) + + >>> params = pnp.array([0.1, 0.2, 0.3], requires_grad=True) >>> qml.gradients.param_shift(circuit)(params) ((array([-0.2, -0.1, -0.4]), array([0.4, 0.2, 0.8])), (array([-0.4 , -0.24, -0.43]), array([0.672 , 0.4032, 0.7224])), @@ -1048,7 +1061,7 @@ def param_shift( circuit evaluations for each operation are batched together, resulting in broadcasted tapes: - >>> params = np.array([0.1, 0.2, 0.3], requires_grad=True) + >>> params = pnp.array([0.1, 0.2, 0.3], requires_grad=True) >>> ops = [qml.RX(params[0], 0), qml.RY(params[1], 0), qml.RX(params[2], 0)] >>> measurements = [qml.expval(qml.Z(0))] >>> tape = qml.tape.QuantumTape(ops, measurements) @@ -1067,13 +1080,16 @@ def param_shift( An advantage of using ``broadcast=True`` is a speedup: - >>> import timeit - >>> @qml.qnode(qml.device("default.qubit")) - ... 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.Z(0)) + .. code-block:: python + + import timeit + @qml.qnode(qml.device("default.qubit")) + 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.Z(0)) + >>> number = 100 >>> serial_call = "qml.gradients.param_shift(circuit, broadcast=False)(params)" >>> timeit.timeit(serial_call, globals=globals(), number=number) / number @@ -1087,6 +1103,8 @@ def param_shift( of the circuit, at least a small improvement can be expected in most cases. Note that ``broadcast=True`` requires additional memory by a factor of the largest batch_size of the created tapes. + + Shot vectors and multiple return measurements are supported with ``broadcast=True``. """ transform_name = "parameter-shift rule" diff --git a/pennylane/io.py b/pennylane/io.py index 7f20513876d..dcf6ff6039e 100644 --- a/pennylane/io.py +++ b/pennylane/io.py @@ -414,6 +414,27 @@ def from_qasm(quantum_circuit: str, measurements=False): """Loads quantum circuits from a QASM string using the converter in the PennyLane-Qiskit plugin. + Args: + quantum_circuit (str): a QASM string containing a valid quantum circuit + measurements (None | MeasurementProcess | list[MeasurementProcess]): an optional PennyLane + measurement or list of PennyLane measurements that overrides any terminal measurements + that may be present in the input circuit. If set to ``None``, existing measurements + in the input circuit will be used. + + Returns: + function: the PennyLane template created based on the QASM string + + By default, ``from_qasm`` will remove any measurements that are present in the QASM code. + If the QASM code contains measurements, set ``measurements=None`` to keep them in the + output of ``from_qasm``. + + .. warning:: + + The current default behaviour of removing measurements in the QASM code is deprecated + and will be changed in a future release. Starting in version ``0.38``, ``from_qasm`` + will keep the measurements from the QASM code by default. To remove all measurements, + set ``measurements=[]`` which overrides the existing measurements with an empty list. + **Example:** .. code-block:: python @@ -421,8 +442,11 @@ def from_qasm(quantum_circuit: str, measurements=False): >>> hadamard_qasm = 'OPENQASM 2.0;' \\ ... 'include "qelib1.inc";' \\ ... 'qreg q[1];' \\ + ... 'creg c[1];' \\ ... 'h q[0];' >>> my_circuit = qml.from_qasm(hadamard_qasm) + >>> my_circuit() + [] The measurements can also be passed directly to the function when creating the quantum function, making it possible to create a PennyLane circuit with @@ -433,23 +457,6 @@ def from_qasm(quantum_circuit: str, measurements=False): >>> circuit() [tensor(1., requires_grad=True)] - .. note:: - - The ``measurements`` keyword allows one to add a list of PennyLane measurements - that will **override** any terminal measurements present in the QASM code, - so that they are not performed before the operations specified in ``measurements``. - - By default, ``from_qasm`` will remove any measurements that are present in the QASM code. - If the QASM code contains measurements, set ``measurements=None`` to keep them in the - output of ``from_qasm``. - - .. warning:: - - The current default behaviour of removing measurements in the QASM code is deprecated - and will be changed in a future release. Starting in version ``0.38``, ``from_qasm`` - will keep the measurements from the QASM code by default. To remove all measurements, - set ``measurements=[]`` which overrides the existing measurements with an empty list. - Mid-circuit measurements inside the QASM code can also be interpreted. .. code-block:: python @@ -492,16 +499,6 @@ def circuit(): >>> my_circuit(wires=(1, 0)) >>> return qml.expval(qml.Z(0)) - Args: - quantum_circuit (str): a QASM string containing a valid quantum circuit - measurements (None | MeasurementProcess | list[MeasurementProcess]): an optional PennyLane - measurement or list of PennyLane measurements that overrides any terminal measurements - that may be present in the input circuit. If set to ``None``, existing measurements - in the input circuit will be used. - - Returns: - function: the PennyLane template created based on the QASM string - """ try: plugin_converter = plugin_converters["qasm"].load() diff --git a/pennylane/math/quantum.py b/pennylane/math/quantum.py index f4748ee7743..32069de24d6 100644 --- a/pennylane/math/quantum.py +++ b/pennylane/math/quantum.py @@ -832,9 +832,10 @@ def expectation_value( The expectation value for any operator can obtained by passing their matrix representation as an argument. For example, for a 2 qubit state, we can compute the expectation value of the operator :math:`Z \otimes I` as - - >>> state_vector = [1/np.sqrt(2), 0, 1/np.sqrt(2), 0] - >>> operator_matrix = qml.matrix(qml.PauliZ(0), wire_order=[0,1]) + >>> import pennylane as qml + >>> import numpy as np + >>> state_vector = [1 / np.sqrt(2), 0, 1 / np.sqrt(2), 0] + >>> operator_matrix = qml.matrix(qml.PauliZ(0), wire_order=[0, 1]) >>> qml.math.expectation_value(operator_matrix, state_vector) tensor(-2.23711432e-17+0.j, requires_grad=True) diff --git a/pennylane/measurements/mid_measure.py b/pennylane/measurements/mid_measure.py index e1f087ec53d..a1ef30e61af 100644 --- a/pennylane/measurements/mid_measure.py +++ b/pennylane/measurements/mid_measure.py @@ -419,7 +419,7 @@ def _transform_bin_op(self, base_bin, other): def __invert__(self): """Return a copy of the measurement value with an inverted control value.""" - return self._apply(lambda v: not v) + return self._apply(qml.math.logical_not) def __eq__(self, other): return self._transform_bin_op(lambda a, b: a == b, other) diff --git a/pennylane/noise/conditionals.py b/pennylane/noise/conditionals.py index d3051a08466..790c304f5fe 100644 --- a/pennylane/noise/conditionals.py +++ b/pennylane/noise/conditionals.py @@ -33,7 +33,7 @@ class WiresIn(BooleanFn): """A conditional for evaluating if the wires of an operation exist in a specified set of wires. Args: - wires (Union[Iterable[int, str], Wires]): sequence of wires for building the wire set. + wires (Union[Iterable[int, str], Wires]): Sequence of wires for building the wire set. .. seealso:: Users are advised to use :func:`~.wires_in` for a functional construction. """ @@ -50,7 +50,7 @@ class WiresEq(BooleanFn): """A conditional for evaluating if a given wire is equal to a specified set of wires. Args: - wires (Union[Iterable[int, str], Wires]): sequence of wires for building the wire set. + wires (Union[Iterable[int, str], Wires]): Sequence of wires for building the wire set. .. seealso:: Users are advised to use :func:`~.wires_eq` for a functional construction. """ @@ -90,17 +90,17 @@ def wires_in(wires): if the wires of an input operation are within the specified set of wires. Args: - wires (Union(Iterable[int, str], Wires, Operation, int, str)): object to be used + wires (Union(Iterable[int, str], Wires, Operation, int, str)): Object to be used for building the wire set. Returns: - :class:`WiresIn `: a callable object with + :class:`WiresIn `: A callable object with signature ``Union(Iterable[int, str], Wires, Operation, int, str)``. It evaluates to ``True`` if the wire set constructed from the input to the callable is a subset of the one built from the specified ``wires`` set. Raises: - ValueError: if the wire set cannot be computed from ``wires``. + ValueError: If the wire set cannot be computed from ``wires``. **Example** @@ -109,6 +109,7 @@ def wires_in(wires): >>> cond_func = qml.noise.wires_in([0, 1]) >>> cond_func(qml.X(0)) True + >>> cond_func(qml.X(3)) False @@ -118,6 +119,7 @@ def wires_in(wires): >>> cond_func = qml.noise.wires_in(qml.CNOT(["alice", "bob"])) >>> cond_func("alice") True + >>> cond_func("eve") False """ @@ -129,17 +131,17 @@ def wires_eq(wires): if a given wire is equal to specified set of wires. Args: - wires (Union(Iterable[int, str], Wires, Operation, int, str)): object to be used + wires (Union(Iterable[int, str], Wires, Operation, int, str)): Object to be used for building the wire set. Returns: - :class:`WiresEq `: a callable object with + :class:`WiresEq `: A callable object with signature ``Union(Iterable[int, str], Wires, Operation, int, str)``. It evaluates to ``True`` if the wire set constructed from the input to the callable is equal to the one built from the specified ``wires`` set. Raises: - ValueError: if the wire set cannot be computed from ``wires``. + ValueError: If the wire set cannot be computed from ``wires``. **Example** @@ -148,6 +150,7 @@ def wires_eq(wires): >>> cond_func = qml.noise.wires_eq(0) >>> cond_func(qml.X(0)) True + >>> cond_func(qml.RY(1.23, wires=[3])) False @@ -157,6 +160,7 @@ def wires_eq(wires): >>> cond_func = qml.noise.wires_eq(qml.RX(1.0, "dino")) >>> cond_func(qml.RZ(1.23, wires="dino")) True + >>> cond_func("eve") False """ @@ -167,7 +171,7 @@ class OpIn(BooleanFn): """A conditional for evaluating if a given operation exist in a specified set of operations. Args: - ops (Union[str, class, Operation, list[str, class, Operation]]): sequence of operation + ops (Union[str, class, Operation, list[str, class, Operation]]): Sequence of operation instances, string representations or classes to build the operation set. .. seealso:: Users are advised to use :func:`~.op_in` for a functional construction. @@ -333,15 +337,15 @@ def op_in(ops): if a given operation exist in a specified set of operations. Args: - ops (str, class, Operation, list(Union[str, class, Operation])): sequence of string - representations, instances or classes of the operation(s). + ops (str, class, Operation, list(Union[str, class, Operation])): Sequence of string + representations, instances, or classes of the operation(s). Returns: :class:`OpIn `: A callable object that accepts - an :class:`~.Operation` and gives a boolean output. It accepts any input from: - ``Union(str, class, Operation, list(Union[str, class, Operation]))`` and evaluates - to ``True``, if input operation(s) exist in the set of operation(s) specified by - ``ops``, based on a comparison of the operation type, irrespective of wires. + an :class:`~.Operation` and returns a boolean output. It accepts any input from: + ``Union[str, class, Operation, list(Union[str, class, Operation])]`` and evaluates + to ``True`` if the input operation(s) exists in the set of operation(s) specified by + ``ops``. Comparison is based on the operation's type, irrespective of wires. **Example** @@ -350,8 +354,10 @@ def op_in(ops): >>> cond_func = qml.noise.op_in(["RX", "RY"]) >>> cond_func(qml.RX(1.23, wires=[0])) True + >>> cond_func(qml.RZ(1.23, wires=[3])) False + >>> cond_func([qml.RX(1.23, wires=[1]), qml.RY(4.56, wires=[2])]) True @@ -361,8 +367,10 @@ def op_in(ops): >>> cond_func = qml.noise.op_in([qml.RX(1.0, "dino"), qml.RY(2.0, "rhino")]) >>> cond_func(qml.RX(1.23, wires=["eve"])) True + >>> cond_func(qml.RY(1.23, wires=["dino"])) True + >>> cond_func([qml.RX(1.23, wires=[1]), qml.RZ(4.56, wires=[2])]) False """ @@ -375,14 +383,14 @@ def op_eq(ops): if a given operation is equal to the specified operation. Args: - ops (str, class, Operation): string representation, an instance or class of the operation. + ops (str, class, Operation): String representation, an instance or class of the operation. Returns: :class:`OpEq `: A callable object that accepts - an :class:`~.Operation` and gives a boolean output. It accepts any input from: - ``Union(str, class, Operation)`` and evaluates to ``True``, if input operation(s) - is equal to the set of operation(s) specified by ``ops``, based on a comparison of - the operation type, irrespective of wires. + an :class:`~.Operation` and returns a boolean output. It accepts any input from: + ``Union[str, class, Operation]`` and evaluates to ``True`` if the input operation(s) + is equal to the set of operation(s) specified by ``ops``. Comparison is based on + the operation's type, irrespective of wires. **Example** @@ -391,8 +399,10 @@ def op_eq(ops): >>> cond_func = qml.noise.op_eq("RX") >>> cond_func(qml.RX(1.23, wires=[0])) True + >>> cond_func(qml.RZ(1.23, wires=[3])) False + >>> cond_func("CNOT") False @@ -402,6 +412,7 @@ def op_eq(ops): >>> cond_func = qml.noise.op_eq(qml.RX(1.0, "dino")) >>> cond_func(qml.RX(1.23, wires=["eve"])) True + >>> cond_func(qml.RY(1.23, wires=["dino"])) False """ @@ -423,19 +434,19 @@ def partial_wires(operation, *args, **kwargs): all argument frozen except ``wires``. Args: - operation (Operation, class): instance of an operation or the class + operation (Operation, class): Instance of an operation or the class corresponding to the operation. - *args: positional arguments provided in the case where the keyword argument + *args: Positional arguments provided in the case where the keyword argument ``operation`` is a class for building the partially evaluated instance. - **kwargs: keyword arguments for the building the partially evaluated instance. + **kwargs: Keyword arguments for the building the partially evaluated instance. These will override any arguments present in the operation instance or ``args``. Returns: - callable: a wrapper function that accepts a sequence of wires as an argument or + callable: A wrapper function that accepts a sequence of wires as an argument or any object with a ``wires`` property. Raises: - ValueError: if ``args`` are provided when the given ``operation`` is an instance. + ValueError: If ``args`` are provided when the given ``operation`` is an instance. **Example** diff --git a/pennylane/noise/noise_model.py b/pennylane/noise/noise_model.py index 962101bf6cd..a2b4397c047 100644 --- a/pennylane/noise/noise_model.py +++ b/pennylane/noise/noise_model.py @@ -36,8 +36,8 @@ class NoiseModel: - The ``conditional`` should be either a function decorated with :class:`~.BooleanFn`, a callable object built via :ref:`constructor functions ` in - the ``qml.noise`` module, or their bit-wise combination. - - The definition of ``noise_fn(op, **kwargs)`` should have the operations in same the order + the ``qml.noise`` module, or their bitwise combination. + - The definition of ``noise_fn(op, **kwargs)`` should have the operations in the same the order in which they are to be queued for an operation ``op``, whenever the corresponding ``conditional`` evaluates to ``True``. @@ -73,12 +73,12 @@ def __init__(self, model_map, **kwargs): @property def model_map(self): - """Gives the conditional model for the noise model""" + """Gives the conditional model for the noise model.""" return self._model_map @property def metadata(self): - """Gives the metadata for the noise model""" + """Gives the metadata for the noise model.""" return self._metadata def __add__(self, data): @@ -124,7 +124,7 @@ def __repr__(self): return model_str @staticmethod - def check_model(model): + def check_model(model: dict) -> None: """Method to validate the ``model`` map for constructing a NoiseModel.""" for condition, noise in model.items(): if not isinstance(condition, qml.BooleanFn): diff --git a/pennylane/ops/functions/equal.py b/pennylane/ops/functions/equal.py index 03d803d13a0..549cf0881ab 100644 --- a/pennylane/ops/functions/equal.py +++ b/pennylane/ops/functions/equal.py @@ -192,10 +192,6 @@ def assert_equal( Raises: AssertionError: An ``AssertionError`` is raised if the two operators are not equal. - .. warning:: - - This function is still under developement. - .. seealso:: :func:`~.equal` @@ -204,7 +200,7 @@ def assert_equal( >>> op1 = qml.RX(np.array(0.12), wires=0) >>> op2 = qml.RX(np.array(1.23), wires=0) - >>> qml.assert_equal(op1, op1) + >>> qml.assert_equal(op1, op2) AssertionError: op1 and op2 have different data. Got (array(0.12),) and (array(1.23),) diff --git a/pennylane/ops/op_math/controlled_decompositions.py b/pennylane/ops/op_math/controlled_decompositions.py index a72815a4ad4..17b8405c0a8 100644 --- a/pennylane/ops/op_math/controlled_decompositions.py +++ b/pennylane/ops/op_math/controlled_decompositions.py @@ -158,6 +158,8 @@ def ctrl_decomp_zyz(target_operation: Operator, control_wires: Wires): .. code-block:: python + import pennylane as qml + dev = qml.device("default.qubit", wires=2) @qml.qnode(dev) @@ -190,7 +192,7 @@ def decomp_circuit(op): control_wires = Wires(control_wires) if len(control_wires) > 1: raise ValueError( - f"The control_wires should be a single wire, instead got: {len(control_wires)}-wires" + f"The control_wires should be a single wire, instead got: {len(control_wires)} wires." ) target_wire = target_operation.wires diff --git a/pennylane/ops/op_math/decompositions/single_qubit_unitary.py b/pennylane/ops/op_math/decompositions/single_qubit_unitary.py index 956082b7c4b..787639b79c7 100644 --- a/pennylane/ops/op_math/decompositions/single_qubit_unitary.py +++ b/pennylane/ops/op_math/decompositions/single_qubit_unitary.py @@ -15,8 +15,6 @@ operations into elementary gates. """ -from typing import Tuple - import numpy as np import pennylane as qml @@ -163,7 +161,7 @@ def _rot_decomposition(U, wire, return_global_phase=False): def _get_single_qubit_rot_angles_via_matrix( U, return_global_phase=False -) -> Tuple[float, float, float]: +) -> tuple[float, float, float]: """Returns a triplet of angles representing the single-qubit decomposition of the matrix of the target operation using ZYZ rotations. """ diff --git a/pennylane/ops/op_math/pow.py b/pennylane/ops/op_math/pow.py index a6daafb9150..6251fb6fdbb 100644 --- a/pennylane/ops/op_math/pow.py +++ b/pennylane/ops/op_math/pow.py @@ -348,6 +348,26 @@ def has_adjoint(self): return isinstance(self.z, int) def adjoint(self): + """Create an operation that is the adjoint of this one. + + Adjointed operations are the conjugated and transposed version of the + original operation. Adjointed ops are equivalent to the inverted operation for unitary + gates. + + .. warning:: + + The adjoint of a fractional power of an operator is not well-defined due to branch cuts in the power function. + Therefore, an ``AdjointUndefinedError`` is raised when the power ``z`` is not an integer. + + The integer power check is a type check, so that floats like ``2.0`` are not considered to be integers. + + Returns: + The adjointed operation. + + Raises: + AdjointUndefinedError: If the exponent ``z`` is not of type ``int``. + + """ if isinstance(self.z, int): return Pow(base=qml.adjoint(self.base), z=self.z) raise AdjointUndefinedError( diff --git a/pennylane/pauli/conversion.py b/pennylane/pauli/conversion.py index c902ee969fb..4c4b4430f15 100644 --- a/pennylane/pauli/conversion.py +++ b/pennylane/pauli/conversion.py @@ -252,6 +252,8 @@ def pauli_decompose( We can use this function to compute the Pauli operator decomposition of an arbitrary Hermitian matrix: + >>> import pennylane as qml + >>> import numpy as np >>> A = np.array( ... [[-2, -2+1j, -2, -2], [-2-1j, 0, 0, -1], [-2, 0, -2, -1], [-2, -1, -1, 0]]) >>> H = qml.pauli_decompose(A) diff --git a/pennylane/pytrees/pytrees.py b/pennylane/pytrees/pytrees.py index f55bd91574f..5dc97b32e0d 100644 --- a/pennylane/pytrees/pytrees.py +++ b/pennylane/pytrees/pytrees.py @@ -226,15 +226,17 @@ def flatten(obj: Any) -> tuple[list[Any], PyTreeStructure]: Returns: List[Any], Union[Structure, Leaf]: a list of leaves and a structure representing the object + See also :func:`~.unflatten`. + + **Example** + >>> op = qml.adjoint(qml.Rot(1.2, 2.3, 3.4, wires=0)) >>> data, structure = flatten(op) >>> data [1.2, 2.3, 3.4] + >>> structure , ()), (Leaf, Leaf, Leaf))>,))> - - See also :function:`~.unflatten`. - """ flatten_fn = flatten_registrations.get(type(obj), None) if flatten_fn is None: @@ -262,7 +264,9 @@ def unflatten(data: list[Any], structure: PyTreeStructure) -> Any: Returns: A repacked pytree. - .. seealso:: :function:`~.flatten` + .. seealso:: :func:`~.flatten` + + **Example** >>> op = qml.adjoint(qml.Rot(1.2, 2.3, 3.4, wires=0)) >>> data, structure = flatten(op) diff --git a/pennylane/qaoa/layers.py b/pennylane/qaoa/layers.py index 4250806aa5e..0c1f7ab9c1d 100644 --- a/pennylane/qaoa/layers.py +++ b/pennylane/qaoa/layers.py @@ -96,6 +96,7 @@ def circuit(gamma): 1: ──H───────────╰RZZ(1.00)─┤ """ + # NOTE: op is defined explicitely as validation inside ApproxTimeEvolution needs to be called before checking Hamiltonian op = qml.templates.ApproxTimeEvolution(hamiltonian, gamma, 1) if not _diagonal_terms(hamiltonian): raise ValueError("hamiltonian must be written only in terms of PauliZ and Identity gates") diff --git a/pennylane/qchem/__init__.py b/pennylane/qchem/__init__.py index 15855fb2a10..6168999676d 100644 --- a/pennylane/qchem/__init__.py +++ b/pennylane/qchem/__init__.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. """ -This subpackage provides the functionality to perform quantum chemistry calculations. +This submodule provides the functionality to perform quantum chemistry calculations. """ from .basis_data import load_basisset from .basis_set import BasisFunction, atom_basis_data, mol_basis_data diff --git a/pennylane/qchem/convert_openfermion.py b/pennylane/qchem/convert_openfermion.py index ce7f3123100..e00c479b4e4 100644 --- a/pennylane/qchem/convert_openfermion.py +++ b/pennylane/qchem/convert_openfermion.py @@ -23,12 +23,7 @@ from pennylane import numpy as np from pennylane.fermi.fermionic import FermiSentence, FermiWord from pennylane.ops import LinearCombination, Sum -from pennylane.qchem.convert import ( - _openfermion_to_pennylane, - _pennylane_to_openfermion, - _process_wires, -) -from pennylane.wires import Wires +from pennylane.qchem.convert import _openfermion_to_pennylane, _pennylane_to_openfermion def _import_of(): @@ -53,27 +48,28 @@ def from_openfermion(openfermion_op, wires=None, tol=1e-16): to PennyLane :class:`~.LinearCombination`. Args: - openfermion_op (FermionOperator, QubitOperator): OpenFermion operator + openfermion_op (FermionOperator, QubitOperator): OpenFermion operator. wires (dict): Custom wire mapping used to convert the external qubit operator to a PennyLane operator. Only dictionaries with integer keys (for qubit-to-wire conversion) are accepted. If ``None``, the identity map (e.g., ``0->0, 1->1, ...``) will be used. - tol (float): tolerance for discarding negligible coefficients + tol (float): Tolerance for discarding negligible coefficients. Returns: - Union[FermiWord, FermiSentence, LinearCombination]: PennyLane operator + Union[FermiWord, FermiSentence, LinearCombination]: PennyLane operator. **Example** + >>> import pennylane as qml >>> from openfermion import FermionOperator, QubitOperator >>> of_op = 0.5 * FermionOperator('0^ 2') + FermionOperator('0 2^') - >>> pl_op = from_openfermion(of_op) + >>> pl_op = qml.from_openfermion(of_op) >>> print(pl_op) 0.5 * a⁺(0) a(2) + 1.0 * a(0) a⁺(2) >>> of_op = QubitOperator('X0', 1.2) + QubitOperator('Z1', 2.4) - >>> pl_op = from_openfermion(of_op) + >>> pl_op = qml.from_openfermion(of_op) >>> print(pl_op) 1.2 * X(0) + 2.4 * Z(1) """ @@ -129,13 +125,20 @@ def to_openfermion( **Example** + >>> import pennylane as qml >>> w1 = qml.fermi.FermiWord({(0, 0) : '+', (1, 1) : '-'}) >>> w2 = qml.fermi.FermiWord({(0, 1) : '+', (1, 2) : '-'}) - >>> s = qml.fermi.FermiSentence({w1 : 1.2, w2: 3.1}) - >>> of_op = qml.to_openfermion(s) - >>> of_op + >>> fermi_s = qml.fermi.FermiSentence({w1 : 1.2, w2: 3.1}) + >>> of_fermi_op = qml.to_openfermion(fermi_s) + >>> of_fermi_op 1.2 [0^ 1] + 3.1 [1^ 2] + + >>> sum_op = 1.2 * qml.X(0) + 2.4 * qml.Z(1) + >>> of_qubit_op = qml.to_openfermion(sum_op) + >>> of_qubit_op + (1.2+0j) [X0] + + (2.4+0j) [Z1] """ return _to_openfermion_dispatch(pennylane_op, wires=wires, tol=tol) diff --git a/pennylane/qchem/hamiltonian.py b/pennylane/qchem/hamiltonian.py index d88e4844796..897976043cb 100644 --- a/pennylane/qchem/hamiltonian.py +++ b/pennylane/qchem/hamiltonian.py @@ -207,19 +207,30 @@ def diff_hamiltonian(mol, cutoff=1.0e-12, core=None, active=None, mapping="jorda **Example** + >>> from pennylane import numpy as pnp >>> symbols = ['H', 'H'] - >>> geometry = np.array([[0.0, 0.0, 0.0], [0.0, 0.0, 1.0]], requires_grad = False) - >>> alpha = np.array([[3.42525091, 0.62391373, 0.1688554], + >>> geometry = pnp.array([[0.0, 0.0, 0.0], [0.0, 0.0, 1.0]], requires_grad = False) + >>> alpha = pnp.array([[3.42525091, 0.62391373, 0.1688554], >>> [3.42525091, 0.62391373, 0.1688554]], requires_grad=True) >>> mol = qml.qchem.Molecule(symbols, geometry, alpha=alpha) >>> args = [alpha] - >>> h = diff_hamiltonian(mol)(*args) - >>> h.coeffs - array([ 0.29817879+0.j, 0.20813365+0.j, 0.20813365+0.j, - 0.17860977+0.j, 0.04256036+0.j, -0.04256036+0.j, - -0.04256036+0.j, 0.04256036+0.j, -0.34724873+0.j, - 0.13290293+0.j, -0.34724873+0.j, 0.17546329+0.j, - 0.17546329+0.j, 0.13290293+0.j, 0.18470917+0.j]) + >>> h = qml.qchem.diff_hamiltonian(mol)(*args) + >>> h.terms()[0] + [tensor(0.29817878, requires_grad=True), + tensor(0.20813366, requires_grad=True), + tensor(-0.34724872, requires_grad=True), + tensor(0.13290292, requires_grad=True), + tensor(0.20813366, requires_grad=True), + tensor(0.17860977, requires_grad=True), + tensor(0.04256036, requires_grad=True), + tensor(-0.04256036, requires_grad=True), + tensor(-0.04256036, requires_grad=True), + tensor(0.04256036, requires_grad=True), + tensor(-0.34724872, requires_grad=True), + tensor(0.17546328, requires_grad=True), + tensor(0.13290292, requires_grad=True), + tensor(0.17546328, requires_grad=True), + tensor(0.18470917, requires_grad=True)] """ def _molecular_hamiltonian(*args): @@ -352,7 +363,7 @@ def molecular_hamiltonian(*args, **kwargs): from pennylane import qchem symbols = ["H", "H"] - geometry = [[0.0, 0.0, 0.0], [0.0, 0.0, 1.0]] + geometry = np.array([[0.0, 0.0, 0.0], [0.0, 0.0, 1.0]]) H, qubit = qchem.molecular_hamiltonian(symbols, geometry, charge=0) diff --git a/pennylane/qchem/observable_hf.py b/pennylane/qchem/observable_hf.py index bc18dc18288..91b100de54f 100644 --- a/pennylane/qchem/observable_hf.py +++ b/pennylane/qchem/observable_hf.py @@ -111,15 +111,18 @@ def qubit_observable(o_ferm, cutoff=1.0e-12, mapping="jordan_wigner"): **Example** - >>> qml.operation.enable_new_opmath() >>> w1 = qml.fermi.FermiWord({(0, 0) : '+', (1, 1) : '-'}) >>> w2 = qml.fermi.FermiWord({(0, 0) : '+', (1, 1) : '-'}) >>> s = qml.fermi.FermiSentence({w1 : 1.2, w2: 3.1}) >>> print(qubit_observable(s)) -0.775j * (Y(0) @ X(1)) + 0.775 * (Y(0) @ Y(1)) + 0.775 * (X(0) @ X(1)) + 0.775j * (X(0) @ Y(1)) - If the new op-math is deactivated, a :class:`~Hamiltonian` instance is returned. + If the new op-math is deactivated, a legacy :class:`~pennylane.ops.Hamiltonian` instance is returned. + >>> qml.operation.disable_new_opmath() + UserWarning: Disabling the new Operator arithmetic system for legacy support. + If you need help troubleshooting your code, please visit + https://docs.pennylane.ai/en/stable/news/new_opmath.html >>> w1 = qml.fermi.FermiWord({(0, 0) : '+', (1, 1) : '-'}) >>> w2 = qml.fermi.FermiWord({(0, 1) : '+', (1, 2) : '-'}) >>> s = qml.fermi.FermiSentence({w1 : 1.2, w2: 3.1}) diff --git a/pennylane/qnn/torch.py b/pennylane/qnn/torch.py index 9dff1a97fe8..bbeef534a5e 100644 --- a/pennylane/qnn/torch.py +++ b/pennylane/qnn/torch.py @@ -106,7 +106,7 @@ def qnode(inputs, weights_0, weight_1): **Output shape** - If the QNode returns a single measurement, then the output of the ``KerasLayer`` will have + If the QNode returns a single measurement, then the output of the ``TorchLayer`` will have shape ``(batch_dim, *measurement_shape)``, where ``measurement_shape`` is the output shape of the measurement: diff --git a/pennylane/queuing.py b/pennylane/queuing.py index ffb35bd9165..ed6b7e0d501 100644 --- a/pennylane/queuing.py +++ b/pennylane/queuing.py @@ -569,7 +569,7 @@ def process_queue( The list of tape operations, the list of tape measurements Raises: - QueuingError: if the queue contains objects that cannot be processed into a QuantumScript + QueuingError: If the queue contains objects that cannot be processed into a QuantumScript """ lists = {"_ops": [], "_measurements": []} diff --git a/pennylane/resource/specs.py b/pennylane/resource/specs.py index bd6536feda7..371af2fff09 100644 --- a/pennylane/resource/specs.py +++ b/pennylane/resource/specs.py @@ -50,7 +50,7 @@ def specs(qnode, **kwargs): about the circuit after applying the specified amount of transforms/expansions first. Args: - qnode (.QNode): the QNode to calculate the specifications for + qnode (.QNode): the QNode to calculate the specifications for. Keyword Args: level (None, str, int, slice): An indication of what transforms to apply before computing the resource information. @@ -77,9 +77,9 @@ def specs(qnode, **kwargs): .. note:: At most, one of ``level`` or ``expansion_strategy`` needs to be provided. If neither is provided, - ``qnode.expansion_strategy`` would be used instead. Users are encouraged to predominantly use ``level``, - as it allows for the same values as ``expansion_strategy``, and allows for more flexibility choosing - the wanted transforms/expansions. + ``qnode.expansion_strategy`` will be used instead. Users are encouraged to predominantly use ``level``, + as it allows for the same values as ``expansion_strategy`` and offers more flexibility in choosing + the desired transforms/expansions. .. warning:: @@ -91,11 +91,13 @@ def specs(qnode, **kwargs): .. code-block:: python3 - x = np.array([0.1, 0.2]) + from pennylane import numpy as pnp + + x = pnp.array([0.1, 0.2]) hamiltonian = qml.dot([1.0, 0.5], [qml.X(0), qml.Y(0)]) dev = qml.device('default.qubit', wires=2) - @qml.qnode(dev, diff_method="parameter-shift", shifts=np.pi / 4) + @qml.qnode(dev, diff_method="parameter-shift", shifts=pnp.pi / 4) def circuit(x, add_ry=True): qml.RX(x[0], wires=0) qml.CNOT(wires=(0,1)) @@ -106,15 +108,15 @@ def circuit(x, add_ry=True): return qml.probs(wires=(0,1)) >>> qml.specs(circuit)(x, add_ry=False) - {'resources': Resources(num_wires=2, num_gates=4, gate_types=defaultdict(, {'RX': 1, 'CNOT': 1, 'TrotterPro - duct': 2}}), gate_sizes=defaultdict(, {1: 3, 2: 1}), depth=4, shots=Shots(total_shots=None, shot_vector=())), + {'resources': Resources(num_wires=2, num_gates=98, gate_types=defaultdict(, {'RX': 1, 'CNOT': 1, 'Exp': 96}), gate_sizes=defaultdict(, {1: 97, 2: 1}), depth=98, shots=Shots(total_shots=None, shot_vector=())), 'errors': {'SpectralNormError': SpectralNormError(0.42998560822421455)}, 'num_observables': 1, 'num_diagonalizing_gates': 0, 'num_trainable_params': 1, 'num_device_wires': 2, + 'num_tape_wires': 2, 'device_name': 'default.qubit', - 'expansion_strategy': 'gradient', + 'level': 'gradient', 'gradient_options': {'shifts': 0.7853981633974483}, 'interface': 'auto', 'diff_method': 'parameter-shift', @@ -145,34 +147,52 @@ def circuit(x): First, we can check the resource information of the ``QNode`` without any modifications. Note that ``level=top`` would return the same results: - >>> qml.specs(circuit, level=0)(0.1)["resources"] - Resources(num_wires=2, num_gates=6, gate_types=defaultdict(, {'RandomLayers': 1, 'RX': 2, 'SWAP': 1, 'PauliX': 2}), - gate_sizes=defaultdict(, {2: 2, 1: 4}), depth=6, shots=Shots(total_shots=None, shot_vector=())) + >>> print(qml.specs(circuit, level=0)(0.1)["resources"]) + wires: 2 + gates: 6 + depth: 6 + shots: Shots(total=None) + gate_types: + {'RandomLayers': 1, 'RX': 2, 'SWAP': 1, 'PauliX': 2} + gate_sizes: + {2: 2, 1: 4} We then check the resources after applying all transforms: - >>> qml.specs(circuit, level=None)(0.1)["resources"] - Resources(num_wires=2, num_gates=2, gate_types=defaultdict(, {'RY': 1, 'RX': 1}), - gate_sizes=defaultdict(, {1: 2}), depth=1, shots=Shots(total_shots=None, shot_vector=())) + >>> print(qml.specs(circuit, level=None)(0.1)["resources"]) + wires: 2 + gates: 2 + depth: 1 + shots: Shots(total=None) + gate_types: + {'RY': 1, 'RX': 1} + gate_sizes: + {1: 2} We can also notice that ``SWAP`` and ``PauliX`` are not present in the circuit if we set ``level=2``: - >>> qml.specs(circuit, level=2)(0.1)["resources"] - Resources(num_wires=2, num_gates=3, gate_types=defaultdict(, {'RandomLayers': 1, 'RX': 2}), - gate_sizes=defaultdict(, {2: 1, 1: 2}), depth=3, shots=Shots(total_shots=None, shot_vector=())) + >>> print(qml.specs(circuit, level=2)(0.1)["resources"]) + wires: 2 + gates: 3 + depth: 3 + shots: Shots(total=None) + gate_types: + {'RandomLayers': 1, 'RX': 2} + gate_sizes: + {2: 1, 1: 2} - If we attempt to only apply the ``merge_rotations`` transform, we would end with only one trainable object, which is in ``RandomLayers``: + If we attempt to apply only the ``merge_rotations`` transform, we end up with only one trainable object, which is in ``RandomLayers``: >>> qml.specs(circuit, level=slice(2, 3))(0.1)["num_trainable_params"] 1 - However, if we apply all transforms, ``RandomLayers`` would be decomposed to an ``RY`` and an ``RX``, giving us two trainable objects: + However, if we apply all transforms, ``RandomLayers`` is decomposed into an ``RY`` and an ``RX``, giving us two trainable objects: >>> qml.specs(circuit, level=None)(0.1)["num_trainable_params"] 2 If a ``QNode`` with a tape-splitting transform is supplied to the function, with the transform included in the desired transforms, a dictionary - would be returned for each resulting tapes: + is returned for each resulting tape: .. code-block:: python3 diff --git a/pennylane/templates/subroutines/qrom.py b/pennylane/templates/subroutines/qrom.py index 38c0f99ddc0..8396fc9c6e0 100644 --- a/pennylane/templates/subroutines/qrom.py +++ b/pennylane/templates/subroutines/qrom.py @@ -68,7 +68,7 @@ def circuit(): target_wires = [2,3,4], work_wires = [5,6,7]) - return qml.sample(wires = [2,3,4]) + return qml.sample(wires = [2,3,4]) .. code-block:: pycon diff --git a/pennylane/transforms/add_noise.py b/pennylane/transforms/add_noise.py index 6b2caa3e86c..38213402b93 100644 --- a/pennylane/transforms/add_noise.py +++ b/pennylane/transforms/add_noise.py @@ -22,25 +22,25 @@ def add_noise(tape, noise_model, level=None): """Insert operations according to a provided noise model. - Circuits passed through this transform will be updated to apply the + Circuits passed through this quantum transform will be updated to apply the insertion-based :class:`~.NoiseModel`, which contains a mapping ``{BooleanFn: Callable}`` from conditions to the corresponding noise - gates. Each condition of the noise model will be evaluated on the + gates. Each condition in the noise model will be evaluated on the operations contained within the given circuit. For conditions that evaluate to ``True``, the noisy gates contained within the ``Callable`` will be inserted after the operation under consideration. Args: tape (QNode or QuantumTape or Callable or pennylane.devices.Device): the input circuit or - device to be transformed - noise_model (~pennylane.NoiseModel): noise model according to which noise has to be inserted + device to be transformed. + noise_model (~pennylane.NoiseModel): noise model according to which noise has to be inserted. level (None, str, int, slice): An indication of which stage in the transform program the noise model should be applied to. Only relevant when transforming a ``QNode``. More details on the following permissible values can be found in the :func:`~.workflow.get_transform_program` - * ``None``: expands the tape to have no ``Adjoint`` and ``Templates``. - * ``str``: acceptable keys are ``"top"``, ``"user"``, ``"device"``, and ``"gradient"`` - * ``int``: how many transforms to include, starting from the front of the program + * ``str``: acceptable keys are ``"top"``, ``"user"``, ``"device"``, and ``"gradient"``. + * ``int``: how many transforms to include, starting from the front of the program. * ``slice``: a slice to select out components of the transform program. Returns: @@ -88,11 +88,16 @@ def circuit(w, x, y, z): Executions of this circuit will differ from the noise-free value: - >>> circuit(0.9, 0.4, 0.5, 0.6) - tensor(0.60722291, requires_grad=True) - >>> print(qml.draw(f)(0.9, 0.4, 0.5, 0.6)) - 0: ──RX(0.9)──PhaseDamping(0.4)───────────────────────╭●──RY(0.5)───ThermalRelaxationError(0.2,2.0,0.2,0.6)─┤ ╭ - 1: ──RY(0.4)──ThermalRelaxationError(0.2,2.0,0.2,0.6)─╰X──RX(0.6)───PhaseDamping(0.4)───────────────────────┤ ╰ + .. code-block:: python + + >>> circuit(0.9, 0.4, 0.5, 0.6) + array(0.544053) + >>> print(qml.draw(circuit)(0.9, 0.4, 0.5, 0.6)) + 0: ──RX(0.90)──PhaseDamping(0.40)──ThermalRelaxationError(0.45,2.00,0.20,0.60)─╭●──RY(0.50) + 1: ──RY(0.40)──────────────────────────────────────────────────────────────────╰X──RX(0.60) + + ───────────────────────────────────────────────────────────────────┤ ╭ + ───PhaseDamping(0.40)──ThermalRelaxationError(0.30,2.00,0.20,0.60)─┤ ╰ .. details:: :title: Tranform Levels @@ -100,7 +105,7 @@ def circuit(w, x, y, z): When transforming an already constructed ``QNode``, the ``add_noise`` transform will be added at the end of the "user" transforms by default, i.e., after all the transforms - that have been manually applied to the QNode until that point. + that have been manually applied to the QNode up to that point. .. code-block:: python3 @@ -123,33 +128,35 @@ def circuit(w, x, y, z): >>> qml.workflow.get_transform_program(circuit) TransformProgram(cancel_inverses, merge_rotations, undo_swaps, _expand_metric_tensor, batch_transform, expand_fn, metric_tensor) + >>> qml.workflow.get_transform_program(noisy_circuit) TransformProgram(cancel_inverses, merge_rotations, undo_swaps, _expand_metric_tensor, add_noise, batch_transform, expand_fn, metric_tensor) - However, one can request inserting it at any specific point of the transform program. Specifying the ``level`` keyword argument while - transforming a ``QNode``, will allow addition of the transform at the end of the transform program extracted at a designated level via - :func:`get_transform_program `. For example, one could specify ``None`` to add it at the end, - which will also ensure that the tape is expanded to have no ``Adjoint`` and ``Templates``: + However, one can request to insert the ``add_noise`` transform at any specific point in the transform program. By specifying the ``level`` keyword argument while + transforming a ``QNode``, this transform can be added at a designated level within the transform program, as determined using the + :func:`get_transform_program `. For example, specifying ``None`` will add it at the end, ensuring that the tape is expanded to have no ``Adjoint`` and ``Templates``: >>> qml.transforms.add_noise(circuit, noise_model, level=None).transform_program TransformProgram(cancel_inverses, merge_rotations, undo_swaps, _expand_metric_tensor, batch_transform, expand_fn, add_noise, metric_tensor) - Other, acceptable values for the level are ``"top"``, ``"user"``, ``"device"``, and ``"gradient"``. Among these, `"top"` will allow addition - to an empty transform program, `"user"` will allow addition at the end of user specified transforms, `"device"` will allow addition at the - end of device-specific transforms, and `"gradient"` will allow addition at the end of transform that expands trainable operations. For example: + Other acceptable values for ``level`` are ``"top"``, ``"user"``, ``"device"``, and ``"gradient"``. Among these, `"top"` will allow addition + to an empty transform program, `"user"` will allow addition at the end of user-specified transforms, `"device"` will allow addition at the + end of device-specific transforms, and `"gradient"` will allow addition at the end of transforms that expand trainable operations. For example: >>> qml.transforms.add_noise(circuit, noise_model, level="top").transform_program TransformProgram(add_noise) + >>> qml.transforms.add_noise(circuit, noise_model, level="user").transform_program TransformProgram(cancel_inverses, merge_rotations, undo_swaps, _expand_metric_tensor, add_noise, metric_tensor) + >>> qml.transforms.add_noise(circuit, noise_model, level="device").transform_program TransformProgram(cancel_inverses, merge_rotations, undo_swaps, _expand_metric_tensor, batch_transform, expand_fn, add_noise, metric_tensor) - Finally, more precise control over the insertion of the transform can be achieved by specifying - an integer or slice for indexing for extracting the transform program. For example, one can do: + Finally, more precise control over the insertion of the transform can be achieved by specifying an integer or slice for indexing when extracting the transform program. For example, one can do: >>> qml.transforms.add_noise(circuit, noise_model, level=2).transform_program TransformProgram(cancel_inverses, merge_rotations, add_noise) + >>> qml.transforms.add_noise(circuit, noise_model, level=slice(1,3)).transform_program TransformProgram(merge_rotations, undo_swaps, add_noise) diff --git a/pennylane/transforms/dynamic_one_shot.py b/pennylane/transforms/dynamic_one_shot.py index cfaef28a225..591553b3021 100644 --- a/pennylane/transforms/dynamic_one_shot.py +++ b/pennylane/transforms/dynamic_one_shot.py @@ -60,7 +60,7 @@ def dynamic_one_shot( """Transform a QNode to into several one-shot tapes to support dynamic circuit execution. Args: - tape (QNode or QuantumTape or Callable): a quantum circuit to add a batch dimension to + tape (QNode or QuantumTape or Callable): a quantum circuit to add a batch dimension to. Returns: qnode (QNode) or quantum function (Callable) or tuple[List[QuantumTape], function]: @@ -231,12 +231,12 @@ def parse_native_mid_circuit_measurements( """Combines, gathers and normalizes the results of native mid-circuit measurement runs. Args: - circuit (QuantumTape): The original ``QuantumScript`` - aux_tapes (List[QuantumTape]): List of auxiliary ``QuantumScript`` objects - results (TensorLike): Array of measurement results + circuit (QuantumTape): The original ``QuantumScript``. + aux_tapes (List[QuantumTape]): List of auxiliary ``QuantumScript`` objects. + results (TensorLike): Array of measurement results. Returns: - tuple(TensorLike): The results of the simulation + tuple(TensorLike): The results of the simulation. """ def measurement_with_no_shots(measurement): diff --git a/pennylane/transforms/split_non_commuting.py b/pennylane/transforms/split_non_commuting.py index 1f52e243fc8..90566b92d7d 100644 --- a/pennylane/transforms/split_non_commuting.py +++ b/pennylane/transforms/split_non_commuting.py @@ -701,6 +701,8 @@ def _sum_terms(res: ResultBatch, coeffs: List[float], offset: float, shape: Tupl # The shape of res at this point is (n_terms, [,n_shots] [,batch_size]) dot_products = [] for c, r in zip(coeffs, res): + if qml.math.get_interface(r) == "autograd": + r = qml.math.array(r) dot_products.append(qml.math.dot(qml.math.squeeze(r), c)) if len(dot_products) == 0: return qml.math.ones(shape) * offset diff --git a/pennylane/transforms/transpile.py b/pennylane/transforms/transpile.py index abbc6739449..b2d4cb38773 100644 --- a/pennylane/transforms/transpile.py +++ b/pennylane/transforms/transpile.py @@ -15,6 +15,7 @@ from pennylane.queuing import QueuingManager from pennylane.tape import QuantumTape from pennylane.transforms import transform +from pennylane.typing import Result, ResultBatch def state_transposition(results, mps, new_wire_order, original_wire_order): @@ -60,7 +61,9 @@ def _process_measurements(expanded_tape, device_wires, is_default_mixed): @transform -def transpile(tape: QuantumTape, coupling_map, device=None) -> (Sequence[QuantumTape], Callable): +def transpile( + tape: QuantumTape, coupling_map, device=None +) -> tuple[Sequence[QuantumTape], Callable[[ResultBatch], Result]]: """Transpile a circuit according to a desired coupling map .. warning:: diff --git a/pennylane/typing.py b/pennylane/typing.py index f5cb0f5d73a..4a289d7c036 100644 --- a/pennylane/typing.py +++ b/pennylane/typing.py @@ -16,7 +16,7 @@ # pylint: disable=import-outside-toplevel, too-few-public-methods import sys -from typing import Dict, Tuple, TypeVar, Union +from typing import Sequence, TypeVar, Union import numpy as np from autograd.numpy.numpy_boxes import ArrayBox @@ -119,8 +119,8 @@ def _is_torch(other, subclass=False): return False -Result = TypeVar("Result", Dict, Tuple, TensorLike) +Result = TypeVar("Result", dict, tuple, TensorLike) -ResultBatch = Tuple[Result] +ResultBatch = Sequence[Result] JSON = Union[None, int, str, bool, list["JSON"], dict[str, "JSON"]] diff --git a/pennylane/workflow/construct_batch.py b/pennylane/workflow/construct_batch.py index 786cd21fdc1..9c95c6003ac 100644 --- a/pennylane/workflow/construct_batch.py +++ b/pennylane/workflow/construct_batch.py @@ -223,13 +223,13 @@ def construct_batch( qnode (QNode): the qnode we want to get the tapes and post-processing for. level (None, str, int, slice): And indication of what transforms to use from the full program. - * ``None``: use the full transform program - * ``str``: Acceptable keys are ``"top"``, ``"user"``, ``"device"``, and ``"gradient"`` - * ``int``: How many transforms to include, starting from the front of the program + * ``None``: use the full transform program. + * ``str``: Acceptable keys are ``"top"``, ``"user"``, ``"device"``, and ``"gradient"``. + * ``int``: How many transforms to include, starting from the front of the program. * ``slice``: a slice to select out components of the transform program. Returns: - Callable: a function with the same call signature as the initial quantum function. This function returns + Callable: A function with the same call signature as the initial quantum function. This function returns a batch (tuple) of tapes and postprocessing function. .. seealso:: :func:`pennylane.workflow.get_transform_program` to inspect the contents of the transform program for a specified level. @@ -242,6 +242,8 @@ def construct_batch( .. code-block:: python + from pennylane.workflow import construct_batch + @qml.transforms.undo_swaps @qml.transforms.merge_rotations @qml.transforms.cancel_inverses @@ -263,8 +265,8 @@ def circuit(x): RX(tensor(2., requires_grad=True), wires=[0]), expval(X(0) + Y(0))] - These tapes can be natively executed by the device, though with non-backprop devices the parameters - will need to be converted to numpy with :func:`~.convert_to_numpy_parameters`. + These tapes can be natively executed by the device. However, with non-backprop devices the parameters + will need to be converted to NumPy with :func:`~.convert_to_numpy_parameters`. >>> fn(dev.execute(batch)) (tensor(-0.90929743, requires_grad=True),) @@ -323,13 +325,15 @@ def batch_constructor(*args, **kwargs) -> Tuple[Tuple["qml.tape.QuantumTape", Ca context_fn = nullcontext - if isinstance(qnode, qml.qnn.KerasLayer): + if type(qnode).__name__ == "KerasLayer": + # note that calling qml.qnn.KerasLayer pulls in a tf import # pylint: disable=import-outside-toplevel import tensorflow as tf context_fn = tf.GradientTape - if isinstance(qnode, qml.qnn.TorchLayer): + elif type(qnode).__name__ == "TorchLayer": + # avoid triggering import of torch if its not needed. x = args[0] kwargs = { **{arg: weight.to(x) for arg, weight in qnode.qnode_weights.items()}, diff --git a/pennylane/workflow/qnode.py b/pennylane/workflow/qnode.py index 3ae456d03f8..fe807e403e8 100644 --- a/pennylane/workflow/qnode.py +++ b/pennylane/workflow/qnode.py @@ -961,7 +961,7 @@ def construct(self, args, kwargs): # pylint: disable=too-many-branches # Before constructing the tape, we pass the device to the # debugger to ensure they are compatible if there are any # breakpoints in the circuit - with pldb_device_manager(self.device) as _: + with pldb_device_manager(self.device): with qml.queuing.AnnotatedQueue() as q: self._qfunc_output = self.func(*args, **kwargs) diff --git a/tests/measurements/test_mid_measure.py b/tests/measurements/test_mid_measure.py index 70a1505a744..379524be83d 100644 --- a/tests/measurements/test_mid_measure.py +++ b/tests/measurements/test_mid_measure.py @@ -224,8 +224,14 @@ def test_inversion(self): """Test the __inv__ dunder method.""" m = MeasurementValue([mp1], lambda v: v) m_inversion = ~m - assert m_inversion[0] is True - assert m_inversion[1] is False + assert qml.math.allclose(m_inversion[0], True) + assert qml.math.allclose(m_inversion[1], False) + values = {mp1: True} + assert qml.math.allclose(m_inversion.concretize(values), False) + values = {mp1: False} + assert qml.math.allclose(m_inversion.concretize(values), True) + values = {mp1: np.random.rand(10) < 0.5} + assert all(m_inversion.concretize(values) != np.array(values.values())) def test_lt(self): """Test the __lt__ dunder method between a MeasurementValue and a float.""" diff --git a/tests/ops/op_math/test_controlled_decompositions.py b/tests/ops/op_math/test_controlled_decompositions.py index 0aa16e6ea40..e4aa1f2a293 100644 --- a/tests/ops/op_math/test_controlled_decompositions.py +++ b/tests/ops/op_math/test_controlled_decompositions.py @@ -89,7 +89,10 @@ def test_invalid_op_error(self): def test_invalid_num_controls(self): """Tests that an error is raised when an invalid number of control wires is passed""" - with pytest.raises(ValueError, match="The control_wires should be a single wire"): + with pytest.raises( + ValueError, + match="The control_wires should be a single wire, instead got: 2", + ): _ = ctrl_decomp_zyz(qml.X([1]), [0, 1]) su2_ops = [ diff --git a/tests/test_compiler.py b/tests/test_compiler.py index 0b407bba814..0529304751a 100644 --- a/tests/test_compiler.py +++ b/tests/test_compiler.py @@ -105,6 +105,14 @@ def circuit(phi, theta): assert jnp.allclose(circuit(jnp.pi, jnp.pi / 2), 1.0) assert jnp.allclose(qml.qjit(circuit)(jnp.pi, jnp.pi / 2), -1.0) + @pytest.mark.parametrize("jax_enable_x64", [False, True]) + def test_jax_enable_x64(self, jax_enable_x64): + """Test whether `qml.compiler.active` changes `jax_enable_x64`.""" + jax.config.update("jax_enable_x64", jax_enable_x64) + assert jax.config.jax_enable_x64 is jax_enable_x64 + qml.compiler.active() + assert jax.config.jax_enable_x64 is jax_enable_x64 + def test_qjit_circuit(self): """Test JIT compilation of a circuit with 2-qubit""" dev = qml.device("lightning.qubit", wires=2) diff --git a/tests/test_debugging.py b/tests/test_debugging.py index 0cc144ee8a5..4a2d06e62f7 100644 --- a/tests/test_debugging.py +++ b/tests/test_debugging.py @@ -712,7 +712,7 @@ class TestPLDB: def test_pldb_init(self): """Test that PLDB initializes correctly""" debugger = PLDB() - assert debugger.prompt == "[pldb]: " + assert debugger.prompt == "[pldb] " assert getattr(debugger, "_PLDB__active_dev") is None def test_valid_context_outside_qnode(self): diff --git a/tests/transforms/test_split_non_commuting.py b/tests/transforms/test_split_non_commuting.py index fccb539494e..5a64b9cda02 100644 --- a/tests/transforms/test_split_non_commuting.py +++ b/tests/transforms/test_split_non_commuting.py @@ -1027,6 +1027,27 @@ def circuit(coeff1, coeff2): assert qml.math.allclose(actual, [-0.5, np.cos(np.pi / 4)], rtol=0.05) + @pytest.mark.autograd + @pytest.mark.parametrize("grouping_strategy", [None, "default", "qwc", "wires"]) + def test_non_trainable_obs_autograd(self, grouping_strategy): + """Test that we can measure a hamiltonian with non-trainable autograd coefficients.""" + + dev = qml.device("default.qubit") + + @partial(split_non_commuting, grouping_strategy=grouping_strategy) + @qml.qnode(dev, diff_method="adjoint") + def circuit(x): + qml.RX(x, 0) + c1 = qml.numpy.array(0.1, requires_grad=False) + c2 = qml.numpy.array(0.2, requires_grad=False) + H = c1 * qml.Z(0) + c2 * qml.X(0) + return qml.expval(H) + + x = qml.numpy.array(0.5) + actual = qml.grad(circuit)(x) + + assert qml.math.allclose(actual, -0.1 * np.sin(x)) + @pytest.mark.jax @pytest.mark.parametrize("use_jit", [False, True]) @pytest.mark.parametrize("grouping_strategy", [None, "default", "qwc", "wires"])