diff --git a/.github/stable/all_interfaces.txt b/.github/stable/all_interfaces.txt index 9075f97be49..74c75ba8068 100644 --- a/.github/stable/all_interfaces.txt +++ b/.github/stable/all_interfaces.txt @@ -8,7 +8,7 @@ black==25.1.0 cachetools==5.5.2 certifi==2025.4.26 cfgv==3.4.0 -charset-normalizer==3.4.1 +charset-normalizer==3.4.2 clarabel==0.10.0 click==8.1.8 contourpy==1.3.2 @@ -38,7 +38,7 @@ isort==5.13.2 jax==0.4.28 jaxlib==0.4.28 Jinja2==3.1.6 -joblib==1.4.2 +joblib==1.5.0 keras==3.9.2 kiwisolver==1.4.8 lazy-object-proxy==1.11.0 @@ -74,7 +74,7 @@ osqp==1.0.3 packaging==25.0 pandas==2.2.3 pathspec==0.12.1 -PennyLane_Lightning==0.42.0.dev7 +PennyLane_Lightning==0.42.0.dev13 pillow==11.2.1 platformdirs==4.3.7 pluggy==1.5.0 @@ -112,7 +112,7 @@ tensorboard==2.18.0 tensorboard-data-server==0.7.2 tensorflow==2.18.1 tensorflow-io-gcs-filesystem==0.37.1 -termcolor==3.0.1 +termcolor==3.1.0 tf_keras==2.18.0 toml==0.10.2 tomli==2.2.1 diff --git a/.github/stable/core.txt b/.github/stable/core.txt index 946e84f7f62..dcddde13736 100644 --- a/.github/stable/core.txt +++ b/.github/stable/core.txt @@ -7,7 +7,7 @@ black==25.1.0 cachetools==5.5.2 certifi==2025.4.26 cfgv==3.4.0 -charset-normalizer==3.4.1 +charset-normalizer==3.4.2 clarabel==0.10.0 click==8.1.8 contourpy==1.3.2 @@ -30,7 +30,7 @@ idna==3.10 iniconfig==2.1.0 isort==5.13.2 Jinja2==3.1.6 -joblib==1.4.2 +joblib==1.5.0 kiwisolver==1.4.8 lazy-object-proxy==1.11.0 markdown-it-py==3.0.0 @@ -46,7 +46,7 @@ osqp==1.0.3 packaging==25.0 pandas==2.2.3 pathspec==0.12.1 -PennyLane_Lightning==0.42.0.dev7 +PennyLane_Lightning==0.42.0.dev13 pillow==11.2.1 platformdirs==4.3.7 pluggy==1.5.0 @@ -78,7 +78,7 @@ scs==3.2.7.post2 six==1.17.0 smmap==5.0.2 tach==0.28.5 -termcolor==3.0.1 +termcolor==3.1.0 toml==0.10.2 tomli==2.2.1 tomli_w==1.2.0 diff --git a/.github/stable/external.txt b/.github/stable/external.txt index 1abec90825b..806ddfa92ec 100644 --- a/.github/stable/external.txt +++ b/.github/stable/external.txt @@ -23,7 +23,7 @@ cachetools==5.5.2 certifi==2025.4.26 cffi==1.17.1 cfgv==3.4.0 -charset-normalizer==3.4.1 +charset-normalizer==3.4.2 chex==0.1.89 cirq-core==1.4.0 clarabel==0.10.0 @@ -32,7 +32,7 @@ comm==0.2.2 contourpy==1.3.2 cotengra==0.7.2 coverage==7.8.0 -cryptography==44.0.2 +cryptography==44.0.3 cvxopt==1.3.2 cvxpy==1.6.5 cycler==0.12.1 @@ -57,7 +57,7 @@ flatbuffers==25.2.10 fonttools==4.57.0 fqdn==1.5.1 fxpmath==0.4.9 -galois==0.4.5 +galois==0.4.6 gast==0.6.0 gitdb==4.0.12 GitPython==3.1.44 @@ -69,7 +69,7 @@ h5py==3.13.0 httpcore==1.0.9 httpx==0.28.1 ibm-cloud-sdk-core==3.23.0 -ibm-platform-services==0.63.0 +ibm-platform-services==0.64.0 identify==2.6.10 idna==3.10 importlib_metadata==8.7.0 @@ -84,7 +84,7 @@ jax==0.4.28 jaxlib==0.4.28 jedi==0.19.2 Jinja2==3.1.6 -joblib==1.4.2 +joblib==1.5.0 json5==0.12.0 jsonpointer==3.0.0 jsonschema==4.23.0 @@ -118,7 +118,7 @@ ml_dtypes==0.5.1 mpmath==1.3.0 mypy_extensions==1.1.0 namex==0.0.9 -narwhals==1.36.0 +narwhals==1.37.1 nbclient==0.10.2 nbconvert==7.16.6 nbformat==5.10.4 @@ -140,10 +140,10 @@ pandocfilters==1.5.1 parso==0.8.4 pathspec==0.12.1 pbr==6.1.1 -PennyLane-Catalyst==0.12.0.dev15 -PennyLane-qiskit @ git+https://github.com/PennyLaneAI/pennylane-qiskit.git@f6f99a459cd4b329f993ed2008b02e91bcbb520e -PennyLane_Lightning==0.42.0.dev0 -PennyLane_Lightning_Kokkos==0.42.0.dev0 +PennyLane-Catalyst==0.12.0.dev20 +PennyLane-qiskit @ git+https://github.com/PennyLaneAI/pennylane-qiskit.git@cbbf8120d64cfc598f8481d84ac99c6383dce641 +PennyLane_Lightning==0.42.0.dev11 +PennyLane_Lightning_Kokkos==0.42.0.dev11 pexpect==4.9.0 pillow==11.2.1 platformdirs==4.3.7 @@ -161,8 +161,8 @@ pure_eval==0.2.3 py==1.11.0 py-cpuinfo==9.0.0 pycparser==2.22 -pydantic==2.11.3 -pydantic_core==2.33.1 +pydantic==2.11.4 +pydantic_core==2.33.2 pydot==3.0.4 Pygments==2.19.1 PyJWT==2.10.1 @@ -189,8 +189,8 @@ qiskit-aer==0.16.0 qiskit-ibm-provider==0.11.0 qiskit-ibm-runtime==0.29.0 qref==0.9.0 -qsharp==1.15.0 -qsharp-widgets==1.15.0 +qsharp==1.16.0 +qsharp-widgets==1.16.0 qualtran==0.6.1 quimb==1.10.0 referencing==0.36.2 @@ -222,7 +222,7 @@ tensorboard==2.18.0 tensorboard-data-server==0.7.2 tensorflow==2.18.1 tensorflow-io-gcs-filesystem==0.37.1 -termcolor==3.0.1 +termcolor==3.1.0 terminado==0.18.1 tf_keras==2.18.0 tinycss2==1.4.0 diff --git a/.github/stable/jax.txt b/.github/stable/jax.txt index 1d774f561be..715b212a552 100644 --- a/.github/stable/jax.txt +++ b/.github/stable/jax.txt @@ -7,7 +7,7 @@ black==25.1.0 cachetools==5.5.2 certifi==2025.4.26 cfgv==3.4.0 -charset-normalizer==3.4.1 +charset-normalizer==3.4.2 clarabel==0.10.0 click==8.1.8 contourpy==1.3.2 @@ -32,7 +32,7 @@ isort==5.13.2 jax==0.4.28 jaxlib==0.4.28 Jinja2==3.1.6 -joblib==1.4.2 +joblib==1.5.0 kiwisolver==1.4.8 lazy-object-proxy==1.11.0 markdown-it-py==3.0.0 @@ -50,7 +50,7 @@ osqp==1.0.3 packaging==25.0 pandas==2.2.3 pathspec==0.12.1 -PennyLane_Lightning==0.42.0.dev7 +PennyLane_Lightning==0.42.0.dev13 pillow==11.2.1 platformdirs==4.3.7 pluggy==1.5.0 @@ -82,7 +82,7 @@ scs==3.2.7.post2 six==1.17.0 smmap==5.0.2 tach==0.28.5 -termcolor==3.0.1 +termcolor==3.1.0 toml==0.10.2 tomli==2.2.1 tomli_w==1.2.0 diff --git a/.github/stable/tf.txt b/.github/stable/tf.txt index 3a9eaa3683a..dc0fc03fce5 100644 --- a/.github/stable/tf.txt +++ b/.github/stable/tf.txt @@ -8,7 +8,7 @@ black==25.1.0 cachetools==5.5.2 certifi==2025.4.26 cfgv==3.4.0 -charset-normalizer==3.4.1 +charset-normalizer==3.4.2 clarabel==0.10.0 click==8.1.8 contourpy==1.3.2 @@ -35,7 +35,7 @@ idna==3.10 iniconfig==2.1.0 isort==5.13.2 Jinja2==3.1.6 -joblib==1.4.2 +joblib==1.5.0 keras==3.9.2 kiwisolver==1.4.8 lazy-object-proxy==1.11.0 @@ -58,7 +58,7 @@ osqp==1.0.3 packaging==25.0 pandas==2.2.3 pathspec==0.12.1 -PennyLane_Lightning==0.42.0.dev7 +PennyLane_Lightning==0.42.0.dev13 pillow==11.2.1 platformdirs==4.3.7 pluggy==1.5.0 @@ -95,7 +95,7 @@ tensorboard==2.18.0 tensorboard-data-server==0.7.2 tensorflow==2.18.1 tensorflow-io-gcs-filesystem==0.37.1 -termcolor==3.0.1 +termcolor==3.1.0 tf_keras==2.18.0 toml==0.10.2 tomli==2.2.1 diff --git a/.github/stable/torch.txt b/.github/stable/torch.txt index a235be5bb8f..9d2bc29ecf6 100644 --- a/.github/stable/torch.txt +++ b/.github/stable/torch.txt @@ -7,7 +7,7 @@ black==25.1.0 cachetools==5.5.2 certifi==2025.4.26 cfgv==3.4.0 -charset-normalizer==3.4.1 +charset-normalizer==3.4.2 clarabel==0.10.0 click==8.1.8 contourpy==1.3.2 @@ -31,7 +31,7 @@ idna==3.10 iniconfig==2.1.0 isort==5.13.2 Jinja2==3.1.6 -joblib==1.4.2 +joblib==1.5.0 kiwisolver==1.4.8 lazy-object-proxy==1.11.0 markdown-it-py==3.0.0 @@ -60,7 +60,7 @@ osqp==1.0.3 packaging==25.0 pandas==2.2.3 pathspec==0.12.1 -PennyLane_Lightning==0.42.0.dev7 +PennyLane_Lightning==0.42.0.dev13 pillow==11.2.1 platformdirs==4.3.7 pluggy==1.5.0 @@ -93,7 +93,7 @@ six==1.17.0 smmap==5.0.2 sympy==1.13.1 tach==0.28.5 -termcolor==3.0.1 +termcolor==3.1.0 toml==0.10.2 tomli==2.2.1 tomli_w==1.2.0 diff --git a/.github/workflows/interface-unit-tests.yml b/.github/workflows/interface-unit-tests.yml index 34945695684..f9cdc84479e 100644 --- a/.github/workflows/interface-unit-tests.yml +++ b/.github/workflows/interface-unit-tests.yml @@ -492,7 +492,7 @@ jobs: pytest_additional_args: ${{ needs.warnings-as-errors-setup.outputs.pytest_warning_args }} pytest_xml_file_path: '${{ inputs.job_name_prefix }}external-libraries-tests (${{ matrix.python-version }})${{ inputs.job_name_suffix }}.xml' additional_pip_packages: | - pyzx matplotlib stim quimb mitiq ply optax scipy-openblas32>=0.3.26 qualtran + pyzx matplotlib stim quimb mitiq ply optax scipy-openblas32>=0.3.26 qualtran xdsl git+https://github.com/PennyLaneAI/pennylane-qiskit.git@master ${{ needs.default-dependency-versions.outputs.jax-version }} ${{ needs.default-dependency-versions.outputs.tensorflow-version }} diff --git a/.github/workflows/tests-labs.yml b/.github/workflows/tests-labs.yml index f6e1a8cc442..5532e498b63 100644 --- a/.github/workflows/tests-labs.yml +++ b/.github/workflows/tests-labs.yml @@ -68,6 +68,6 @@ jobs: python_version: '3.10' pytest_test_directory: pennylane/labs/tests additional_pip_packages: | - geometric mpi4py h5py basis_set_exchange pyscf - geometric mpi4py h5py basis-set-exchange pyscf optax + geometric mpi4py h5py basis_set_exchange pyscf galois + geometric mpi4py h5py basis-set-exchange pyscf galois optax ${{ needs.default-dependency-versions.outputs.jax-version }} diff --git a/.github/workflows/upload.yml b/.github/workflows/upload.yml index 93b016728da..e6a590a7689 100644 --- a/.github/workflows/upload.yml +++ b/.github/workflows/upload.yml @@ -37,9 +37,8 @@ jobs: - name: Build PennyLane wheel run: | - python -m pip install --upgrade pip wheel - pip install -r requirements-ci.txt --upgrade - python setup.py bdist_wheel + python -m pip install build + python -m build --wheel --outdir dist - name: Publish uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/doc/_templates/autosummary_core/class.rst b/doc/_templates/autosummary_core/class.rst index 36bc3638a9e..9e202738e65 100644 --- a/doc/_templates/autosummary_core/class.rst +++ b/doc/_templates/autosummary_core/class.rst @@ -15,6 +15,7 @@ .. currentmodule:: {{ module }} .. autoclass:: {{ objname }} + :no-index: :show-inheritance: {% if '__init__' in methods %} diff --git a/doc/code/qml_operation.rst b/doc/code/qml_operation.rst index 8e44dace725..32fa5c12e5f 100644 --- a/doc/code/qml_operation.rst +++ b/doc/code/qml_operation.rst @@ -1,4 +1,6 @@ qml.operation ============= +.. currentmodule:: pennylane.operation + .. automodule:: pennylane.operation diff --git a/doc/code/qml_ops_op_math.rst b/doc/code/qml_ops_op_math.rst index 0b872a5e06c..ac1c9f596ac 100644 --- a/doc/code/qml_ops_op_math.rst +++ b/doc/code/qml_ops_op_math.rst @@ -1,4 +1,6 @@ qml.ops.op_math =============== +.. currentmodule:: pennylane.ops.op_math + .. automodule:: pennylane.ops.op_math \ No newline at end of file diff --git a/doc/code/qml_pauli.rst b/doc/code/qml_pauli.rst index fae62b4a84d..dbd0ad7893b 100644 --- a/doc/code/qml_pauli.rst +++ b/doc/code/qml_pauli.rst @@ -4,6 +4,8 @@ qml.pauli Overview -------- +.. currentmodule:: pennylane.pauli + This module defines functions and classes for generating and manipulating elements of the Pauli group. It also contains a subpackage :mod:`pauli/grouping` for Pauli-word partitioning functionality used in measurement optimization. diff --git a/doc/code/qml_pulse.rst b/doc/code/qml_pulse.rst index b87b219868b..30a4f9c8a39 100644 --- a/doc/code/qml_pulse.rst +++ b/doc/code/qml_pulse.rst @@ -1,4 +1,6 @@ qml.pulse ========= +.. currentmodule:: pennylane.pulse + .. automodule:: pennylane.pulse diff --git a/doc/code/qml_qaoa.rst b/doc/code/qml_qaoa.rst index 01f6e01a96e..ed0de6718c9 100644 --- a/doc/code/qml_qaoa.rst +++ b/doc/code/qml_qaoa.rst @@ -4,6 +4,8 @@ qml.qaoa Overview -------- +.. currentmodule:: pennylane.qaoa + This module provides a collection of methods that help in the construction of QAOA workflows. diff --git a/doc/code/qml_qchem.rst b/doc/code/qml_qchem.rst index 375187a0b8d..4476d3c541d 100644 --- a/doc/code/qml_qchem.rst +++ b/doc/code/qml_qchem.rst @@ -4,6 +4,8 @@ qml.qchem Overview -------- +.. currentmodule:: pennylane.qchem + 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. It also includes functionalities to convert to and from Openfermion's diff --git a/doc/code/qml_resource.rst b/doc/code/qml_resource.rst index 9958dc20163..cec7feb55fa 100644 --- a/doc/code/qml_resource.rst +++ b/doc/code/qml_resource.rst @@ -1,4 +1,5 @@ qml.resource ============= +.. currentmodule:: pennylane.resource .. automodule:: pennylane.resource diff --git a/doc/code/qml_shadows.rst b/doc/code/qml_shadows.rst index c374187225f..6c8ee7fdb92 100644 --- a/doc/code/qml_shadows.rst +++ b/doc/code/qml_shadows.rst @@ -1,4 +1,5 @@ qml.shadows ============= +.. currentmodule:: pennylane.shadows .. automodule:: pennylane.shadows \ No newline at end of file diff --git a/doc/code/qml_tape.rst b/doc/code/qml_tape.rst index 0e295a282b8..0f9747684a6 100644 --- a/doc/code/qml_tape.rst +++ b/doc/code/qml_tape.rst @@ -1,6 +1,8 @@ qml.tape ======== +.. currentmodule:: pennylane.tape + Quantum tapes are a datastructure that can represent quantum circuits and measurement statistics in PennyLane. They are queuing contexts that can record and process quantum operations and measurements. In addition to being created internally by QNodes, quantum tapes can also be created, diff --git a/doc/conf.py b/doc/conf.py index d8cac44db9e..25584422007 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -27,7 +27,7 @@ # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. -needs_sphinx = "3.3" +needs_sphinx = "8.1" # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named "sphinx.ext.*") or your custom @@ -48,6 +48,7 @@ "sphinx_copybutton", "sphinxext.opengraph", "m2r2", + "sphinx_automodapi.smart_resolver" ] # Open Graph metadata @@ -58,6 +59,7 @@ "line_color": "#03b2ff", } ogp_image = "_static/opengraph.png" +numpydoc_show_class_members = False # The base URL with a proper language and version. @@ -82,14 +84,16 @@ copybutton_prompt_is_regexp = True intersphinx_mapping = { - "demo": ("https://pennylane.ai/qml/", None), + "demo": ("https://pennylane.ai/qml", None), "catalyst": ("https://docs.pennylane.ai/projects/catalyst/en/stable", None), } + mathjax_path = ( "https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/MathJax.js?config=TeX-MML-AM_CHTML" ) ignore_warnings = [("code/api/qml_transforms*", "no module named pennylane.transforms")] +autodoc_mock_imports = ["torch"] # Add any paths that contain templates here, relative to this directory. templates_path = ["_templates"] @@ -128,7 +132,7 @@ # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. -language = None +language = "en" # today_fmt is used as the format for a strftime call. today_fmt = "%Y-%m-%d" diff --git a/doc/development/adding_operators.rst b/doc/development/adding_operators.rst index a64f2874f05..bcf7869dfc8 100644 --- a/doc/development/adding_operators.rst +++ b/doc/development/adding_operators.rst @@ -109,7 +109,7 @@ New operators can be created by applying arithmetic functions to operators, such multiplication, taking the adjoint, or controlling an operator. At the moment, such arithmetic is only implemented for specific subclasses. -* Operators inheriting from :class:`~.Observable` support addition and scalar multiplication: +* Operators inheriting from :class:`~.Operator` support addition and scalar multiplication: >>> op = qml.PauliX(0) + 0.1 * qml.PauliZ(0) >>> op.name @@ -139,11 +139,6 @@ knows a native implementation for ``FlipAndRotate``). It also defines an adjoint class FlipAndRotate(qml.operation.Operation): - # Define how many wires the operator acts on in total. - # In our case this may be one or two, which is why we - # use the AnyWires Enumeration to indicate a variable number. - num_wires = qml.operation.AnyWires - # This attribute tells PennyLane what differentiation method to use. Here # we request parameter-shift (or "analytic") differentiation. grad_method = "A" @@ -242,8 +237,7 @@ If the above operator omitted the ``_unflatten`` custom definition, it would rai For local testing, try type(op)._unflatten(*op._flatten()) -The new gate can be used with PennyLane devices. Device support for an operation can be checked via -``dev.stopping_condition(op)``. If ``True``, then the device supports the operation. +The new gate can be used with PennyLane devices. ``DefaultQubit`` first checks if the operator has a matrix using the :attr:`~.Operator.has_matrix` property. @@ -295,7 +289,7 @@ Defining special properties of an operator ########################################## Apart from the main :class:`~.Operator` class, operators with special methods or representations -are implemented as subclasses :class:`~.Operation`, :class:`~.Observable`, :class:`~.Channel`, +are implemented as subclasses :class:`~.Operation`, :class:`~.Channel`, :class:`~.CVOperation` and :class:`~.CVObservable`. However, unlike many other frameworks, PennyLane does not use class diff --git a/doc/development/deprecations.rst b/doc/development/deprecations.rst index c90838bf8be..831a0ffa2cf 100644 --- a/doc/development/deprecations.rst +++ b/doc/development/deprecations.rst @@ -9,6 +9,78 @@ deprecations are listed below. Pending deprecations -------------------- +* ``qml.operation.Observable`` and the accompanying ``Observable.compare`` methods are deprecated. At this point, ``Observable`` only + provides a default value of ``is_hermitian=True`` and prevents the object from being processed into a tape. Instead of inheriting from + ``Observable``, operator developers should manually set ``is_hermitian = True`` and update the ``queue`` function to stop it from being + processed into the circuit. + + .. code-block:: python + + class MyObs(Operator): + + is_hermitian = True + + def queue(self, context=qml.QueuingManager): + return self + + To check if an operator is likely to be hermitian, the ``op.is_hermitian`` property can be checked. + + ``qml.equal`` and ``op1 == op2`` should be used to compare instances instead of ``op1.compare(op2)``. + + - Deprecated in v0.42 + - Will be removed in v0.43 + +* ``qml.operation.WiresEnum``, ``qml.operation.AllWires``, and ``qml.operation.AnyWires`` are deprecated. If an operation can act + on any number of wires ``Operator.num_wires = None`` should be used instead. This is the default, and does not need + to be overridden unless the operator developer wants to validate that the correct number of wires is passed. + + - Deprecated in v0.42 + - Will be removed in v0.43 + +* The boolean functions provided by ``pennylane.operation`` are deprecated. See below for alternate code to + use instead. + These include ``not_tape``, ``has_gen``, ``has_grad_method``, ``has_multipar``, ``has_nopar``, ``has_unitary_gen``, + ``is_measurement``, ``defines_diagonalizing_gates``, and ``gen_is_multi_term_hamiltonian``. + + - Deprecated in v0.42 + - Will be removed in v0.43 + +.. code-block:: python + + def not_tape(obj): + return not isinstance(obj, qml.tape.QuantumScript) + + def has_gen(obj): + return obj.has_generator + + def has_grad_method(obj): + return obj.grad_method is not None + + def has_multipar(obj): + return obj.num_params > 1 + + def has_nopar(obj): + return obj.num_params == 0 + + def has_unitary_gen(obj): + return obj in qml.ops.qubit.attributes.has_unitary_generator + + def is_measurement(obj): + return isinstance(obj, qml.measurements.MeasurementProcess) + + def defines_diagonalizing_gates(obj): + return obj.has_diagonalizing_gates + + def gen_is_multi_term_hamiltonian(obj): + if not isinstance(obj, Operator) or not obj.has_generator: + return False + try: + generator = obj.generator() + _, ops = generator.terms() + return len(ops) > 1 + except TermsUndefinedError: + return False + * The :func:`qml.QNode.get_gradient_fn` method is now deprecated. Instead, use :func:`~.workflow.get_best_diff_method` to obtain the differentiation method. - Deprecated in v0.42 @@ -61,7 +133,8 @@ Completed deprecation cycles - Removed in v0.42 * The ``KerasLayer`` class in ``qml.qnn.keras`` has been removed because Keras 2 is no longer actively maintained. - Please consider using a different machine learning framework, like :doc:`PyTorch ` or :doc:`JAX `. + Please consider using a different machine learning framework, like `PyTorch `_ + or `JAX `_. - Deprecated in v0.41 - Removed in v0.42 diff --git a/doc/development/release_notes.md b/doc/development/release_notes.md index cce3109a07c..42867adbfe8 100644 --- a/doc/development/release_notes.md +++ b/doc/development/release_notes.md @@ -5,6 +5,8 @@ This page contains the release notes for PennyLane. .. mdinclude:: ../releases/changelog-dev.md +.. mdinclude:: ../releases/changelog-0.41.1.md + .. mdinclude:: ../releases/changelog-0.41.0.md .. mdinclude:: ../releases/changelog-0.40.0.md diff --git a/doc/introduction/dynamic_quantum_circuits.rst b/doc/introduction/dynamic_quantum_circuits.rst index 2042e7d6e32..12398eb2e40 100644 --- a/doc/introduction/dynamic_quantum_circuits.rst +++ b/doc/introduction/dynamic_quantum_circuits.rst @@ -41,9 +41,9 @@ of mid-circuit measurements, as well as information about simulation strategies and how to configure them :ref:`further below `. Additional information can be found in the documentation of the individual methods. Also consider our -:doc:`Introduction to mid-circuit measurements ` -:doc:`how-to on collecting statistics of mid-circuit measurements `, -and :doc:`how-to on creating dynamic circuits with mid-circuit measurements `. +`Introduction to mid-circuit measurements `_, +`how-to on collecting statistics of mid-circuit measurements `_, +and `how-to on creating dynamic circuits with mid-circuit measurements `_. Resetting qubits **************** @@ -127,8 +127,8 @@ condition based on such values and pass it to :func:`~.pennylane.cond`: tensor([0.88660045, 0.11339955], requires_grad=True) For more examples, refer to the :func:`~.pennylane.cond` documentation -and the :doc:`how-to on creating dynamic circuits with mid-circuit measurements -`. +and the `how-to on creating dynamic circuits with mid-circuit measurements +`_. .. _mid_circuit_measurements_statistics: diff --git a/doc/introduction/interfaces.rst b/doc/introduction/interfaces.rst index 4886d8616e5..f59789a577c 100644 --- a/doc/introduction/interfaces.rst +++ b/doc/introduction/interfaces.rst @@ -215,7 +215,7 @@ gradients; a well-known example of this approach is backpropagation. These metho However, for rapid prototyping on simulators, these methods typically out-perform forward-mode accumulators such as the parameter-shift rule and finite-differences. For more details, see the -:doc:`quantum backpropagation ` demonstration. +`quantum backpropagation `__ demonstration. * ``"backprop"``: Use standard backpropagation. @@ -238,7 +238,7 @@ Hardware-compatible differentiation The following methods support both quantum hardware and simulators, and are examples of `forward accumulation `__. However, when using a simulator, you may notice that the number of circuit executions required to -compute the gradients with these methods :doc:`scales linearly ` +compute the gradients with these methods `scales linearly `__ with the number of trainable circuit parameters. * ``"parameter-shift"``: Use the analytic `parameter-shift rule diff --git a/doc/releases/changelog-0.41.0.md b/doc/releases/changelog-0.41.0.md index 1f95b80cdd9..d7ab5a46a99 100644 --- a/doc/releases/changelog-0.41.0.md +++ b/doc/releases/changelog-0.41.0.md @@ -1,6 +1,6 @@ :orphan: -# Release 0.41.0 (current release) +# Release 0.41.0

New features since last release

diff --git a/doc/releases/changelog-0.41.1.md b/doc/releases/changelog-0.41.1.md new file mode 100644 index 00000000000..d02794ce730 --- /dev/null +++ b/doc/releases/changelog-0.41.1.md @@ -0,0 +1,14 @@ +:orphan: + +# Release 0.41.1 (current release) + +

Bug fixes 🐛

+ +* A warning is raised if PennyLane is imported and a version of JAX greater than 0.4.28 is installed. + [(#7369)](https://github.com/PennyLaneAI/pennylane/pull/7369) + +

Contributors ✍️

+ +This release contains contributions from (in alphabetical order): + +Pietropaolo Frisoni \ No newline at end of file diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md index 736e6c4c485..3cccd8790c1 100644 --- a/doc/releases/changelog-dev.md +++ b/doc/releases/changelog-dev.md @@ -29,7 +29,6 @@ [0.87758256+0.j 0.47942554+0.j 0. +0.j 0. +0.j 0. +0.j 0. +0.j 0. +0.j 0. +0.j] ``` - * The transform `convert_to_mbqc_gateset` is added to the `ftqc` module to convert arbitrary circuits to a limited gate-set that can be translated to the MBQC formalism. @@ -121,12 +120,48 @@

Improvements 🛠

+* Computing the angles for uniformly controlled rotations, used in :class:`~.MottonenStatePreparation` + and :class:`~.SelectPauliRot`, now takes much less computational effort and memory. + [(#7377)](https://github.com/PennyLaneAI/pennylane/pull/7377) + +* An experimental quantum dialect written in [xDSL](https://xdsl.dev/index) has been introduced. + This is similar to [Catalyst's MLIR dialects](https://docs.pennylane.ai/projects/catalyst/en/stable/dev/dialects.html#mlir-dialects-in-catalyst), + but it is coded in Python instead of C++. + [(#7357)](https://github.com/PennyLaneAI/pennylane/pull/7357) + * The :func:`~.transforms.cancel_inverses` transform no longer changes the order of operations that don't have shared wires, providing a deterministic output. [(#7328)](https://github.com/PennyLaneAI/pennylane/pull/7328) * Alias for Identity (`I`) is now accessible from `qml.ops`. [(#7200)](https://github.com/PennyLaneAI/pennylane/pull/7200) +* The `ftqc` module `measure_arbitrary_basis`, `measure_x` and `measure_y` functions + can now be captured when program capture is enabled. + [(#7219)](https://github.com/PennyLaneAI/pennylane/pull/7219) + [(#7368)](https://github.com/PennyLaneAI/pennylane/pull/7368) + +* `Operator.num_wires` now defaults to `None` to indicate that the operator can be on + any number of wires. + [(#7312)](https://github.com/PennyLaneAI/pennylane/pull/7312) + +* Shots can now be overridden for specific `qml.Snapshot` instances via a `shots` keyword argument. + [(#7326)](https://github.com/PennyLaneAI/pennylane/pull/7326) + + ```python + dev = qml.device("default.qubit", wires=2, shots=10) + + @qml.qnode(dev) + def circuit(): + qml.Snapshot("sample", measurement=qml.sample(qml.X(0)), shots=5) + return qml.sample(qml.X(0)) + ``` + + ```pycon + >>> qml.snapshots(circuit)() + {'sample': array([-1., -1., -1., -1., -1.]), + 'execution_results': array([ 1., -1., -1., -1., -1., 1., -1., -1., 1., -1.])} + ``` + * Two-qubit `QubitUnitary` gates no longer decompose into fundamental rotation gates; it now decomposes into single-qubit `QubitUnitary` gates. This allows the decomposition system to further decompose single-qubit unitary gates more flexibly using different rotations. @@ -142,9 +177,19 @@ interface to maintain a list of special implementations. [(#7327)](https://github.com/PennyLaneAI/pennylane/pull/7327) +* Sphinx version was updated to 8.1. Sphinx is upgraded to version 8.1 and uses Python 3.10. + References to intersphinx (e.g. `` or `` are updated to remove the + :doc: prefix that is incompatible with sphinx 8.1. + [(7212)](https://github.com/PennyLaneAI/pennylane/pull/7212) +

Labs: a place for unified and rapid prototyping of research software 🧪

-* A `parity_matrix` function is now available in `pennylane.labs.phase_polynomials`. +* A `rowcol` function is now available in `pennylane.labs.intermediate_reps`. + Given the parity matrix of a CNOT circuit and a qubit connectivity graph, it synthesizes a + possible implementation of the parity matrix that respects the connectivity. + [(#7394)](https://github.com/PennyLaneAI/pennylane/pull/7394) + +* A `parity_matrix` function is now available in `pennylane.labs.intermediate_reps`. It allows to compute the parity matrix of a CNOT circuit, which is an efficient intermediate representation. It is important for CNOT routing algorithms and other quantum compilation routines. [(#7229)](https://github.com/PennyLaneAI/pennylane/pull/7229) @@ -155,7 +200,7 @@ [(#7322)](https://github.com/PennyLaneAI/pennylane/pull/7322) * The `KerasLayer` class in `qml.qnn.keras` has been removed because Keras 2 is no longer actively maintained. - Please consider using a different machine learning framework, like :doc:`PyTorch ` or :doc:`JAX `. + Please consider using a different machine learning framework, like `PyTorch `__ or `JAX `__. [(#7320)](https://github.com/PennyLaneAI/pennylane/pull/7320) * The `qml.gradients.hamiltonian_grad` function has been removed because this gradient recipe is no @@ -186,11 +231,36 @@

Deprecations 👋

+Here's a list of deprecations made this release. For a more detailed breakdown of deprecations and alternative code to use instead, Please consult the :doc:`deprecations and removals page `. + +* `qml.operation.Observable` and the corresponding `Observable.compare` have been deprecated, as + pennylane now depends on the more general `Operator` interface instead. The + `Operator.is_hermitian` property can instead be used to check whether or not it is highly likely + that the operator instance is Hermitian. + [(#7316)](https://github.com/PennyLaneAI/pennylane/pull/7316) + +* The boolean functions provided in `pennylane.operation` are deprecated. See the :doc:`deprecations page ` + for equivalent code to use instead. These include `not_tape`, `has_gen`, `has_grad_method`, `has_multipar`, + `has_nopar`, `has_unitary_gen`, `is_measurement`, `defines_diagonalizing_gates`, and `gen_is_multi_term_hamiltonian`. + [(#7319)](https://github.com/PennyLaneAI/pennylane/pull/7319) + +* `qml.operation.WiresEnum`, `qml.operation.AllWires`, and `qml.operation.AnyWires` are deprecated. To indicate that + an operator can act on any number of wires, `Operator.num_wires = None` should be used instead. This is the default + and does not need to be overwritten unless the operator developer wants to add wire number validation. + [(#7313)](https://github.com/PennyLaneAI/pennylane/pull/7313) + * The :func:`qml.QNode.get_gradient_fn` method is now deprecated. Instead, use :func:`~.workflow.get_best_diff_method` to obtain the differentiation method. [(#7323)](https://github.com/PennyLaneAI/pennylane/pull/7323)

Internal changes ⚙️

+* Wheel releases for PennyLane now follow the `PyPA binary-distribution format _` guidelines more closely. + [(#7382)](https://github.com/PennyLaneAI/pennylane/pull/7382) + +* `null.qubit` can now support an optional `track_resources` argument which allows it to record which gates are executed. + [(#7226)](https://github.com/PennyLaneAI/pennylane/pull/7226) + [(#7372)](https://github.com/PennyLaneAI/pennylane/pull/7372) + * A new internal module, `qml.concurrency`, is added to support internal use of multiprocess and multithreaded execution of workloads. This also migrates the use of `concurrent.futures` in `default.qubit` to this new design. [(#7303)](https://github.com/PennyLaneAI/pennylane/pull/7303) @@ -215,14 +285,30 @@

Documentation 📝

-* The entry in the :doc:`/news/program_capture_sharp_bits` page for using program capture with Catalyst +* The entry in the :doc:`/news/program_capture_sharp_bits` page for using program capture with Catalyst has been updated. Instead of using ``qjit(experimental_capture=True)``, Catalyst is now compatible - with the global toggles ``qml.capture.enable()`` and ``qml.capture.disable()`` for enabling and + with the global toggles ``qml.capture.enable()`` and ``qml.capture.disable()`` for enabling and disabling program capture. [(#7298)](https://github.com/PennyLaneAI/pennylane/pull/7298)

Bug fixes 🐛

+* Fixed a bug in the validation of :class:`~.SelectPauliRot` that prevents parameter broadcasting. + [(#7377)](https://github.com/PennyLaneAI/pennylane/pull/7377) + +* Usage of NumPy in `default.mixed` source code has been converted to `qml.math` to avoid + unnecessary dependency on NumPy and to fix a bug that caused an error when using `default.mixed` with PyTorch and GPUs. + [(#7384)](https://github.com/PennyLaneAI/pennylane/pull/7384) + +* With program capture enabled (`qml.capture.enable()`), `QSVT` no treats abstract values as metadata. + [(#7360)](https://github.com/PennyLaneAI/pennylane/pull/7360) + +* A fix was made to `default.qubit` to allow for using `qml.Snapshot` with defer-measurements (`mcm_method="deferred"`). + [(#7335)](https://github.com/PennyLaneAI/pennylane/pull/7335) + +* Fixes the repr for empty `Prod` and `Sum` instances to better communicate the existence of an empty instance. + [(#7346)](https://github.com/PennyLaneAI/pennylane/pull/7346) + * Fixes a bug where circuit execution fails with ``BlockEncode`` initialized with sparse matrices. [(#7285)](https://github.com/PennyLaneAI/pennylane/pull/7285) @@ -253,6 +339,25 @@ * Fixed a bug where the phase is used as the wire label for a `qml.GlobalPhase` when capture is enabled. [(#7211)](https://github.com/PennyLaneAI/pennylane/pull/7211) +* Fixed a bug that caused `CountsMP.process_counts` to return results in the computational basis, even if + an observable was specified. + [(#7342)](https://github.com/PennyLaneAI/pennylane/pull/7342) + +* Fixed a bug that caused `SamplesMP.process_counts` used with an observable to return a list of eigenvalues + for each individual operation in the observable, instead of the overall result. + [(#7342)](https://github.com/PennyLaneAI/pennylane/pull/7342) + +* Fixed a bug where `two_qubit_decomposition` provides an incorrect decomposition for some special matrices. + [(#7340)](https://github.com/PennyLaneAI/pennylane/pull/7340) + +* Fixes a bug where the powers of `qml.ISWAP` and `qml.SISWAP` were decomposed incorrectly. + [(#7361)](https://github.com/PennyLaneAI/pennylane/pull/7361) + +* Returning `MeasurementValue`s from the `ftqc` module's parametric mid-circuit measurements + (`measure_arbitrary_basis`, `measure_x` and `measure_y`) no longer raises an error in circuits + using `diagonalize_mcms`. + [(#7387)](https://github.com/PennyLaneAI/pennylane/pull/7387) +

Contributors ✍️

This release contains contributions from (in alphabetical order): @@ -264,5 +369,7 @@ Lillian Frederiksen, Pietropaolo Frisoni, Korbinian Kottmann, Christina Lee, +Andrija Paurevic, Lee J. O'Riordan, -Andrija Paurevic +David Wierichs, +Jake Zaia diff --git a/doc/requirements.txt b/doc/requirements.txt index 83204519130..260bd2b6d91 100644 --- a/doc/requirements.txt +++ b/doc/requirements.txt @@ -11,30 +11,29 @@ numpy pygments-github-lexers packaging scipy -docutils==0.16 -sphinx~=3.5.0; python_version < "3.10" -sphinx==4.2; python_version == "3.10" +docutils==0.20 +sphinx==8.1 sphinx-automodapi==0.13 sphinx-copybutton -sphinxcontrib-bibtex==2.4.2 -sphinxcontrib-applehelp==1.0.4 -sphinxcontrib-devhelp==1.0.2 -sphinxcontrib-qthelp==1.0.3 -sphinxcontrib-htmlhelp==2.0.1 -sphinxcontrib-serializinghtml==1.1.5 +sphinxcontrib-bibtex==2.6.3 +sphinxcontrib-applehelp==2.0.0 +sphinxcontrib-devhelp==2.0.0 +sphinxcontrib-qthelp==2.0.0 +sphinxcontrib-htmlhelp==2.1.0 +sphinxcontrib-serializinghtml==2.0.0 tensorflow-cpu==2.18.0; platform_machine == "x86_64" tensorflow_macos==2.9.0; sys_platform == "darwin" and platform_machine == "arm64" tensornetwork==0.3 tomlkit -torch==2.3.0 -jinja2==3.0.3 +torch==2.5.1 +jinja2>=3.1 rustworkx>=0.14.0 -networkx==2.6 -requests~=2.28.1 +networkx==3.1 +requests==2.32.3 # we do not pin the sphinx theme, to allow the # navbar and header data to be updated at the source pennylane-sphinx-theme tomli~=2.0.0 # Drop once minimum Python version is 3.11 -sphinxext-opengraph==0.6.3 # Note: latest version 0.9.0 requires Sphinx >=4.0 +sphinxext-opengraph==0.9.0 matplotlib==3.8.0 -diastatic-malt +diastatic-malt \ No newline at end of file diff --git a/pennylane/_deprecated_observable.py b/pennylane/_deprecated_observable.py new file mode 100644 index 00000000000..eb716f0350d --- /dev/null +++ b/pennylane/_deprecated_observable.py @@ -0,0 +1,101 @@ +# Copyright 2025 Xanadu Quantum Technologies Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +This module contains the deprecated Observable class to allow for deprecation warnings on access to the class. +""" +import warnings +from typing import Union + +import pennylane as qml + +from .operation import Operation, Operator + + +class Observable(Operator): + """Base class representing observables. + + .. warning:: + + ``qml.operation.Observable`` is now deprecated. A generic operator can be used anywhere an ``Observable`` + can, and is less restrictive. To preserve prior ``Observable`` default behavior, an operator can override + ``Operator.queue()`` with empty behavior, and set ``is_hermitian = True`` manually: + + .. code-block:: python + + class MyObs(Operator): + + is_hermitian = True + + def queue(self, context=qml.QueuingManager): + return self + + + + Args: + params (tuple[tensor_like]): trainable parameters + wires (Iterable[Any] or Any): Wi're label(s) that the operator acts on. + If not given, args[-1] is interpreted as wires. + id (str): custom label given to an operator instance, + can be useful for some applications where the instance has to be identified + """ + + @property + def _queue_category(self): + return "_ops" if isinstance(self, Operation) else None + + @property + def is_hermitian(self) -> bool: + """All observables must be hermitian""" + return True + + def compare( + self, + other: Union["Observable", "qml.ops.LinearCombination"], + ) -> bool: + r"""Compares with another :class:`~Observable`, to determine if they are equivalent. + + .. warning:: + + This method is deprecated. ``qml.equal`` or ``op1 == op2`` should be used instead. + + Observables are equivalent if they represent the same operator + (their matrix representations are equal), and they are defined on the same wires. + + .. Warning:: + + The compare method does **not** check if the matrix representation + of a :class:`~.Hermitian` observable is equal to an equivalent + observable expressed in terms of Pauli matrices. + To do so would require the matrix form to be calculated, which would + drastically increase runtime. + + Returns: + (bool): True if equivalent. + + **Examples** + + >>> ob1 = qml.X(0) @ qml.Identity(1) + >>> ob2 = qml.Hamiltonian([1], [qml.X(0)]) + >>> ob1.compare(ob2) + True + >>> ob1 = qml.X(0) + >>> ob2 = qml.Hermitian(np.array([[0, 1], [1, 0]]), 0) + >>> ob1.compare(ob2) + False + """ + warnings.warn( + "The compare method is deprecated and will be removed in v0.43." + " op1 == op2 or qml.equal should be used instead.", + qml.exceptions.PennyLaneDeprecationWarning, + ) + return qml.equal(self, other) diff --git a/pennylane/_version.py b/pennylane/_version.py index 723f7c8a66c..005f9b9a267 100644 --- a/pennylane/_version.py +++ b/pennylane/_version.py @@ -16,4 +16,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "0.42.0-dev16" +__version__ = "0.42.0-dev23" diff --git a/pennylane/capture/capture_operators.py b/pennylane/capture/capture_operators.py index 7ea77fca53f..9b458047751 100644 --- a/pennylane/capture/capture_operators.py +++ b/pennylane/capture/capture_operators.py @@ -15,14 +15,28 @@ This submodule defines the abstract classes and primitives for capturing operators. """ +import importlib.metadata as importlib_metadata +import warnings from functools import lru_cache from typing import Optional, Type +from packaging.version import Version + import pennylane as qml has_jax = True try: import jax + + jax_version = importlib_metadata.version("jax") + if Version(jax_version) > Version("0.4.28"): # pragma: no cover + warnings.warn( + f"PennyLane is not yet compatible with JAX versions > 0.4.28. " + f"You have version {jax_version} installed. " + f"Please downgrade JAX to <=0.4.28 to avoid runtime errors.", + RuntimeWarning, + ) + except ImportError: has_jax = False diff --git a/pennylane/circuit_graph.py b/pennylane/circuit_graph.py index 9aec185bba8..8909473c2b3 100644 --- a/pennylane/circuit_graph.py +++ b/pennylane/circuit_graph.py @@ -23,7 +23,7 @@ import rustworkx as rx from pennylane.measurements import MeasurementProcess -from pennylane.operation import Observable, Operator +from pennylane.operation import Operator from pennylane.ops.identity import I from pennylane.queuing import QueuingManager, WrappedObj from pennylane.resource import ResourcesOperation @@ -84,7 +84,7 @@ class CircuitGraph: Args: ops (Iterable[.Operator]): quantum operators constituting the circuit, in temporal order - obs (List[Union[MeasurementProcess, Observable]]): terminal measurements, in temporal order + obs (List[Union[MeasurementProcess, Operator]]): terminal measurements, in temporal order wires (.Wires): The addressable wire registers of the device that will be executing this graph par_info (Optional[list[dict]]): Parameter information. For each index, the entry is a dictionary containing an operation and an index into that operation's parameters. @@ -93,11 +93,11 @@ class CircuitGraph: quantum circuit. """ - # pylint: disable=too-many-arguments + # pylint: disable=too-many-arguments, too-many-positional-arguments def __init__( self, ops: list[Union[Operator, MeasurementProcess]], - obs: List[Union[MeasurementProcess, Observable]], + obs: List[Union[MeasurementProcess, Operator]], wires: Wires, par_info: Optional[list[dict]] = None, trainable_params: Optional[set[int]] = None, @@ -195,7 +195,7 @@ def observables_in_order(self): Currently the topological order is determined by the queue index. Returns: - List[Union[MeasurementProcess, Observable]]: observables + List[Union[MeasurementProcess, Operator]]: observables """ return self._observables diff --git a/pennylane/compiler/python_compiler/quantum_dialect.py b/pennylane/compiler/python_compiler/quantum_dialect.py new file mode 100644 index 00000000000..9b075963886 --- /dev/null +++ b/pennylane/compiler/python_compiler/quantum_dialect.py @@ -0,0 +1,669 @@ +# Copyright 2025 Xanadu Quantum Technologies Inc. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +This file contains the definition of the Quantum dialect for the Python compiler. + +The Quantum dialect is a set of operations and types used to represent quantum computations +in the xDSL framework. + +It was initially generated by xDSL (using the ``xdsl-tblgen`` tool) +starting from the catalyst/mlir/include/Quantum/IR/QuantumOps.td file in the catalyst repository. +""" + +# pylint: disable=too-few-public-methods + +# pragma: no cover + +from xdsl.dialects.builtin import ( + AnyAttr, + AnyOf, + BaseAttr, + Float64Type, + IntegerType, + StringAttr, + UnitAttr, +) +from xdsl.ir import Dialect, ParametrizedAttribute, TypeAttribute +from xdsl.irdl import ( + AttrSizedOperandSegments, + AttrSizedResultSegments, + EqAttrConstraint, + IRDLOperation, + irdl_attr_definition, + irdl_op_definition, + operand_def, + opt_operand_def, + opt_prop_def, + opt_result_def, + prop_def, + region_def, + result_def, + var_operand_def, + var_result_def, +) + + +@irdl_attr_definition +class ObservableType(ParametrizedAttribute, TypeAttribute): + """A quantum observable for use in measurements.""" + + name = "quantum.obs" + + +@irdl_attr_definition +class QubitType(ParametrizedAttribute, TypeAttribute): + """A value-semantic qubit (state).""" + + name = "quantum.bit" + + +@irdl_attr_definition +class QuregType(ParametrizedAttribute, TypeAttribute): + """An array of value-semantic qubits (i.e. quantum register).""" + + name = "quantum.reg" + + +@irdl_attr_definition +class ResultType(ParametrizedAttribute, TypeAttribute): + """A quantum measurement result.""" + + name = "quantum.res" + + +@irdl_attr_definition +class NamedObservableAttr(ParametrizedAttribute): + """Known named observables""" + + name = "quantum.named_observable" + + +@irdl_op_definition +class AdjointOp(IRDLOperation): + """Calculate the adjoint of the enclosed operations""" + + name = "quantum.adjoint" + + assembly_format = """ + `(` $qreg `)` attr-dict `:` type(operands) $region + """ + + qreg = operand_def(BaseAttr(QuregType)) + + out_qreg = result_def(BaseAttr(QuregType)) + + region = region_def("single_block") + + +@irdl_op_definition +class AllocOp(IRDLOperation): + """Allocate n qubits into a quantum register.""" + + name = "quantum.alloc" + + # assembly_format = """ + # `(` ($nqubits^):($nqubits_attr)? `)` attr-dict `:` type(results) + # """ + + nqubits = opt_operand_def(EqAttrConstraint(IntegerType(64))) + + nqubits_attr = opt_prop_def(AnyAttr()) + + qreg = result_def(BaseAttr(QuregType)) + + +@irdl_op_definition +class ComputationalBasisOp(IRDLOperation): + """Define a pseudo-obeservable of the computational basis for use in measurements""" + + name = "quantum.compbasis" + + assembly_format = """ + (`qubits` $qubits^)? (`qreg` $qreg^)? attr-dict `:` type(results) + """ + + irdl_options = [AttrSizedOperandSegments(as_property=True)] + + qubits = var_operand_def(BaseAttr(QubitType)) + + qreg = opt_operand_def(BaseAttr(QuregType)) + + obs = result_def(BaseAttr(ObservableType)) + + +@irdl_op_definition +class CountsOp(IRDLOperation): + """Compute sample counts for the given observable for the current state""" + + name = "quantum.counts" + + assembly_format = """ + $obs ( `shape` $dynamic_shape^ )? + ( `in` `(` $in_eigvals^ `:` type($in_eigvals) `,` $in_counts `:` type($in_counts) `)` )? + attr-dict ( `:` type($eigvals)^ `,` type($counts) )? + """ + + irdl_options = [ + AttrSizedOperandSegments(as_property=True), + AttrSizedResultSegments(as_property=True), + ] + + obs = operand_def(BaseAttr(ObservableType)) + + dynamic_shape = opt_operand_def(EqAttrConstraint(IntegerType(64))) + + in_eigvals = opt_operand_def(AnyAttr()) + + in_counts = opt_operand_def(AnyAttr()) + + eigvals = opt_result_def(AnyAttr()) + + counts = opt_result_def(AnyAttr()) + + +@irdl_op_definition +class CustomOp(IRDLOperation): + """A generic quantum gate on n qubits with m floating point parameters.""" + + name = "quantum.custom" + + # assembly_format = """ + # $gate_name `(` $params `)` $in_qubits attr-dict ( `ctrls` `(` $in_ctrl_qubits^ `)` )? ( `ctrlvals` `(` $in_ctrl_values^ `)` )? `:` type($out_qubits) (`ctrls` type($out_ctrl_qubits)^ )? + # """ + + irdl_options = [ + AttrSizedOperandSegments(as_property=True), + AttrSizedResultSegments(as_property=True), + ] + + params = var_operand_def(EqAttrConstraint(Float64Type())) + + in_qubits = var_operand_def(BaseAttr(QubitType)) + + gate_name = prop_def(BaseAttr(StringAttr)) + + adjoint = opt_prop_def(EqAttrConstraint(UnitAttr())) + + in_ctrl_qubits = var_operand_def(BaseAttr(QubitType)) + + in_ctrl_values = var_operand_def(EqAttrConstraint(IntegerType(1))) + + out_qubits = var_result_def(BaseAttr(QubitType)) + + out_ctrl_qubits = var_result_def(BaseAttr(QubitType)) + + +@irdl_op_definition +class DeallocOp(IRDLOperation): + """Deallocate a quantum register.""" + + name = "quantum.dealloc" + + assembly_format = """ + $qreg attr-dict `:` type(operands) + """ + + qreg = operand_def(BaseAttr(QuregType)) + + +@irdl_op_definition +class DeviceInitOp(IRDLOperation): + """Initialize a quantum device.""" + + name = "quantum.device_init" + + # assembly_format = """ + # (`shots` `(` $shots^ `)`)? `[` $lib `,` $name `,` $kwargs `]` attr-dict + # """ + + shots = opt_operand_def(EqAttrConstraint(IntegerType(64))) + + lib = prop_def(BaseAttr(StringAttr)) + + kwargs = prop_def(BaseAttr(StringAttr)) + + +@irdl_op_definition +class DeviceReleaseOp(IRDLOperation): + """Release the active quantum device.""" + + name = "quantum.device_release" + + assembly_format = """ + attr-dict + """ + + +@irdl_op_definition +class ExpvalOp(IRDLOperation): + """Compute the expectation value of the given observable for the current state""" + + name = "quantum.expval" + + assembly_format = """ + $obs attr-dict `:` type(results) + """ + + obs = operand_def(BaseAttr(ObservableType)) + + expval = result_def(EqAttrConstraint(Float64Type())) + + +@irdl_op_definition +class ExtractOp(IRDLOperation): + """Extract a qubit value from a register.""" + + name = "quantum.extract" + + # assembly_format = """ + # $qreg `[` ($idx^):($idx_attr)? `]` attr-dict `:` type($qreg) `->` type(results) + # """ + + qreg = operand_def(BaseAttr(QuregType)) + + idx = opt_operand_def(EqAttrConstraint(IntegerType(64))) + + idx_attr = opt_prop_def(AnyAttr()) + + qubit = result_def(BaseAttr(QubitType)) + + +@irdl_op_definition +class FinalizeOp(IRDLOperation): + """Teardown the quantum runtime.""" + + name = "quantum.finalize" + + assembly_format = """ + attr-dict + """ + + +@irdl_op_definition +class GlobalPhaseOp(IRDLOperation): + """Global Phase.""" + + name = "quantum.gphase" + + # assembly_format = """ + # `(` $params `)` attr-dict ( `ctrls` `(` $in_ctrl_qubits^ `)` )? ( `ctrlvals` `(` $in_ctrl_values^ `)` )? `:` (`ctrls` type($out_ctrl_qubits)^ )? + # """ + + irdl_options = [ + AttrSizedOperandSegments(as_property=True), + AttrSizedResultSegments(as_property=True), + ] + + params = operand_def(EqAttrConstraint(Float64Type())) + + adjoint = opt_prop_def(EqAttrConstraint(UnitAttr())) + + in_ctrl_qubits = var_operand_def(BaseAttr(QubitType)) + + in_ctrl_values = var_operand_def(EqAttrConstraint(IntegerType(1))) + + out_ctrl_qubits = var_result_def(BaseAttr(QubitType)) + + +@irdl_op_definition +class HamiltonianOp(IRDLOperation): + """Define a Hamiltonian observable for use in measurements""" + + name = "quantum.hamiltonian" + + assembly_format = """ + `(` $coeffs `:` type($coeffs) `)` $terms attr-dict `:` type(results) + """ + + coeffs = operand_def(AnyOf((AnyAttr(), AnyAttr()))) + + terms = var_operand_def(BaseAttr(ObservableType)) + + obs = result_def(BaseAttr(ObservableType)) + + +@irdl_op_definition +class HermitianOp(IRDLOperation): + """Define a Hermitian observable for use in measurements""" + + name = "quantum.hermitian" + + assembly_format = """ + `(` $matrix `:` type($matrix) `)` $qubits attr-dict `:` type(results) + """ + + matrix = operand_def(AnyOf((AnyAttr(), AnyAttr()))) + + qubits = var_operand_def(BaseAttr(QubitType)) + + obs = result_def(BaseAttr(ObservableType)) + + +@irdl_op_definition +class InitializeOp(IRDLOperation): + """Initialize the quantum runtime.""" + + name = "quantum.init" + + assembly_format = """ + attr-dict + """ + + +@irdl_op_definition +class InsertOp(IRDLOperation): + """Update the qubit value of a register.""" + + name = "quantum.insert" + + # assembly_format = """ + # $in_qreg `[` ($idx^):($idx_attr)? `]` `,` $qubit attr-dict `:` type($in_qreg) `,` type($qubit) + # """ + + in_qreg = operand_def(BaseAttr(QuregType)) + + idx = opt_operand_def(EqAttrConstraint(IntegerType(64))) + + idx_attr = opt_prop_def(AnyAttr()) + + qubit = operand_def(BaseAttr(QubitType)) + + out_qreg = result_def(BaseAttr(QuregType)) + + +@irdl_op_definition +class MeasureOp(IRDLOperation): + """A single-qubit projective measurement in the computational basis.""" + + name = "quantum.measure" + + # assembly_format = """ + # $in_qubit attr-dict `:` type(results) + # """ + + in_qubit = operand_def(BaseAttr(QubitType)) + + postselect = opt_prop_def(AnyAttr()) + + mres = result_def(EqAttrConstraint(IntegerType(1))) + + out_qubit = result_def(BaseAttr(QubitType)) + + +@irdl_op_definition +class MultiRZOp(IRDLOperation): + """Apply an arbitrary multi Z rotation""" + + name = "quantum.multirz" + + # assembly_format = """ + # `(` $theta `)` $in_qubits attr-dict ( `ctrls` `(` $in_ctrl_qubits^ `)` )? ( `ctrlvals` `(` $in_ctrl_values^ `)` )? `:` type($out_qubits) (`ctrls` type($out_ctrl_qubits)^ )? + # """ + + irdl_options = [ + AttrSizedOperandSegments(as_property=True), + AttrSizedResultSegments(as_property=True), + ] + + theta = operand_def(EqAttrConstraint(Float64Type())) + + in_qubits = var_operand_def(BaseAttr(QubitType)) + + adjoint = opt_prop_def(EqAttrConstraint(UnitAttr())) + + in_ctrl_qubits = var_operand_def(BaseAttr(QubitType)) + + in_ctrl_values = var_operand_def(EqAttrConstraint(IntegerType(1))) + + out_qubits = var_result_def(BaseAttr(QubitType)) + + out_ctrl_qubits = var_result_def(BaseAttr(QubitType)) + + +@irdl_op_definition +class NamedObsOp(IRDLOperation): + """Define a Named observable for use in measurements""" + + name = "quantum.namedobs" + + assembly_format = """ + $qubit `[` $type `]` attr-dict `:` type(results) + """ + + qubit = operand_def(BaseAttr(QubitType)) + + type = prop_def(BaseAttr(NamedObservableAttr)) + + obs = result_def(BaseAttr(ObservableType)) + + +@irdl_op_definition +class ProbsOp(IRDLOperation): + """Compute computational basis probabilities for the current state""" + + name = "quantum.probs" + + assembly_format = """ + $obs ( `shape` $dynamic_shape^ )? + ( `in` `(` $state_in^ `:` type($state_in) `)` )? + attr-dict ( `:` type($probabilities)^ )? + """ + + irdl_options = [ + AttrSizedOperandSegments(as_property=True), + AttrSizedResultSegments(as_property=True), + ] + + obs = operand_def(BaseAttr(ObservableType)) + + dynamic_shape = opt_operand_def(EqAttrConstraint(IntegerType(64))) + + state_in = opt_operand_def(AnyAttr()) + + probabilities = opt_result_def(AnyAttr()) + + +@irdl_op_definition +class QubitUnitaryOp(IRDLOperation): + """Apply an arbitrary fixed unitary matrix""" + + name = "quantum.unitary" + + # assembly_format = """ + # `(` $matrix `:` type($matrix) `)` $in_qubits attr-dict ( `ctrls` `(` $in_ctrl_qubits^ `)` )? ( `ctrlvals` `(` $in_ctrl_values^ `)` )? `:` type($out_qubits) (`ctrls` type($out_ctrl_qubits)^ )? + # """ + + irdl_options = [ + AttrSizedOperandSegments(as_property=True), + AttrSizedResultSegments(as_property=True), + ] + + matrix = operand_def(AnyOf((AnyAttr(), AnyAttr()))) + + in_qubits = var_operand_def(BaseAttr(QubitType)) + + adjoint = opt_prop_def(EqAttrConstraint(UnitAttr())) + + in_ctrl_qubits = var_operand_def(BaseAttr(QubitType)) + + in_ctrl_values = var_operand_def(EqAttrConstraint(IntegerType(1))) + + out_qubits = var_result_def(BaseAttr(QubitType)) + + out_ctrl_qubits = var_result_def(BaseAttr(QubitType)) + + +@irdl_op_definition +class SampleOp(IRDLOperation): + """Sample eigenvalues from the given observable for the current state""" + + name = "quantum.sample" + + assembly_format = """ + $obs ( `shape` $dynamic_shape^ )? + ( `in` `(` $in_data^ `:` type($in_data) `)` )? + attr-dict ( `:` type($samples)^ )? + """ + + irdl_options = [AttrSizedOperandSegments(as_property=True)] + + obs = operand_def(BaseAttr(ObservableType)) + + dynamic_shape = var_operand_def(EqAttrConstraint(IntegerType(64))) + + in_data = opt_operand_def(AnyOf((AnyAttr(), AnyAttr()))) + + samples = opt_result_def(AnyOf((AnyAttr(), AnyAttr()))) + + +@irdl_op_definition +class SetBasisStateOp(IRDLOperation): + """Set basis state.""" + + name = "quantum.set_basis_state" + + assembly_format = """ + `(` $basis_state`)` $in_qubits attr-dict `:` functional-type(operands, results) + """ + + basis_state = operand_def(AnyOf((AnyAttr(), AnyAttr()))) + + in_qubits = var_operand_def(BaseAttr(QubitType)) + + out_qubits = var_result_def(BaseAttr(QubitType)) + + +@irdl_op_definition +class SetStateOp(IRDLOperation): + """Set state to a complex vector.""" + + name = "quantum.set_state" + + assembly_format = """ + `(` $in_state `)` $in_qubits attr-dict `:` functional-type(operands, results) + """ + + in_state = operand_def(AnyOf((AnyAttr(), AnyAttr()))) + + in_qubits = var_operand_def(BaseAttr(QubitType)) + + out_qubits = var_result_def(BaseAttr(QubitType)) + + +@irdl_op_definition +class StateOp(IRDLOperation): + """Return the current statevector""" + + name = "quantum.state" + + assembly_format = """ + $obs ( `shape` $dynamic_shape^ )? + ( `in` `(` $state_in^ `:` type($state_in) `)` )? + attr-dict ( `:` type($state)^ )? + """ + + irdl_options = [AttrSizedOperandSegments(as_property=True)] + + obs = operand_def(BaseAttr(ObservableType)) + + dynamic_shape = opt_operand_def(EqAttrConstraint(IntegerType(64))) + + state_in = opt_operand_def(AnyAttr()) + + state = opt_result_def(AnyAttr()) + + +@irdl_op_definition +class TensorOp(IRDLOperation): + """Define a tensor product of observables for use in measurements""" + + name = "quantum.tensor" + + assembly_format = """ + $terms attr-dict `:` type(results) + """ + + terms = var_operand_def(BaseAttr(ObservableType)) + + obs = result_def(BaseAttr(ObservableType)) + + +@irdl_op_definition +class VarianceOp(IRDLOperation): + """Compute the variance of the given observable for the current state""" + + name = "quantum.var" + + assembly_format = """ + $obs attr-dict `:` type(results) + """ + + obs = operand_def(BaseAttr(ObservableType)) + + variance = result_def(EqAttrConstraint(Float64Type())) + + +@irdl_op_definition +class YieldOp(IRDLOperation): + """Return results from quantum program regions""" + + name = "quantum.yield" + + # assembly_format = """ + # attr-dict ($results^ `:` type($results))? + # """ + + results = var_operand_def(BaseAttr(QuregType)) + + +QuantumDialect = Dialect( + "quantum", + [ + AdjointOp, + AllocOp, + ComputationalBasisOp, + CountsOp, + CustomOp, + DeallocOp, + DeviceInitOp, + DeviceReleaseOp, + ExpvalOp, + ExtractOp, + FinalizeOp, + GlobalPhaseOp, + HamiltonianOp, + HermitianOp, + InitializeOp, + InsertOp, + MeasureOp, + MultiRZOp, + NamedObsOp, + ProbsOp, + QubitUnitaryOp, + SampleOp, + SetBasisStateOp, + SetStateOp, + StateOp, + TensorOp, + VarianceOp, + YieldOp, + ], + [ + ObservableType, + QubitType, + QuregType, + ResultType, + NamedObservableAttr, + ], +) diff --git a/pennylane/data/base/attribute.py b/pennylane/data/base/attribute.py index 5655066e2a6..803d67fb564 100644 --- a/pennylane/data/base/attribute.py +++ b/pennylane/data/base/attribute.py @@ -35,14 +35,15 @@ class AttributeInfo(MutableMapping): attribute. Is stored in the HDF5 object's ``attrs`` dict. Attributes: - attrs_bind: The HDF5 attrs dict that this instance is bound to, - or any mutable mapping **kwargs: Extra metadata to include. Must be a string, number or numpy array """ attrs_namespace: ClassVar[str] = "qml.data" attrs_bind: MutableMapping[str, Any] + """The HDF5 attrs dict that this instance is bound to, + or any mutable mapping + """ @overload def __init__( # overload to specify known keyword args @@ -157,13 +158,10 @@ class DatasetAttribute(ABC, Generic[HDF5, ValueType, InitValueType]): The DatasetAttribute class provides an interface for converting Python objects to and from a HDF5 array or Group. It uses the registry pattern to maintain a mapping of type_id to DatasetAttribute, and Python types to compatible AttributeTypes. - - Attributes: - type_id: Unique identifier for this DatasetAttribute class. Must be declared - in subclasses. """ type_id: ClassVar[str] + """Unique identifier for this DatasetAttribute class. Must be declared in subclasses.""" @abstractmethod def hdf5_to_value(self, bind: HDF5) -> ValueType: diff --git a/pennylane/debugging/debugger.py b/pennylane/debugging/debugger.py index 190f3f696b2..be0765e21fa 100644 --- a/pennylane/debugging/debugger.py +++ b/pennylane/debugging/debugger.py @@ -354,7 +354,7 @@ def debug_probs(wires=None, op=None): Args: wires (Union[Iterable, int, str, list]): the wires the operation acts on - op (Union[Observable, MeasurementValue]): observable (with a ``diagonalizing_gates`` + op (Union[Operator, MeasurementValue]): an observable (with a ``diagonalizing_gates`` attribute) that rotates the computational basis, or a ``MeasurementValue`` corresponding to mid-circuit measurements. diff --git a/pennylane/debugging/snapshot.py b/pennylane/debugging/snapshot.py index 430ba08f494..4d01cdeafc7 100644 --- a/pennylane/debugging/snapshot.py +++ b/pennylane/debugging/snapshot.py @@ -101,11 +101,17 @@ def circuit(): qml.Hadamard(wires=0) qml.CNOT(wires=[0, 1]) qml.Snapshot() + qml.Snapshot("sample", measurement=qml.sample(), shots=5) return qml.expval(qml.X(0)) >>> qml.snapshots(circuit)() {0: 1.0, 1: array([0.70710678+0.j, 0. +0.j, 0. +0.j, 0.70710678+0.j]), + 'sample': array([[0, 0], + [1, 1], + [1, 1], + [0, 0], + [1, 1]]), 'execution_results': 0.0} .. code-block:: python3 @@ -179,8 +185,6 @@ def circuit(): [, , , ] """ - qml.devices.preprocess.validate_measurements(tape) - new_tapes = [] accumulated_ops = [] snapshot_tags = [] @@ -189,7 +193,14 @@ def circuit(): if isinstance(op, qml.Snapshot): snapshot_tags.append(op.tag or len(new_tapes)) meas_op = op.hyperparameters["measurement"] - new_tapes.append(tape.copy(operations=accumulated_ops, measurements=[meas_op])) + shots = ( + tape.shots + if op.hyperparameters["shots"] == "workflow" + else op.hyperparameters["shots"] + ) + new_tapes.append( + tape.copy(operations=accumulated_ops, measurements=[meas_op], shots=shots) + ) else: accumulated_ops.append(op) @@ -213,22 +224,11 @@ def snapshots_qnode(self, qnode, targs, tkwargs): """ def get_snapshots(*args, **kwargs): - # Need to construct to generate the tape and be able to validate - tape = qml.workflow.construct_tape(qnode)(*args, **kwargs) - qml.devices.preprocess.validate_measurements(tape) - - old_interface = qnode.interface - if old_interface == "auto": - qnode.interface = qml.math.get_interface(*args, *list(kwargs.values())) with _SnapshotDebugger(qnode.device) as dbg: # pylint: disable=protected-access results = qnode(*args, **kwargs) - # Reset interface - if old_interface == "auto": - qnode.interface = "auto" - dbg.snapshots["execution_results"] = results return dbg.snapshots diff --git a/pennylane/devices/_legacy_device.py b/pennylane/devices/_legacy_device.py index 47cff19437a..bca46fa9f42 100644 --- a/pennylane/devices/_legacy_device.py +++ b/pennylane/devices/_legacy_device.py @@ -35,7 +35,7 @@ StateMP, VarianceMP, ) -from pennylane.operation import Observable, Operation, Operator, StatePrepBase +from pennylane.operation import Operation, Operator, StatePrepBase from pennylane.ops import LinearCombination, Prod, SProd, Sum from pennylane.queuing import QueuingManager from pennylane.tape import QuantumScript, expand_tape_state_prep @@ -425,7 +425,7 @@ def execute(self, queue, observables, parameters=None, **kwargs): Args: queue (Iterable[~.operation.Operation]): operations to execute on the device - observables (Iterable[~.operation.Observable]): observables to measure and return + observables (Iterable[~.operation.Operator]): observables to measure and return parameters (dict[int, list[ParameterDependency]]): Mapping from free parameter index to the list of :class:`Operations ` (in the queue) that depend on it. @@ -858,7 +858,7 @@ def obs_queue(self): ValueError: if outside of the execution context Returns: - list[~.operation.Observable] + list[~.operation.Operator] """ if self._obs_queue is None: raise ValueError( @@ -951,18 +951,18 @@ def supports_observable(self, observable): observable (type or str): observable to be checked Raises: - ValueError: if `observable` is not a :class:`~.Observable` class or string + ValueError: if `observable` is not a :class:`~.Operator` class or string Returns: bool: ``True`` iff supplied observable is supported """ - if isinstance(observable, type) and issubclass(observable, Observable): + if isinstance(observable, type) and issubclass(observable, Operator): return observable.__name__ in self.observables if isinstance(observable, str): return observable in self.observables raise ValueError( - "The given observable must either be a pennylane.Observable class or a string." + "The given observable must either be a pennylane.operation.Operator class or a string." ) def check_validity(self, queue, observables): @@ -971,7 +971,7 @@ def check_validity(self, queue, observables): Args: queue (Iterable[~.operation.Operation]): quantum operation objects which are intended to be applied on the device - observables (Iterable[~.operation.Observable]): observables which are intended + observables (Iterable[~.operation.Operator]): observables which are intended to be evaluated on the device Raises: diff --git a/pennylane/devices/_qubit_device.py b/pennylane/devices/_qubit_device.py index c8351a0f8be..015bb17c1de 100644 --- a/pennylane/devices/_qubit_device.py +++ b/pennylane/devices/_qubit_device.py @@ -1513,7 +1513,7 @@ def sample(self, observable, shot_range=None, bin_size=None, counts=False): """Return samples of an observable. Args: - observable (Observable): the observable to sample + observable (Operator): the observable to sample shot_range (tuple[int]): 2-tuple of integers specifying the range of samples to use. If not specified, all samples are used. bin_size (int): Divides the shot range into bins of size ``bin_size``, and diff --git a/pennylane/devices/default_mixed.py b/pennylane/devices/default_mixed.py index 7d6dc5591b9..ef72afc10e7 100644 --- a/pennylane/devices/default_mixed.py +++ b/pennylane/devices/default_mixed.py @@ -22,8 +22,6 @@ # pylint: disable=wrong-import-order, ungrouped-imports import logging -import numpy as np - import pennylane as qml from pennylane.math import get_canonical_interface_name @@ -237,13 +235,13 @@ def __init__( # pylint: disable=too-many-arguments super().__init__(wires=wires, shots=shots) # Seed setting - seed = np.random.randint(0, high=10000000) if seed == "global" else seed + seed = qml.math.random.randint(0, high=10000000) if seed == "global" else seed if qml.math.get_interface(seed) == "jax": self._prng_key = seed - self._rng = np.random.default_rng(None) + self._rng = qml.math.random.default_rng(None) else: self._prng_key = None - self._rng = np.random.default_rng(seed) + self._rng = qml.math.random.default_rng(seed) self._debugger = None diff --git a/pennylane/devices/default_qubit.py b/pennylane/devices/default_qubit.py index 85986066347..5a223341f9f 100644 --- a/pennylane/devices/default_qubit.py +++ b/pennylane/devices/default_qubit.py @@ -25,6 +25,7 @@ import numpy as np import pennylane as qml +from pennylane import math from pennylane.logging import debug_logger, debug_logger_init from pennylane.measurements import ClassicalShadowMP, ShadowExpvalMP from pennylane.measurements.mid_measure import MidMeasureMP @@ -63,7 +64,7 @@ def stopping_condition(op: qml.operation.Operator) -> bool: return False if op.name == "Snapshot": return True - if op.__class__.__name__[:3] == "Pow" and qml.operation.is_trainable(op): + if op.__class__.__name__[:3] == "Pow" and any(math.requires_grad(d) for d in op.data): return False if op.name == "FromBloq" and len(op.wires) > 3: return False @@ -220,7 +221,7 @@ def adjoint_ops(op: qml.operation.Operator) -> bool: """Specify whether or not an Operator is supported by adjoint differentiation.""" return not isinstance(op, (Conditional, MidMeasureMP)) and ( op.num_params == 0 - or not qml.operation.is_trainable(op) + or not any(math.requires_grad(d) for d in op.data) or (op.num_params == 1 and op.has_generator) ) @@ -560,10 +561,11 @@ def preprocess( if config.interface == qml.math.Interface.JAX_JIT: transform_program.add_transform(no_counts) - transform_program.add_transform(validate_device_wires, self.wires, name=self.name) transform_program.add_transform( mid_circuit_measurements, device=self, mcm_config=config.mcm_config ) + # validate_device_wires needs to be after defer_measurement has added more wires. + transform_program.add_transform(validate_device_wires, self.wires, name=self.name) transform_program.add_transform( decompose, stopping_condition=stopping_condition, diff --git a/pennylane/devices/default_tensor.py b/pennylane/devices/default_tensor.py index 7eb71546574..303ff525a36 100644 --- a/pennylane/devices/default_tensor.py +++ b/pennylane/devices/default_tensor.py @@ -41,7 +41,7 @@ StateMP, VarianceMP, ) -from pennylane.operation import Observable, Operation +from pennylane.operation import Operation, Operator from pennylane.ops import LinearCombination, Prod, SProd, Sum from pennylane.tape import QuantumScript, QuantumScriptOrBatch from pennylane.templates.subroutines.trotter import _recursive_expression @@ -1044,7 +1044,7 @@ def apply_operation_core_trotter_product(ops: qml.TrotterProduct, device): @singledispatch -def expval_core(obs: Observable, device) -> float: +def expval_core(obs: Operator, device) -> float: """Dispatcher for expval.""" return device._local_expectation(qml.matrix(obs), tuple(obs.wires)) diff --git a/pennylane/devices/legacy_facade.py b/pennylane/devices/legacy_facade.py index 9b0a7231bdb..76273405a4e 100644 --- a/pennylane/devices/legacy_facade.py +++ b/pennylane/devices/legacy_facade.py @@ -22,7 +22,7 @@ from dataclasses import replace import pennylane as qml -from pennylane.math import get_canonical_interface_name +from pennylane.math import get_canonical_interface_name, requires_grad from pennylane.measurements import MidMeasureMP, Shots from pennylane.transforms.core.transform_program import TransformProgram @@ -101,7 +101,7 @@ def legacy_device_batch_transform(tape, device): def adjoint_ops(op: qml.operation.Operator) -> bool: """Specify whether or not an Operator is supported by adjoint differentiation.""" - if isinstance(op, qml.QubitUnitary) and not qml.operation.is_trainable(op): + if isinstance(op, qml.QubitUnitary) and not any(requires_grad(d) for d in op.data): return True return not isinstance(op, MidMeasureMP) and ( op.num_params == 0 or (op.num_params == 1 and op.has_generator) diff --git a/pennylane/devices/null_qubit.py b/pennylane/devices/null_qubit.py index 5ced33798c8..285f6f8d36e 100644 --- a/pennylane/devices/null_qubit.py +++ b/pennylane/devices/null_qubit.py @@ -18,7 +18,9 @@ # pylint:disable=unused-argument import inspect +import json import logging +from collections import defaultdict from dataclasses import replace from functools import lru_cache, singledispatch from numbers import Number @@ -37,6 +39,7 @@ ProbabilityMP, StateMP, ) +from pennylane.ops.op_math import Adjoint, Controlled, ControlledOp from pennylane.tape import QuantumScriptOrBatch from pennylane.transforms.core import TransformProgram from pennylane.typing import Result, ResultBatch @@ -129,6 +132,50 @@ def _interface(config: ExecutionConfig): return config.interface.get_like() if config.gradient_method == "backprop" else "numpy" +def _simulate_resource_use(circuit, resources_fname="__pennylane_resources_data.json"): + num_wires = len(circuit.wires) + gate_types = defaultdict(int) + + for op in circuit.operations: + controls = 0 + adj = False + + while hasattr(op, "base"): + if type(op) in (Controlled, ControlledOp): + # Don't check this with `isinstance` to avoid unrolling ops like CNOT + controls += len(op.control_wires) + elif isinstance(op, Adjoint): + adj = not adj + else: + break # Certain gates have "base" but shouldn't be broken down (like CNOT) + # NOTE: Pow, Exp, Add, etc. intentionally not handled to be compatible with Catalyst + op = op.base + + # Barrier is not a gate and takes no resources, so we skip it + if op.name == "Barrier": + # (this confuses codecov, despite being tested) + continue # pragma: no cover + + name = op.name + if adj: + name = f"Adj({name})" + if controls: + name = f"{controls if controls > 1 else ''}C({name})" + + gate_types[name] += 1 + # NOTE: For now, this information is being printed to match the behavior of catalyst resource tracking. + # In the future it may be better to return this information in a more structured way. + with open(resources_fname, "w") as f: + json.dump( + { + "num_wires": num_wires, + "num_gates": sum(gate_types.values()), + "gate_types": gate_types, + }, + f, + ) + + @simulator_tracking @single_tape_support class NullQubit(Device): @@ -141,7 +188,7 @@ class NullQubit(Device): (``['aux_wire', 'q1', 'q2']``). Default ``None`` if not specified. shots (int, Sequence[int], Sequence[Union[int, Sequence[int]]]): The default number of shots to use in executions involving this device. - + track_resources (bool): If ``True``, track the number of resources used by the device. This argument is experimental and subject to change. **Example:** .. code-block:: python @@ -225,13 +272,21 @@ def name(self): """The name of the device.""" return "null.qubit" - def __init__(self, wires=None, shots=None) -> None: + def __init__(self, wires=None, shots=None, track_resources=False) -> None: super().__init__(wires=wires, shots=shots) self._debugger = None + self._track_resources = track_resources + + # this is required by Catalyst to toggle the tracker at runtime + self.device_kwargs = {"track_resources": track_resources} def _simulate(self, circuit, interface): num_device_wires = len(self.wires) if self.wires else len(circuit.wires) results = [] + + if self._track_resources: + _simulate_resource_use(circuit) + for s in circuit.shots or [None]: r = tuple( zero_measurement(mp, num_device_wires, s, circuit.batch_size, interface) diff --git a/pennylane/devices/preprocess.py b/pennylane/devices/preprocess.py index 520f2dfc913..d709b635593 100644 --- a/pennylane/devices/preprocess.py +++ b/pennylane/devices/preprocess.py @@ -20,11 +20,11 @@ import warnings from collections.abc import Callable, Generator, Sequence from copy import copy -from itertools import chain from typing import Optional, Type import pennylane as qml from pennylane import Snapshot, transform +from pennylane.math import requires_grad from pennylane.measurements import SampleMeasurement, StateMeasurement from pennylane.operation import StatePrepBase from pennylane.tape import QuantumScript, QuantumScriptBatch @@ -161,7 +161,9 @@ def validate_device_wires( modified = True new_mp = copy(mp) new_mp._wires = wires # pylint:disable=protected-access - new_ops[i] = qml.Snapshot(measurement=new_mp, tag=op.tag) + new_ops[i] = qml.Snapshot( + measurement=new_mp, tag=op.tag, shots=op.hyperparameters["shots"] + ) if not new_ops: new_ops = tape.operations # no copy in this case @@ -277,13 +279,13 @@ def validate_adjoint_trainable_params( """ for op in tape.operations[: tape.num_preps]: - if qml.operation.is_trainable(op): + if any(requires_grad(d) for d in op.data): raise qml.QuantumFunctionError( "Differentiating with respect to the input parameters of state-prep operations " "is not supported with the adjoint differentiation method." ) for m in tape.measurements: - if m.obs and qml.operation.is_trainable(m.obs): + if m.obs and any(requires_grad(d) for d in m.obs.data): warnings.warn( f"Differentiating with respect to the input parameters of {m.obs.name} " "is not supported with the adjoint differentiation method. Gradients are computed " @@ -509,28 +511,38 @@ def analytic_measurements(m): def sample_measurements(m): return isinstance(m, SampleMeasurement) - # Gather all the measurements present in the snapshot operations with the - # exception of `qml.state` as this is supported for any supported simulator regardless - # of its configuration - snapshot_measurements = [ - meas - for op in tape.operations - if isinstance(op, qml.Snapshot) - and not isinstance(meas := op.hyperparameters["measurement"], qml.measurements.StateMP) - ] - - shots = qml.measurements.Shots(tape.shots) - - if shots.total_shots is not None: - for m in chain(snapshot_measurements, tape.measurements): + if tape.shots: + for m in tape.measurements: if not sample_measurements(m): raise qml.DeviceError(f"Measurement {m} not accepted with finite shots on {name}") - else: - for m in chain(snapshot_measurements, tape.measurements): + for m in tape.measurements: if not analytic_measurements(m): raise qml.DeviceError( f"Measurement {m} not accepted for analytic simulation on {name}." ) + _validate_snapshot_shots(tape, sample_measurements, analytic_measurements, name) + return (tape,), null_postprocessing + + +def _validate_snapshot_shots(tape, sample_measurements, analytic_measurements, name): + for op in tape.operations: + if isinstance(op, qml.Snapshot): + shots = ( + tape.shots + if op.hyperparameters["shots"] == "workflow" + else op.hyperparameters["shots"] + ) + m = op.hyperparameters["measurement"] + if shots: + if not sample_measurements(m): + raise qml.DeviceError( + f"Measurement {m} not accepted with finite shots on {name}" + ) + else: + if not analytic_measurements(m): + raise qml.DeviceError( + f"Measurement {m} not accepted for analytic simulation on {name}." + ) diff --git a/pennylane/devices/qubit/apply_operation.py b/pennylane/devices/qubit/apply_operation.py index 176e237ee38..b80c507dac4 100644 --- a/pennylane/devices/qubit/apply_operation.py +++ b/pennylane/devices/qubit/apply_operation.py @@ -630,27 +630,30 @@ def apply_snapshot( op: qml.Snapshot, state, is_state_batched: bool = False, debugger=None, **execution_kwargs ): """Take a snapshot of the state.""" - if debugger is not None and debugger.active: - measurement = op.hyperparameters["measurement"] - + if debugger is None or not debugger.active: + return state + measurement = op.hyperparameters["measurement"] + if op.hyperparameters["shots"] == "workflow": shots = execution_kwargs.get("tape_shots") + else: + shots = op.hyperparameters["shots"] - if isinstance(measurement, qml.measurements.StateMP) or not shots: - snapshot = qml.devices.qubit.measure(measurement, state, is_state_batched) - else: - snapshot = qml.devices.qubit.measure_with_samples( - [measurement], - state, - shots, - is_state_batched, - execution_kwargs.get("rng"), - execution_kwargs.get("prng_key"), - )[0] - - if op.tag: - debugger.snapshots[op.tag] = snapshot - else: - debugger.snapshots[len(debugger.snapshots)] = snapshot + if shots: + snapshot = qml.devices.qubit.measure_with_samples( + [measurement], + state, + shots, + is_state_batched, + execution_kwargs.get("rng"), + execution_kwargs.get("prng_key"), + )[0] + else: + snapshot = qml.devices.qubit.measure(measurement, state, is_state_batched) + + if op.tag: + debugger.snapshots[op.tag] = snapshot + else: + debugger.snapshots[len(debugger.snapshots)] = snapshot return state diff --git a/pennylane/devices/qubit_mixed/apply_operation.py b/pennylane/devices/qubit_mixed/apply_operation.py index 836219bd62a..46ac8c9b97d 100644 --- a/pennylane/devices/qubit_mixed/apply_operation.py +++ b/pennylane/devices/qubit_mixed/apply_operation.py @@ -28,7 +28,7 @@ from .einsum_manpulation import get_einsum_mapping -alphabet_array = np.array(list(alphabet)) +alphabet_array = math.array(list(alphabet)) TENSORDOT_STATE_NDIM_PERF_THRESHOLD = 9 @@ -678,7 +678,10 @@ def apply_snapshot( measurement = op.hyperparameters.get( "measurement", None ) # default: None, meaning no measurement, simply copy the state - shots = execution_kwargs.get("tape_shots", None) # default: None, analytic + if op.hyperparameters["shots"] == "workflow": + shots = execution_kwargs.get("tape_shots") + else: + shots = op.hyperparameters["shots"] if isinstance(measurement, qml.measurements.StateMP) or not shots: snapshot = qml.devices.qubit_mixed.measure(measurement, state, is_state_batched) @@ -690,7 +693,7 @@ def apply_snapshot( is_state_batched, execution_kwargs.get("rng"), execution_kwargs.get("prng_key"), - ) + )[0] # Store snapshot with optional tag if op.tag: diff --git a/pennylane/devices/qubit_mixed/einsum_manpulation.py b/pennylane/devices/qubit_mixed/einsum_manpulation.py index bd69a008078..f61f0bce709 100644 --- a/pennylane/devices/qubit_mixed/einsum_manpulation.py +++ b/pennylane/devices/qubit_mixed/einsum_manpulation.py @@ -17,9 +17,8 @@ import pennylane as qml from pennylane import math -from pennylane import numpy as np -alphabet_array = np.array(list(alphabet)) +alphabet_array = math.array(list(alphabet)) def get_einsum_mapping( diff --git a/pennylane/devices/qubit_mixed/initialize_state.py b/pennylane/devices/qubit_mixed/initialize_state.py index be3dbd5a7d6..3a9563aa473 100644 --- a/pennylane/devices/qubit_mixed/initialize_state.py +++ b/pennylane/devices/qubit_mixed/initialize_state.py @@ -17,7 +17,6 @@ from typing import Union import pennylane as qml -import pennylane.numpy as np from pennylane import math @@ -44,7 +43,7 @@ def create_initial_state( 2 * num_wires ) # we initialize the density matrix as the tensor form to keep compatibility with the rest of the module if not prep_operation: - state = np.zeros((2,) * num_axes, dtype=complex) + state = math.zeros((2,) * num_axes, dtype=complex) state[(0,) * num_axes] = 1 return math.asarray(state, like=like) @@ -60,9 +59,9 @@ def create_initial_state( ) # don't assume the expected shape to be fixed if batch_size is None: is_state_batched = False - density_matrix = np.outer(pure_state, np.conj(pure_state)) + density_matrix = _flatten_outer(pure_state) else: - density_matrix = math.stack([np.outer(s, np.conj(s)) for s in pure_state]) + density_matrix = math.stack([_flatten_outer(s) for s in pure_state]) return _post_process(density_matrix, num_axes, like, is_state_batched) @@ -73,11 +72,17 @@ def _post_process(density_matrix, num_axes, like, is_state_batched=True): matrix form, as requested by all the other more fundamental chore functions in the module (again from some legacy code). """ - density_matrix = np.reshape(density_matrix, (-1,) + (2,) * num_axes) + density_matrix = math.reshape(density_matrix, (-1,) + (2,) * num_axes) dtype = str(density_matrix.dtype) floating_single = "float32" in dtype or "complex64" in dtype dtype = "complex64" if floating_single else "complex128" dtype = "complex128" if like == "tensorflow" else dtype if not is_state_batched: - density_matrix = np.reshape(density_matrix, (2,) * num_axes) + density_matrix = math.reshape(density_matrix, (2,) * num_axes) return math.cast(math.asarray(density_matrix, like=like), dtype) + + +def _flatten_outer(s): + r"""Flattens the outer product of a vector.""" + s_flatten = math.flatten(s) + return math.outer(s_flatten, math.conj(s_flatten)) diff --git a/pennylane/devices/qubit_mixed/sampling.py b/pennylane/devices/qubit_mixed/sampling.py index 22cd51631c0..d1047985e59 100644 --- a/pennylane/devices/qubit_mixed/sampling.py +++ b/pennylane/devices/qubit_mixed/sampling.py @@ -207,17 +207,17 @@ def process_state_with_shots(mp, state, wire_order, shots, rng=None, is_state_ba # seed the random measurement generation so that recipes # are the same for different executions with the same seed seed = mp.seed - recipe_rng = np.random.RandomState(seed) + recipe_rng = math.random.RandomState(seed) recipes = recipe_rng.randint(0, 3, size=(n_snapshots, n_qubits)) - outcomes = np.zeros((n_snapshots, n_qubits)) + outcomes = math.zeros((n_snapshots, n_qubits)) # Single-qubit diagonalizing ops for X, Y, Z diag_list = [ qml.Hadamard.compute_matrix(), # X qml.Hadamard.compute_matrix() @ qml.RZ.compute_matrix(-np.pi / 2), # Y qml.Identity.compute_matrix(), # Z ] - bit_rng = np.random.default_rng(rng) + bit_rng = math.random.default_rng(rng) for t in range(n_snapshots): for q_idx, q_wire in enumerate(mapped_wires): @@ -235,13 +235,13 @@ def process_state_with_shots(mp, state, wire_order, shots, rng=None, is_state_ba rotated = math.dot(U, math.dot(rho_q, U_dag)) # (C) probability of outcome 0 => rotated[0,0].real - p0 = np.clip(math.real(rotated[0, 0]), 0.0, 1.0) + p0 = math.clip(math.real(rotated[0, 0]), 0.0, 1.0) if bit_rng.random() < p0: outcomes[t, q_idx] = 0 else: outcomes[t, q_idx] = 1 - res = np.stack([outcomes, recipes]).astype(np.int8) + res = math.stack([outcomes, recipes]).astype(np.int8) return res diff --git a/pennylane/devices/qutrit_mixed/apply_operation.py b/pennylane/devices/qutrit_mixed/apply_operation.py index 48c07e4ff6e..a8eebf9b826 100644 --- a/pennylane/devices/qutrit_mixed/apply_operation.py +++ b/pennylane/devices/qutrit_mixed/apply_operation.py @@ -179,7 +179,10 @@ def apply_snapshot( if debugger and debugger.active: measurement = op.hyperparameters["measurement"] - shots = execution_kwargs.get("tape_shots") + if op.hyperparameters["shots"] == "workflow": + shots = execution_kwargs.get("tape_shots") + else: + shots = op.hyperparameters["shots"] if isinstance(measurement, qml.measurements.StateMP) or not shots: snapshot = qml.devices.qutrit_mixed.measure(measurement, state, is_state_batched) diff --git a/pennylane/fourier/reconstruct.py b/pennylane/fourier/reconstruct.py index afc7e1198de..78b5bd26fb1 100644 --- a/pennylane/fourier/reconstruct.py +++ b/pennylane/fourier/reconstruct.py @@ -393,9 +393,9 @@ def reconstruct(qnode, ids=None, nums_frequency=None, spectra=None, shifts=None) `Wierichs, Izaac, Wang and Lin (2022) `__ . An introduction to the concept of quantum circuits as Fourier series can also be found in the - :doc:`Quantum models as Fourier series ` + `Quantum models as Fourier series `__ and - :doc:`General parameter-shift rules ` + `General parameter-shift rules `__ demos as well as the :mod:`qml.fourier ` module docstring. diff --git a/pennylane/ftqc/__init__.py b/pennylane/ftqc/__init__.py index 6817153eafe..868dc1e8851 100644 --- a/pennylane/ftqc/__init__.py +++ b/pennylane/ftqc/__init__.py @@ -39,7 +39,7 @@ ) from .conditional_measure import cond_measure from .lattice import Lattice, generate_lattice -from .graph_state_preparation import GraphStatePrep +from .graph_state_preparation import GraphStatePrep, make_graph_state from .qubit_graph import QubitGraph from .utils import QubitMgr @@ -60,4 +60,5 @@ "diagonalize_mcms", "convert_to_mbqc_gateset", "generate_lattice", + "make_graph_state", ] diff --git a/pennylane/ftqc/conditional_measure.py b/pennylane/ftqc/conditional_measure.py index 80ab417a0d9..ad83aa31865 100644 --- a/pennylane/ftqc/conditional_measure.py +++ b/pennylane/ftqc/conditional_measure.py @@ -17,9 +17,10 @@ from functools import wraps from typing import Callable, Union -import pennylane as qml +from pennylane import capture from pennylane.measurements import MeasurementValue, MidMeasureMP -from pennylane.ops.op_math.condition import CondCallable, Conditional +from pennylane.ops.op_math.condition import CondCallable, Conditional, cond +from pennylane.queuing import QueuingManager def cond_measure( @@ -31,11 +32,6 @@ def cond_measure( supplied expression. This conditional expression may involve the results of other mid-circuit qubit measurements. - .. note:: - - This function is currently not compatible with :func:`~.qjit`, or with - :func:`.pennylane.capture.enabled`. - Args: condition (Union[.MeasurementValue, bool]): a conditional expression that may involve a mid-circuit measurement value (see :func:`.pennylane.measure`). @@ -49,7 +45,6 @@ def cond_measure( wire, and they must have the same settings for `reset` and `postselection`. The two branches can differ only in regard to the measurement basis of the applied measurement. - Returns: function: A new function that applies the conditional measurements. The returned function takes the same input arguments as ``true_fn`` and ``false_fn``. @@ -94,10 +89,8 @@ def qnode(x, y): While such statements may not result in errors, they may result in incorrect behaviour. """ - if qml.capture.enabled(): - raise NotImplementedError( - "The `cond_measure` function is not compatible with program capture" - ) + if capture.enabled(): + cond(condition, true_fn, false_fn) if not isinstance(condition, MeasurementValue): # The condition is not a mid-circuit measurement - we can simplify immediately @@ -112,7 +105,7 @@ def qnode(x, y): @wraps(true_fn) def wrapper(*args, **kwargs): - with qml.QueuingManager.stop_recording(): + with QueuingManager.stop_recording(): true_meas_return = true_fn(*args, **kwargs) false_meas_return = false_fn(*args, **kwargs) diff --git a/pennylane/ftqc/graph_state_preparation.py b/pennylane/ftqc/graph_state_preparation.py index 19a9eb29d9a..bb5e8b8ba7f 100644 --- a/pennylane/ftqc/graph_state_preparation.py +++ b/pennylane/ftqc/graph_state_preparation.py @@ -25,6 +25,18 @@ from .qubit_graph import QubitGraph +def make_graph_state(graph, wires, one_qubit_ops=qml.H, two_qubit_ops=qml.CZ): + """A program-capture compatible way to create a GraphStatePrep template. + We can't capture the graph object in plxpr, so instead, if capture is enabled, + we capture the operations generated in computing the decomposition.""" + if qml.capture.enabled(): + GraphStatePrep.compute_decomposition(wires, graph, one_qubit_ops, two_qubit_ops) + else: + GraphStatePrep( + graph=graph, wires=wires, one_qubit_ops=one_qubit_ops, two_qubit_ops=two_qubit_ops + ) + + class GraphStatePrep(Operation): r""" Encode a graph state with a single graph operation applied on each qubit, and an entangling diff --git a/pennylane/ftqc/parametric_midmeasure.py b/pennylane/ftqc/parametric_midmeasure.py index 0c719d8eb05..cee5ecc99cf 100644 --- a/pennylane/ftqc/parametric_midmeasure.py +++ b/pennylane/ftqc/parametric_midmeasure.py @@ -17,16 +17,60 @@ import uuid from collections.abc import Hashable +from copy import copy +from functools import lru_cache from typing import Iterable, Optional, Union import numpy as np -import pennylane as qml +from pennylane import capture from pennylane.drawer.tape_mpl import _add_operation_to_drawer +from pennylane.exceptions import QuantumFunctionError from pennylane.measurements.mid_measure import MeasurementValue, MidMeasureMP, measure +from pennylane.ops.op_math import Conditional, adjoint +from pennylane.ops.qubit import RX, RY, H, PhaseShift, S +from pennylane.queuing import QueuingManager +from pennylane.transforms import transform from pennylane.wires import Wires +@lru_cache(maxsize=1) +def _create_parametrized_mid_measure_primitive(): + """Create a primitive corresponding to a parametrized mid-circuit measurement type. + + Called when using a parametrized mid-circuit measurement, such as + :func:`~pennylane.measure_arbitrary_basis`. + + Returns: + jax.core.Primitive: A new jax primitive corresponding to a mid-circuit + measurement. + + """ + # pylint: disable=import-outside-toplevel + import jax + + from pennylane.capture.custom_primitives import NonInterpPrimitive + + measure_in_basis_p = NonInterpPrimitive("measure_in_basis") + + @measure_in_basis_p.def_impl + def _(wires, angle=0.0, plane="ZX", reset=False, postselect=None): + return _measure_impl( + wires, + measurement_class=ParametricMidMeasureMP, + angle=angle, + plane=plane, + reset=reset, + postselect=postselect, + ) + + @measure_in_basis_p.def_abstract_eval + def _(*_, **__): + return jax.core.ShapedArray((), jax.numpy.bool) + + return measure_in_basis_p + + def measure_arbitrary_basis( wires: Union[Hashable, Wires], angle: float, @@ -126,8 +170,14 @@ def func(x, y): result will return a binary sequence of samples. See :ref:`here ` for more details. """ + if len(Wires(wires)) > 1: + raise QuantumFunctionError( + "Only a single qubit can be measured in the middle of the circuit" + ) - # ToDo: if capture is enabled, create and bind primitive here and return primitive instead (subsequent PR) + if capture.enabled(): + primitive = _create_parametrized_mid_measure_primitive() + return primitive.bind(angle, wires, plane=plane, reset=reset, postselect=postselect) return _measure_impl( wires, ParametricMidMeasureMP, angle=angle, plane=plane, reset=reset, postselect=postselect @@ -171,8 +221,14 @@ def measure_x( QuantumFunctionError: if multiple wires were specified """ + if len(Wires(wires)) > 1: + raise QuantumFunctionError( + "Only a single qubit can be measured in the middle of the circuit" + ) - # ToDo: if capture is enabled, create and bind primitive here and return primitive instead (subsequent PR) + if capture.enabled(): + primitive = _create_parametrized_mid_measure_primitive() + return primitive.bind(0.0, wires, plane="XY", reset=reset, postselect=postselect) return _measure_impl(wires, XMidMeasureMP, reset=reset, postselect=postselect) @@ -214,8 +270,14 @@ def measure_y( QuantumFunctionError: if multiple wires were specified """ + if len(Wires(wires)) > 1: + raise QuantumFunctionError( + "Only a single qubit can be measured in the middle of the circuit" + ) - # ToDo: if capture is enabled, create and bind primitive here and return primitive instead (subsequent PR) + if capture.enabled(): + primitive = _create_parametrized_mid_measure_primitive() + return primitive.bind(np.pi / 2, wires, plane="XY", reset=reset, postselect=postselect) return _measure_impl(wires, YMidMeasureMP, reset=reset, postselect=postselect) @@ -249,6 +311,7 @@ def measure_z( QuantumFunctionError: if multiple wires were specified """ + # capture is already handled inside qml.measure return measure(wires, reset=reset, postselect=postselect) @@ -259,10 +322,6 @@ def _measure_impl( ): """Concrete implementation of qml.measure""" wires = Wires(wires) - if len(wires) > 1: - raise qml.QuantumFunctionError( - "Only a single qubit can be measured in the middle of the circuit" - ) # Create a UUID and a map between MP and MV to support serialization measurement_id = str(uuid.uuid4()) @@ -341,6 +400,16 @@ def hash(self): return hash(fingerprint) + # pylint: disable=too-many-positional-arguments + @classmethod + def _primitive_bind_call( + cls, angle=0.0, wires=None, plane="ZX", reset=False, postselect=None, id=None + ): + wires = () if wires is None else wires + return cls._wires_primitive.bind( + *wires, angle=angle, plane=plane, reset=reset, postselect=postselect, id=id + ) + def __repr__(self): """Representation of this class.""" return f"{self._shortname}_{self.plane.lower()}(wires={self.wires.tolist()}, angle={self.angle})" @@ -353,11 +422,11 @@ def has_diagonalizing_gates(self): def diagonalizing_gates(self): """Decompose to a diagonalizing gate and a standard MCM in the computational basis""" if self.plane == "XY": - return [qml.PhaseShift(-self.angle, self.wires), qml.H(self.wires)] + return [PhaseShift(-self.angle, self.wires), H(self.wires)] if self.plane == "ZX": - return [qml.RY(-self.angle, self.wires)] + return [RY(-self.angle, self.wires)] if self.plane == "YZ": - return [qml.RX(-self.angle, self.wires)] + return [RX(-self.angle, self.wires)] raise NotImplementedError( f"{self.plane} plane not implemented. Available plans are 'XY' 'ZX' and 'YZ'." @@ -453,7 +522,7 @@ def label( def diagonalizing_gates(self): """Decompose to a diagonalizing gate and a standard MCM in the computational basis""" - return [qml.H(self.wires)] + return [H(self.wires)] class YMidMeasureMP(ParametricMidMeasureMP): @@ -520,7 +589,7 @@ def label( def diagonalizing_gates(self): """Decompose to a diagonalizing gate and a standard MCM in the computational basis""" # alternatively we could apply (Z, S) instead of adjoint(S) - return [qml.adjoint(qml.S(self.wires)), qml.H(self.wires)] + return [adjoint(S(self.wires)), H(self.wires)] @_add_operation_to_drawer.register @@ -551,7 +620,7 @@ def null_postprocessing(results): return results[0] -@qml.transform +@transform def diagonalize_mcms(tape): """Diagonalize any mid-circuit measurements in a parameterized basis into the computational basis. @@ -657,14 +726,14 @@ def circuit(x): new_operations.extend(diag_gates) # add computational basis MCM to tape - with qml.QueuingManager.stop_recording(): + with QueuingManager.stop_recording(): new_mp = MidMeasureMP(op.wires, reset=op.reset, postselect=op.postselect, id=op.id) new_operations.append(new_mp) # track mapping from original to computational basis MCMs mps_mapping[op] = new_mp - elif isinstance(op, qml.ops.Conditional): + elif isinstance(op, Conditional): # from MCM mapping, map any MCMs in the condition if needed mps = [mps_mapping.get(op, op) for op in op.meas_val.measurements] @@ -681,14 +750,14 @@ def circuit(x): expr_true = MeasurementValue(mps, processing_fn=true_cond.meas_val.processing_fn) expr_false = MeasurementValue(mps, processing_fn=false_cond.meas_val.processing_fn) - with qml.QueuingManager.stop_recording(): + with QueuingManager.stop_recording(): diag_gates_true = [ - qml.ops.Conditional(expr=expr_true, then_op=gate) + Conditional(expr=expr_true, then_op=gate) for gate in true_cond.diagonalizing_gates() ] diag_gates_false = [ - qml.ops.Conditional(expr=expr_false, then_op=gate) + Conditional(expr=expr_false, then_op=gate) for gate in false_cond.diagonalizing_gates() ] @@ -707,8 +776,8 @@ def circuit(x): processing_fn = op.meas_val.processing_fn expr = MeasurementValue(mps, processing_fn=processing_fn) - with qml.QueuingManager.stop_recording(): - new_cond = qml.ops.Conditional(expr=expr, then_op=op.base) + with QueuingManager.stop_recording(): + new_cond = Conditional(expr=expr, then_op=op.base) new_operations.append(new_cond) else: @@ -716,6 +785,16 @@ def circuit(x): curr_idx += 1 + new_measurements = [] + for mp in tape.measurements: + if mp.mv is None: + new_measurements.append(mp) + else: + new_mp = copy(mp) + mps = [mps_mapping.get(m, m) for m in mp.mv.measurements] + new_mp.mv.measurements = mps + new_measurements.append(new_mp) + new_tape = tape.copy(operations=new_operations) return (new_tape,), null_postprocessing diff --git a/pennylane/ftqc/primitives.py b/pennylane/ftqc/primitives.py new file mode 100644 index 00000000000..fd65d94dcaf --- /dev/null +++ b/pennylane/ftqc/primitives.py @@ -0,0 +1,25 @@ +# Copyright 2025 Xanadu Quantum Technologies Inc. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +This submodule offers all the non-operator/ measurement custom primitives +created in the ftqc module. +""" + +from .parametric_midmeasure import _create_parametrized_mid_measure_primitive + +measure_in_basis_prim = _create_parametrized_mid_measure_primitive() + +__all__ = [ + "measure_in_basis_prim", +] diff --git a/pennylane/gradients/metric_tensor.py b/pennylane/gradients/metric_tensor.py index 3238c95de66..f8395e1ee6d 100644 --- a/pennylane/gradients/metric_tensor.py +++ b/pennylane/gradients/metric_tensor.py @@ -413,7 +413,7 @@ def _metric_tensor_cov_matrix(tape, argnum, diag_approx): # pylint: disable=too required for the covariance matrix callable: Post-processing function that computes the covariance matrix from the results of the tapes in the first return value - list[list[.Observable]]: Observables measured in each tape, one inner list + list[list[.Operator]]: Observables measured in each tape, one inner list corresponding to one tape in the first return value list[list[float]]: Coefficients to scale the results for each observable, one inner list corresponding to one tape in the first return value diff --git a/pennylane/gradients/parameter_shift_cv.py b/pennylane/gradients/parameter_shift_cv.py index da514d6e0fa..63f21b298ae 100644 --- a/pennylane/gradients/parameter_shift_cv.py +++ b/pennylane/gradients/parameter_shift_cv.py @@ -152,13 +152,13 @@ def _transform_observable(obs, Z, device_wires): """Apply a Gaussian linear transformation to an observable. Args: - obs (.Observable): observable to transform + obs (.Operator): observable to transform Z (array[float]): Heisenberg picture representation of the linear transformation device_wires (.Wires): wires on the device the transformed observable is to be measured on Returns: - .Observable: the transformed observable + .Operator: the transformed observable """ # Get the Heisenberg representation of the observable # in the position/momentum basis. The returned matrix/vector diff --git a/pennylane/labs/.pylintrc b/pennylane/labs/.pylintrc index 51a87506549..a4f760baf2d 100644 --- a/pennylane/labs/.pylintrc +++ b/pennylane/labs/.pylintrc @@ -30,7 +30,7 @@ ignored-classes=numpy,scipy,autograd,toml,appdir,autograd.numpy,autograd.numpy.l # it should appear only once). # Cyclical import checks are disabled for now as they are frequently used in # the code base, but this can be removed in the future once cycles are resolved. -disable=line-too-long,invalid-name,too-many-lines,redefined-builtin,too-many-locals,duplicate-code,cyclic-import,import-error,bad-option-value,too-few-public-methods +disable=line-too-long,invalid-name,too-many-lines,redefined-builtin,too-many-locals,duplicate-code,cyclic-import,import-error,bad-option-value,too-few-public-methods,no-self-use [MISCELLANEOUS] diff --git a/pennylane/labs/dla/variational_kak.py b/pennylane/labs/dla/variational_kak.py index 817abbb89ca..c3af78086c4 100644 --- a/pennylane/labs/dla/variational_kak.py +++ b/pennylane/labs/dla/variational_kak.py @@ -77,11 +77,11 @@ def Kc(theta_opt: Iterable[float], k: Iterable[Operator]): .. math:: f(\theta) = \langle H, K(\theta) e^{-i \sum_{j=1}^{|\mathfrak{a}|} \pi^j a_j} K(\theta)^\dagger \rangle, - see eq. (6) therein and our :doc:`demo ` for more details. + see eq. (6) therein and our `demo `__ for more details. Instead of relying on having Pauli words, we use the adjoint representation for a more general evaluation of the cost function. The rest is the same. - .. seealso:: :doc:`The KAK decomposition in theory (demo) `, :doc:`The KAK decomposition in practice (demo) `. + .. seealso:: `The KAK decomposition in theory (demo) `__, `The KAK decomposition in practice (demo) `__. Args: H (Union[Operator, PauliSentence, np.ndarray]): Hamiltonian to decompose diff --git a/pennylane/labs/intermediate_reps/__init__.py b/pennylane/labs/intermediate_reps/__init__.py index e7042ac31c2..851f900358b 100644 --- a/pennylane/labs/intermediate_reps/__init__.py +++ b/pennylane/labs/intermediate_reps/__init__.py @@ -28,3 +28,4 @@ """ from .parity_matrix import parity_matrix +from .rowcol import postorder_traverse, preorder_traverse, rowcol diff --git a/pennylane/labs/intermediate_reps/rowcol.py b/pennylane/labs/intermediate_reps/rowcol.py new file mode 100644 index 00000000000..925a29afcc4 --- /dev/null +++ b/pennylane/labs/intermediate_reps/rowcol.py @@ -0,0 +1,214 @@ +# Copyright 2025 Xanadu Quantum Technologies Inc. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""CNOT routing algorithm ROWCOL as described in https://arxiv.org/abs/1910.14478.""" + +from typing import Iterable + +import galois +import networkx as nx +import numpy as np +from networkx.algorithms.approximation import steiner_tree + + +def postorder_traverse(tree: nx.Graph, source: int, source_parent: int = None): + """Post-order traverse a tree graph, starting from (but excluding) the node ``source``. + + Args: + tree (nx.Graph): Tree graph to traverse. Must contain ``source``. Must contain + ``source_parent`` if it is not None. Typing assumes integer-labeled nodes. + source (int): Node to start the traversal from + source_parent (Optional[int]): Parent node of ``source`` in ``tree``. + + Returns: + list[tuple[int]]: Pairs of nodes that constitute post-order traversal of ``tree`` + starting at ``source``. Strictly speaking, the traversal is encoded in the first + entry of each pair, and the second entry simply is the parent node for each first entry. + + A useful illustration of depth-first tree traversals can be found on + `Wikipedia `__. + """ + out = [] + # First, traverse the subtrees attached to the current node ``source`` + for child in tree.neighbors(source): + # The graph does not know of the tree structure, so we need to make sure we skip + # the parent of ``source`` among its neighbors. + if child != source_parent: + # Recurse + out.extend(postorder_traverse(tree, child, source)) + # Second, attach the current node itself, together with its parent + if source_parent is not None: + out.append((source, source_parent)) + + return out + + +def preorder_traverse(tree: nx.Graph, source: int, source_parent: int = None): + """Pre-order traverse a tree graph, starting from (but excluding) the node ``source``. + + Args: + tree (nx.Graph): Tree graph to traverse. Must contain ``source``. Must contain + ``source_parent`` if it is not None. Typing assumes integer-labeled nodes. + source (int): Node to start the traversal from + source_parent (Optional[int]): Parent node of ``source`` in ``tree``. + + Returns: + list[tuple[int]]: Pairs of nodes that constitute pre-order traversal of ``tree`` + starting at ``source``. Strictly speaking, the traversal is encoded in the first + entry of each pair, and the second entry simply is the parent node for each first entry. + + A useful illustration of depth-first tree traversals can be found on + `Wikipedia `__. + """ + out = [] + + # First, attach the current node itself, together with its parent + if source_parent is not None: + out.append((source, source_parent)) + # Second, traverse the subtrees attached to the current node ``source`` + for child in tree.neighbors(source): + # The graph does not know of the tree structure, so we need to make sure we skip + # the parent of ``source`` among its neighbors. + if child != source_parent: + # Recurse + out.extend(preorder_traverse(tree, child, source)) + + return out + + +def _update(P: np.ndarray, cnots: list[tuple[int]], control: int, target: int): + """In-place apply update corresponding to a CNOT on wires ``(control, target)`` + to parity matrix P and list of CNOT gates ``cnots``.""" + P[target] += P[control] + cnots.append((control, target)) + + +F_2 = galois.GF(2) + + +def _get_S(P: np.ndarray, idx: int, node_set: Iterable[int], mode: str): + # Find S (S') either by simply extracting a column or by solving a linear system for the row + if mode == "column": + b = P[:, idx] + else: + P = F_2(P) + e_i = F_2.Zeros(len(P)) + e_i[idx] = 1 + b = np.linalg.solve(P.T, e_i) # This solve step is over F_2! + S = set(np.where(b)[0]) + # Add the node ``idx`` itself + S.add(idx) + # Manually remove nodes from S that are no longer part of ``connectivity``. + S = S.intersection(node_set) + return S + + +def _eliminate(P: np.ndarray, connectivity: nx.Graph, idx: int, mode: str, verbose: bool): + """Eliminate the column or row with index ``idx`` of the parity matrix P, + respecting the connectivity constraints given by ``connectivity``. + + Args: + P (np.ndarray): Parity matrix + connectivity (nx.Graph): Connectivity graph + idx (int): Column or row index to eliminate + mode (str): Whether to eliminate the column (``column``) or row (``row``) of ``P``. + verbose (bool): Whether to print elimination results + + Returns: + tuple[np.ndarray, list[tuple[int]]]: Updated parity matrix and list of CNOTs that + accomplish the update, in terms of ``(control, target)`` qubit pairs. + """ + + # i.1.1/i.2.1 Construct S (mode="column") or S' (mode="row"), respectively. + S = _get_S(P, idx, connectivity.nodes(), mode) + if len(S) == 1: + # idx is in S for sure, so this column/row already has the right content. No need to update + if verbose: + print(f"CNOTs from {mode} elimination ({idx}): {[]}") + return P, [] + + cnots = [] + # i.1.1/i.2.1 Find Steiner tree within S (S'). + T = steiner_tree(connectivity, list(S)) + + # Need post-order nodes in any case + visit_nodes = postorder_traverse(T, source=idx) + + if mode == "column": + # For column mode use post-order and parities constraint in first pass (i.1.2)... + _ = [ + _update(P, cnots, child, parent) + for child, parent in visit_nodes + if P[child, idx] == 1 and P[parent, idx] == 0 + ] + # ... and no constraints for second pass (i.1.3) + _ = [_update(P, cnots, parent, child) for child, parent in visit_nodes] + + else: + # For row mode use pre-order and constraints from S' in first pass (i.2.2)... + previsit_nodes = preorder_traverse(T, source=idx) + _ = [_update(P, cnots, child, parent) for child, parent in previsit_nodes if child not in S] + # ... and no constraints for second pass (i.2.3) + _ = [_update(P, cnots, child, parent) for child, parent in visit_nodes] + + if verbose: + print(f"CNOTs from {mode} elimination ({idx}): {cnots}") + return P % 2, cnots + + +def rowcol(P: np.ndarray, connectivity: nx.Graph = None, verbose: bool = False) -> list[tuple[int]]: + """CNOT routing algorithm ROWCOL. + + This algorithm was introduced by `Wu et al. `__ and is + detailed in the `compilation hub `__, + where examples can be found as well. + + Args: + P (np.ndarray): Parity matrix to implement. Will not be altered + connectivity (nx.Graph): Connectivity graph to route into. If not given, + full connectivity is assumed. May be altered by this function + verbose (bool): Whether or not to print progress of obtained CNOT gates. + + Returns: + list[tuple[int]]: Wire pairs for CNOTs that implement the parity matrix (control first, + target second). + + """ + P = P.copy() + n = len(P) + # If no connectivity is given, assume full connectivity + if connectivity is None: + connectivity = nx.complete_graph(n) + cut_vertices = set() + else: + cut_vertices = set(nx.articulation_points(connectivity)) + + cnots = [] + while connectivity.number_of_nodes() > 1: + # Pick a vertex that is not a cut vertex of the (remaining) connectivity graph + i = next(v for v in connectivity.nodes() if v not in cut_vertices) + # Eliminate column and row of parity matrix with index v (Steps i.1 and i.2 in compilation hub) + for mode in ("column", "row"): + P, new_cnots = _eliminate(P, connectivity, i, mode, verbose) + # Memorize CNOTs required to eliminate column/row + cnots.extend(new_cnots) + # Remove vertex i + connectivity.remove_nodes_from([i]) + + # Recompute cut vertices + cut_vertices = set(nx.articulation_points(connectivity)) + + # Assert that the parity matrix was transformed into the identity matrix + assert np.allclose(np.eye(n), P) + # Return CNOTs in reverse order + return P, cnots[::-1] diff --git a/pennylane/labs/tests/intermediate_reps/test_rowcol.py b/pennylane/labs/tests/intermediate_reps/test_rowcol.py new file mode 100644 index 00000000000..4025b6d7cdc --- /dev/null +++ b/pennylane/labs/tests/intermediate_reps/test_rowcol.py @@ -0,0 +1,218 @@ +# Copyright 2025 Xanadu Quantum Technologies Inc. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Tests for the CNOT routing algorithm ROWCOL.""" +# pylint: disable=no-self-use +import networkx as nx +import numpy as np +import pytest + +from pennylane import CNOT +from pennylane.labs.intermediate_reps import ( + parity_matrix, + postorder_traverse, + preorder_traverse, + rowcol, +) +from pennylane.tape import QuantumScript + +path_graph_4 = nx.path_graph(4) +binary_graph_3 = nx.balanced_tree(2, 3) +ternary_graph_2 = nx.balanced_tree(3, 2) + + +class TestTreeTraversal: + """Tests for tree-traversal methods.""" + + @pytest.mark.parametrize( + "tree, source, expected", + [ + (path_graph_4, 0, [(3, 2), (2, 1), (1, 0)]), + (path_graph_4, 1, [(0, 1), (3, 2), (2, 1)]), + (path_graph_4, 2, [(0, 1), (1, 2), (3, 2)]), + (path_graph_4, 3, [(0, 1), (1, 2), (2, 3)]), + ( + binary_graph_3, + 0, + # fmt: off + [ + (7, 3), (8, 3), (3, 1), (9, 4), (10, 4), (4, 1), (1, 0), + (11, 5), (12, 5), (5, 2), (13, 6), (14, 6), (6, 2), (2, 0), + ], + # fmt: on + ), + ( + binary_graph_3, + 4, + # fmt: off + [ + (11, 5), (12, 5), (5, 2), (13, 6), (14, 6), (6, 2), (2, 0), (0, 1), + (7, 3), (8, 3), (3, 1), (1, 4), (9, 4), (10, 4), + ], + # fmt: on + ), + ( + ternary_graph_2, + 0, + # fmt: off + [ + (4, 1), (5, 1), (6, 1), (1, 0), (7, 2), (8, 2), (9, 2), + (2, 0), (10, 3), (11, 3), (12, 3), (3, 0), + ], + # fmt: on + ), + ( + ternary_graph_2, + 5, + # fmt: off + [ + (7, 2), (8, 2), (9, 2), (2, 0), (10, 3), (11, 3), (12, 3), (3, 0), + (0, 1), (4, 1), (6, 1), (1, 5), + ], + # fmt: on + ), + ], + ) + def test_postorder_traverse(self, tree, source, expected): + """Tests for postorder_traverse.""" + post = postorder_traverse(tree, source) + assert post == expected + + @pytest.mark.parametrize( + "tree, source, expected", + [ + (path_graph_4, 0, [(1, 0), (2, 1), (3, 2)]), + (path_graph_4, 1, [(0, 1), (2, 1), (3, 2)]), + (path_graph_4, 2, [(1, 2), (0, 1), (3, 2)]), + (path_graph_4, 3, [(2, 3), (1, 2), (0, 1)]), + ( + binary_graph_3, + 0, + # fmt: off + [ + (1, 0), (3, 1), (7, 3), (8, 3), (4, 1), (9, 4), (10, 4), + (2, 0), (5, 2), (11, 5), (12, 5), (6, 2), (13, 6), (14, 6), + ], + # fmt: on + ), + ( + binary_graph_3, + 4, + # fmt: off + [ + (1, 4), (0, 1), (2, 0), (5, 2), (11, 5), (12, 5), (6, 2), + (13, 6), (14, 6), (3, 1), (7, 3), (8, 3), (9, 4), (10, 4), + ], + # fmt: on + ), + ( + ternary_graph_2, + 0, + # fmt: off + [ + (1, 0), (4, 1), (5, 1), (6, 1), (2, 0), (7, 2), (8, 2), (9, 2), + (3, 0), (10, 3), (11, 3), (12, 3), + ], + # fmt: on + ), + ( + ternary_graph_2, + 5, + # fmt: off + [ + (1, 5), (0, 1), (2, 0), (7, 2), (8, 2), (9, 2), + (3, 0), (10, 3), (11, 3), (12, 3), (4, 1), (6, 1), + ], + # fmt: on + ), + ], + ) + def test_preorder_traverse(self, tree, source, expected): + """Tests for preorder_traverse.""" + pre = preorder_traverse(tree, source) + assert pre == expected + + +def assert_reproduces_parity_matrix(cnots, expected_P): + """Helper function that compares a CNOT circuit to a given parity matrix.""" + tape = QuantumScript([CNOT(wires) for wires in cnots]) + new_P = parity_matrix(tape, wire_order=list(range(len(expected_P)))) + assert np.allclose(new_P, expected_P) + + +class TestRowCol: + """Tests for rowcol.""" + + @pytest.mark.parametrize("n", list(range(2, 13))) + @pytest.mark.parametrize("conn", [nx.path_graph, nx.complete_graph]) + def test_identity(self, n, conn): + """Test with the identity Parity matrix/circuit.""" + P = np.eye(n, dtype=int) + connectivity = conn(n) + new_P, cnots = rowcol(P, connectivity) + assert not cnots + assert np.allclose(new_P, P) + + @pytest.mark.parametrize("n", list(range(2, 13))) + @pytest.mark.parametrize("conn", [nx.path_graph, nx.complete_graph]) + def test_few_commuting_cnots(self, n, conn): + """Test with a few commuting CNOTs.""" + P = np.eye(n, dtype=int) + for i in range(0, n - 1, 2): + P[i + 1] += P[i] + for i in range(0, n - 2, 2): + P[i + 1] += P[i + 2] + input_P = P.copy() + + connectivity = conn(n) + new_P, cnots = rowcol(P, connectivity) + exp = sum(([(i, i + 1), (i + 2, i + 1)] for i in range(0, n - 2, 2)), start=[]) + if n % 2 == 0: + exp.append((n - 2, n - 1)) + assert set(cnots) == set(exp) + assert np.allclose(new_P, np.eye(n)) + assert np.allclose(input_P, P) # Check that P was not altered + assert_reproduces_parity_matrix(cnots, input_P) + + @pytest.mark.parametrize("n", list(range(3, 13))) + def test_long_range_cnot(self, n): + """Test with a single long-ranged CNOT in linear connectivity.""" + P = np.eye(n, dtype=int) + P[0] += P[-1] + input_P = P.copy() + + connectivity = nx.path_graph(n) + new_P, cnots = rowcol(P, connectivity) + assert len(cnots) == 4 * (n - 2) # Minimal CNOT count for longe-range CNOT + assert np.allclose(new_P, np.eye(n)) + assert np.allclose(input_P, P) # Check that P was not altered + assert_reproduces_parity_matrix(cnots, input_P) + + @pytest.mark.parametrize("n", list(range(2, 13))) + @pytest.mark.parametrize("conn", [nx.path_graph, nx.complete_graph]) + @pytest.mark.parametrize("input_depth", [(lambda n: n), (lambda n: n**3)]) + def test_random_circuit(self, n, conn, input_depth): + """Test with a random CNOT circuit.""" + + P = np.eye(n, dtype=int) + for _ in range(input_depth(n)): + i, j = np.random.choice(n, size=2, replace=False) + P[i] += P[j] + P %= 2 + input_P = P.copy() + + connectivity = conn(n) + new_P, cnots = rowcol(P, connectivity) + assert np.allclose(new_P, np.eye(n)) + assert np.allclose(input_P, P) # Check that P was not altered + assert_reproduces_parity_matrix(cnots, input_P) diff --git a/pennylane/liealg/__init__.py b/pennylane/liealg/__init__.py index 4796208a787..42d452916ec 100644 --- a/pennylane/liealg/__init__.py +++ b/pennylane/liealg/__init__.py @@ -23,7 +23,7 @@ For full universality, we require the available gates to span all of :math:`SU(2^n)` in order to reach any state in Hilbert space from any other state. -:math:`SU(2^n)` is a Lie group and has an associated :doc:`Lie algebra ` +:math:`SU(2^n)` is a Lie group and has an associated `Lie algebra `__ to it, called :math:`\mathfrak{su}(2^n)`. In some cases, it is more convenient to work with the associated Lie algebra rather than the Lie group. @@ -95,11 +95,11 @@ Check out the following demos to learn more about Lie algebras in the context of quantum computation: -* :doc:`Introducing (dynamical) Lie algebras for quantum practitioners ` -* :doc:`g-sim: Lie-algebraic classical simulations for variational quantum computing ` -* :doc:`(g + P)-sim: Extending g-sim by non-DLA observables and gates ` -* :doc:`Fixed depth Hamiltonian simulation via Cartan decomposition ` -* :doc:`The KAK decomposition ` +* `Introducing (dynamical) Lie algebras for quantum practitioners `__ +* `g-sim: Lie-algebraic classical simulations for variational quantum computing `__ +* `(g + P)-sim: Extending g-sim by non-DLA observables and gates `__ +* `Fixed depth Hamiltonian simulation via Cartan decomposition `__ +* `The KAK decomposition `__ diff --git a/pennylane/liealg/horizontal_cartan_subalgebra.py b/pennylane/liealg/horizontal_cartan_subalgebra.py index b86a52c84d5..af5adba0011 100644 --- a/pennylane/liealg/horizontal_cartan_subalgebra.py +++ b/pennylane/liealg/horizontal_cartan_subalgebra.py @@ -88,7 +88,7 @@ def horizontal_cartan_subalgebra( where :math:`\mathfrak{a})` is the CSA and :math:`\tilde{\mathfrak{m}}` is the remainder of the horizontal subspace :math:`\mathfrak{m}`. - .. seealso:: :func:`~cartan_decomp`, :func:`~structure_constants`, :doc:`The KAK decomposition in theory (demo) `, :doc:`The KAK decomposition in practice (demo) `. + .. seealso:: :func:`~cartan_decomp`, :func:`~structure_constants`, `The KAK decomposition in theory (demo) `__, `The KAK decomposition in practice (demo) `__. Args: k (List[Union[PauliSentence, TensorLike]]): Vertical space :math:`\mathfrak{k}` from Cartan decomposition :math:`\mathfrak{g} = \mathfrak{k} \oplus \mathfrak{m}`. diff --git a/pennylane/math/quantum.py b/pennylane/math/quantum.py index 7f7460d33c2..6e90988de6c 100644 --- a/pennylane/math/quantum.py +++ b/pennylane/math/quantum.py @@ -43,7 +43,7 @@ def cov_matrix(prob, obs, wires=None, diag_approx=False): Args: prob (tensor_like): probability distribution - obs (list[.Observable]): a list of observables for which + obs (list[.Operator]): a list of observables for which to compute the covariance matrix diag_approx (bool): if True, return the diagonal approximation wires (.Wires): The wire register of the system. If not provided, diff --git a/pennylane/measurements/counts.py b/pennylane/measurements/counts.py index 69d79cbc1e4..c9a99385afc 100644 --- a/pennylane/measurements/counts.py +++ b/pennylane/measurements/counts.py @@ -41,7 +41,7 @@ def counts( specified on the device. Args: - op (Observable or MeasurementValue or None): a quantum observable object. To get counts + op (Operator or MeasurementValue or None): a quantum observable object. To get counts for mid-circuit measurements, ``op`` should be a ``MeasurementValue``. wires (Sequence[int] or int or None): the wires we wish to sample from, ONLY set wires if op is None @@ -184,7 +184,7 @@ class CountsMP(SampleMeasurement): _shortname = "counts" - # pylint: disable=too-many-arguments + # pylint: disable=too-many-arguments, too-many-positional-arguments def __init__( self, obs: Optional[Operator] = None, @@ -381,6 +381,17 @@ def process_counts(self, counts: dict, wire_order: Wires) -> dict: self._include_all_outcomes(mapped_counts) else: _remove_unobserved_outcomes(mapped_counts) + + if self.eigvals() is not None: + eigvals = self.eigvals() + eigvals_dict = {k: qml.math.int64(0) for k in eigvals} + for outcome, count in mapped_counts.items(): + val = eigvals[int(outcome, 2)] + eigvals_dict[val] += count + if not self.all_outcomes: + _remove_unobserved_outcomes(eigvals_dict) + return eigvals_dict + return mapped_counts def _map_counts(self, counts_to_map: dict, wire_order: Wires) -> dict: diff --git a/pennylane/measurements/expval.py b/pennylane/measurements/expval.py index 45863de63b4..82a4a079bc0 100644 --- a/pennylane/measurements/expval.py +++ b/pennylane/measurements/expval.py @@ -50,7 +50,7 @@ def circuit(x): -0.4794255386042029 Args: - op (Union[Observable, MeasurementValue]): a quantum observable object. To + op (Union[Operator, MeasurementValue]): a quantum observable object. To get expectation values for mid-circuit measurements, ``op`` should be a ``MeasurementValue``. diff --git a/pennylane/measurements/probs.py b/pennylane/measurements/probs.py index cc1035f1732..88057be3ed0 100644 --- a/pennylane/measurements/probs.py +++ b/pennylane/measurements/probs.py @@ -46,7 +46,7 @@ def probs(wires=None, op=None) -> "ProbabilityMP": Args: wires (Sequence[int] or int): the wire the operation acts on - op (Observable or MeasurementValue or Sequence[MeasurementValue]): Observable (with a ``diagonalizing_gates`` + op (Operator or MeasurementValue or Sequence[MeasurementValue]): Observable (with a ``diagonalizing_gates`` attribute) that rotates the computational basis, or a ``MeasurementValue`` corresponding to mid-circuit measurements. @@ -256,11 +256,14 @@ def process_counts(self, counts: dict, wire_order: Wires) -> np.ndarray: return prob_vector def process_density_matrix(self, density_matrix: TensorLike, wire_order: Wires): - if len(np.shape(density_matrix)) == 2: + if len(qml.math.shape(density_matrix)) == 2: prob = qml.math.diagonal(density_matrix) else: - prob = qml.math.array( - [qml.math.diagonal(density_matrix[i]) for i in range(np.shape(density_matrix)[0])] + prob = qml.math.stack( + [ + qml.math.diagonal(density_matrix[i]) + for i in range(qml.math.shape(density_matrix)[0]) + ] ) # Since we only care about the probabilities, we can simplify the task here by creating a 'pseudo-state' to carry the diagonal elements and reuse the process_state method diff --git a/pennylane/measurements/sample.py b/pennylane/measurements/sample.py index 20636c2b2dd..e00476bd2bd 100644 --- a/pennylane/measurements/sample.py +++ b/pennylane/measurements/sample.py @@ -40,7 +40,7 @@ def sample( specified on the device. Args: - op (Observable or MeasurementValue): a quantum observable object. To get samples + op (Operator or MeasurementValue): a quantum observable object. To get samples for mid-circuit measurements, ``op`` should be a ``MeasurementValue``. wires (Sequence[int] or int or None): the wires we wish to sample from; ONLY set wires if op is ``None``. @@ -302,8 +302,8 @@ def process_counts(self, counts: dict, wire_order: Wires): mapped_counts = self._map_counts(counts, wire_order) for outcome, count in mapped_counts.items(): outcome_sample = self._compute_outcome_sample(outcome) - if len(self.wires) == 1: - # If only one wire is sampled, flatten the list + if len(self.wires) == 1 and self.eigvals() is None: + # For sampling wires, if only one wire is sampled, flatten the list outcome_sample = outcome_sample[0] samples.extend([outcome_sample] * count) @@ -331,10 +331,8 @@ def _compute_outcome_sample(self, outcome) -> list: list: A list of outcome samples for given binary string. If eigenvalues exist, the binary outcomes are mapped to their corresponding eigenvalues. """ - outcome_samples = [int(bit) for bit in outcome] - if self.eigvals() is not None: eigvals = self.eigvals() - outcome_samples = [eigvals[outcome] for outcome in outcome_samples] + return eigvals[int(outcome, 2)] - return outcome_samples + return [int(bit) for bit in outcome] diff --git a/pennylane/noise/conditionals.py b/pennylane/noise/conditionals.py index f4a8f36f265..40f451da37d 100644 --- a/pennylane/noise/conditionals.py +++ b/pennylane/noise/conditionals.py @@ -21,7 +21,6 @@ import pennylane as qml from pennylane.boolean_fn import BooleanFn from pennylane.measurements import MeasurementProcess, MeasurementValue, MidMeasureMP -from pennylane.operation import AnyWires from pennylane.ops import Adjoint, Controlled from pennylane.templates import ControlledSequence from pennylane.wires import WireError, Wires @@ -747,7 +746,7 @@ def _partial(wires=None, **partial_kwargs): op_args[key] = val if issubclass(op_class, qml.operation.Operation): - num_wires = getattr(op_class, "num_wires", AnyWires) + num_wires = getattr(op_class, "num_wires", None) if "wires" in op_args and isinstance(num_wires, int): if num_wires < len(op_args["wires"]) and num_wires == 1: op_wires = op_args.pop("wires") diff --git a/pennylane/operation.py b/pennylane/operation.py index bfb297a8919..b8182f8ebb5 100644 --- a/pennylane/operation.py +++ b/pennylane/operation.py @@ -29,7 +29,7 @@ Qubit Operations ~~~~~~~~~~~~~~~~ The :class:`Operator` class serves as a base class for operators, -and is inherited by both the :class:`Observable` class and the +and is inherited by the :class:`Operation` class. These classes are subclassed to implement quantum operations and measure observables in PennyLane. @@ -47,10 +47,6 @@ represents an application of the operation with given parameter values to a given sequence of wires (subsystems). -* Each :class:`~.Observable` subclass represents a type of physical observable. - Each instance of these subclasses represents an instruction to measure and - return the respective result for the given parameter values on a - sequence of wires (subsystems). Differentiation ^^^^^^^^^^^^^^^ @@ -75,8 +71,7 @@ ~~~~~~~~~~~~~~~~~~~~~~~~~ Due to additional requirements, continuous-variable (CV) operations must subclass the -:class:`~.CVOperation` or :class:`~.CVObservable` classes instead of :class:`~.Operation` -and :class:`~.Observable`. +:class:`~.CVOperation` or :class:`~.CVObservable` classes instead of :class:`~.Operation`. Differentiation ^^^^^^^^^^^^^^^ @@ -124,7 +119,7 @@ .. currentmodule:: pennylane.operation -.. inheritance-diagram:: Operator Operation Observable Channel CV CVObservable CVOperation StatePrepBase +.. inheritance-diagram:: Operator Operation Channel CV CVObservable CVOperation StatePrepBase :parts: 1 Errors @@ -156,6 +151,11 @@ :class:`~.BooleanFn`'s are functions of a single object that return ``True`` or ``False``. The ``operation`` module provides the following: +.. warning:: + + All of the boolean functions in this module are currently deprecated. See the individual functions + for alternative code. + .. currentmodule:: pennylane.operation .. autosummary:: @@ -181,9 +181,6 @@ :toctree: api ~operation_derivative - ~WiresEnum - ~AllWires - ~AnyWires .. currentmodule:: pennylane @@ -232,6 +229,7 @@ import pennylane as qml from pennylane.capture import ABCCaptureMeta, create_operator_primitive +from pennylane.exceptions import PennyLaneDeprecationWarning from pennylane.math import expand_matrix from pennylane.queuing import QueuingManager from pennylane.typing import TensorLike @@ -299,22 +297,37 @@ class ParameterFrequenciesUndefinedError(OperatorPropertyUndefined): # ============================================================================= -class WiresEnum(IntEnum): +class _WiresEnum(IntEnum): """Integer enumeration class to represent the number of wires - an operation acts on""" + an operation acts on. + + .. warning:: + + This class is deprecated ``Operator.num_wires=None`` should now be used to indicate + that an operator can exist on any number of wires. + + """ AnyWires = -1 + """A enumeration that represents that an operator can act on any number of wires. + + .. warning:: + + ``AnyWires`` is deprecated ``Operator.num_wires=None`` should now be used to indicate + that an operator can exist on any number of wires. + + """ + AllWires = -2 + """A enumeration that represents that an operator acts on all wires in the system. + .. warning:: -AllWires = WiresEnum.AllWires -"""IntEnum: An enumeration which represents all wires in the -subsystem. It is equivalent to an integer with value 0.""" + ``AllWires`` is deprecated ``Operator.num_wires=None`` should now be used to indicate + that an operator can exist on any number of wires. -AnyWires = WiresEnum.AnyWires -"""IntEnum: An enumeration which represents any wires in the -subsystem. It is equivalent to an integer with value -1.""" + """ # ============================================================================= @@ -434,11 +447,6 @@ class Operator(abc.ABC, metaclass=ABCCaptureMeta): class FlipAndRotate(qml.operation.Operation): - # Define how many wires the operator acts on in total. - # In our case this may be one or two, which is why we - # use the AnyWires Enumeration to indicate a variable number. - num_wires = qml.operation.AnyWires - # This attribute tells PennyLane what differentiation method to use. Here # we request parameter-shift (or "analytic") differentiation. grad_method = "A" @@ -972,7 +980,7 @@ def terms(self) -> tuple[list[TensorLike], list["Operation"]]: # pylint: disabl """ raise TermsUndefinedError - num_wires: Union[int, WiresEnum] = AnyWires + num_wires: Optional[Union[int, _WiresEnum]] = None """Number of wires the operator acts on.""" @property @@ -1136,7 +1144,9 @@ def __init__( self._wires: Wires = Wires(wires) # check that the number of wires given corresponds to required number - if self.num_wires not in {AllWires, AnyWires} and len(self._wires) != self.num_wires: + if (self.num_wires is not None and not isinstance(self.num_wires, _WiresEnum)) and len( + self._wires + ) != self.num_wires: raise ValueError( f"{self.name}: wrong number of wires. " f"{len(self._wires)} wires given, {self.num_wires} expected." @@ -1886,7 +1896,7 @@ def parameter_frequencies(self) -> list[tuple[Union[float, int]]]: warnings.filterwarnings( action="ignore", message=r".+ eigenvalues will be computed numerically\." ) - eigvals = qml.eigvals(gen, k=2**self.num_wires) + eigvals = qml.eigvals(gen, k=2 ** len(self.wires)) eigvals = tuple(np.round(eigvals, 8)) return [qml.gradients.eigvals_to_frequencies(eigvals)] @@ -1978,81 +1988,6 @@ def kraus_matrices(self): return self.compute_kraus_matrices(*self.parameters, **self.hyperparameters) -# ============================================================================= -# Base Observable class -# ============================================================================= - - -class Observable(Operator): - """Base class representing observables. - - Observables define a return type - - Args: - params (tuple[tensor_like]): trainable parameters - wires (Iterable[Any] or Any): Wire label(s) that the operator acts on. - If not given, args[-1] is interpreted as wires. - id (str): custom label given to an operator instance, - can be useful for some applications where the instance has to be identified - """ - - @property - def _queue_category(self) -> Literal["_ops", "_measurements", None]: - """Used for sorting objects into their respective lists in `QuantumTape` objects. - - This property is a temporary solution that should not exist long-term and should not be - used outside of ``QuantumTape._process_queue``. - - Options are: - * `"_ops"` - * `"_measurements"` - * None - - Non-pauli observables like Hermitian should not be processed into any queue. - The Pauli observables double as Operations, and should therefore be processed - into `_ops` if unowned. - """ - return "_ops" if isinstance(self, Operation) else None - - @property - def is_hermitian(self) -> bool: - """All observables must be hermitian""" - return True - - def compare( - self, - other: Union["Observable", "qml.ops.LinearCombination"], - ) -> bool: - r"""Compares with another :class:`~Observable`, to determine if they are equivalent. - - Observables are equivalent if they represent the same operator - (their matrix representations are equal), and they are defined on the same wires. - - .. Warning:: - - The compare method does **not** check if the matrix representation - of a :class:`~.Hermitian` observable is equal to an equivalent - observable expressed in terms of Pauli matrices. - To do so would require the matrix form to be calculated, which would - drastically increase runtime. - - Returns: - (bool): True if equivalent. - - **Examples** - - >>> ob1 = qml.X(0) @ qml.Identity(1) - >>> ob2 = qml.Hamiltonian([1], [qml.X(0)]) - >>> ob1.compare(ob2) - True - >>> ob1 = qml.X(0) - >>> ob2 = qml.Hermitian(np.array([[0, 1], [1, 0]]), 0) - >>> ob1.compare(ob2) - False - """ - return qml.equal(self, other) - - # ============================================================================= # CV Operations and observables # ============================================================================= @@ -2112,7 +2047,7 @@ def loc(w): for k, w in enumerate(wire_indices): W[loc(w)] = U[loc(k)] elif U.ndim == 2: - W = np.zeros((dim, dim)) if isinstance(self, Observable) else np.eye(dim) + W = np.zeros((dim, dim)) if isinstance(self, CVObservable) else np.eye(dim) W[0, 0] = U[0, 0] for k1, w1 in enumerate(wire_indices): @@ -2283,7 +2218,7 @@ def heisenberg_tr(self, wire_order, inverse=False): return self.heisenberg_expand(U, wire_order) -class CVObservable(CV, Observable): +class CVObservable(CV, Operator): r"""Base class representing continuous-variable observables. CV observables provide a special Heisenberg representation. @@ -2310,6 +2245,12 @@ class CVObservable(CV, Observable): can be useful for some applications where the instance has to be identified """ + is_hermitian = True + + def queue(self, context=QueuingManager): + """Avoids queuing the observable.""" + return self + # pylint: disable=abstract-method ev_order = None #: None, int: Order in `(x, p)` that a CV observable is a polynomial of. @@ -2391,13 +2332,30 @@ def operation_derivative(operation: Operation) -> TensorLike: @qml.BooleanFn def not_tape(obj): - """Returns ``True`` if the object is not a quantum tape""" - return isinstance(obj, qml.tape.QuantumScript) + """Returns ``True`` if the object is not a quantum tape. + + .. warning:: + + **Deprecated**. Use ``not isinstance(obj, qml.tape.QuantumScript)`` instead. + """ + warnings.warn( + "not_tape is deprecated. Use ``not isinstance(obj, qml.tape.QuantumScript)`` instead.", + PennyLaneDeprecationWarning, + ) + return not isinstance(obj, qml.tape.QuantumScript) @qml.BooleanFn def has_gen(obj): - """Returns ``True`` if an operator has a generator defined.""" + """Returns ``True`` if an operator has a generator defined. + + .. warning:: + + **Deprecated**. Use ``obj.has_generator`` instead. + """ + warnings.warn( + "has_gen is deprecated. Use Operator.has_generator instead.", PennyLaneDeprecationWarning + ) if isinstance(obj, Operator): return obj.has_generator try: @@ -2409,42 +2367,87 @@ def has_gen(obj): @qml.BooleanFn def has_grad_method(obj): - """Returns ``True`` if an operator has a grad_method defined.""" + """Returns ``True`` if an operator has a grad_method defined. + + .. warning:: + + **Deprecated**: Use ``obj.grad_method is not None`` instead. + """ + warnings.warn( + "has_grad_method is deprecated. Use obj.grad_method is not None instead. ", + PennyLaneDeprecationWarning, + ) return obj.grad_method is not None @qml.BooleanFn def has_multipar(obj): """Returns ``True`` if an operator has more than one parameter - according to ``num_params``.""" + according to ``num_params``. + + .. warning:: + + **Deprecated**: Use ``obj.num_params > 1`` instead. + """ + warnings.warn( + "has_multipar is deprecated. Use obj.num_params > 1 instead.", PennyLaneDeprecationWarning + ) return obj.num_params > 1 @qml.BooleanFn def has_nopar(obj): """Returns ``True`` if an operator has no parameters - according to ``num_params``.""" + according to ``num_params``. + + .. warning:: + + **Deprecated**: Use ``obj.num_params == 0`` instead. + """ + warnings.warn( + "has_nopar is deprecated. Use obj.num_params == 0 instead.", PennyLaneDeprecationWarning + ) return obj.num_params == 0 @qml.BooleanFn def has_unitary_gen(obj): """Returns ``True`` if an operator has a unitary_generator - according to the ``has_unitary_generator`` flag.""" + according to the ``has_unitary_generator`` flag. + + .. warning:: + + **Deprecated**: Use ``obj in qml.ops.qubit.attributes.has_unitary_gen`` insteaed. + + """ + warnings.warn( + "has_unitary_gen is deprecated. Use `obj in qml.ops.qubit.attributes.has_unitary_generator` instead.", + PennyLaneDeprecationWarning, + ) # Linting check disabled as static analysis can misidentify qml.ops as the set instance qml.ops.qubit.ops return obj in qml.ops.qubit.attributes.has_unitary_generator # pylint:disable=no-member @qml.BooleanFn def is_measurement(obj): - """Returns ``True`` if an operator is a ``MeasurementProcess`` instance.""" + """Returns ``True`` if an operator is a ``MeasurementProcess`` instance. + + .. warning:: + + **Deprecated**: Use ``isinstance(obj, qml.measurements.MeasurementProcess)`` instead. + """ + warnings.warn( + "is_measurement is deprecated. Use isinstance(obj, qml.measurements.MeasurementProcess) instead.", + PennyLaneDeprecationWarning, + ) return isinstance(obj, qml.measurements.MeasurementProcess) @qml.BooleanFn def is_trainable(obj): """Returns ``True`` if any of the parameters of an operator is trainable - according to ``qml.math.requires_grad``.""" + according to ``qml.math.requires_grad``. + """ return any(qml.math.requires_grad(p) for p in obj.parameters) @@ -2452,16 +2455,57 @@ def is_trainable(obj): def defines_diagonalizing_gates(obj): """Returns ``True`` if an operator defines the diagonalizing gates. - This helper function is useful if the property is to be checked in - a queuing context, but the resulting gates must not be queued. + .. warning:: + + **Deprecated:** Use ``obj.has_diagonalizing_gates`` instead. + """ + warnings.warn( + "defines_diagonalizing_gates is deprecated. Use obj.has_diagonalizing_gates instead.", + PennyLaneDeprecationWarning, + ) return obj.has_diagonalizing_gates +_gen_is_multi_term_hamiltonian_code = """ +if not isinstance(obj, Operator) or not obj.has_generator: + return False +try: + generator = obj.generator() + _, ops = generator.terms() + return len(ops) > 1 +except TermsUndefinedError: + return False +""" + + @qml.BooleanFn def gen_is_multi_term_hamiltonian(obj): """Returns ``True`` if an operator has a generator defined and it is a Hamiltonian - with more than one term.""" + with more than one term. + + .. warning:: + + **Deprecated**: Use the following code instead: + + .. code-block:: python + + def gen_is_multi_term_hamiltonian(obj): + if not isinstance(obj, Operator) or not obj.has_generator: + return False + try: + generator = obj.generator() + _, ops = generator.terms() + return len(ops) > 1 + except TermsUndefinedError: + return False + + + """ + warnings.warn( + f"gen_is_multiterm_hamiltonian is deprecated. Use {_gen_is_multi_term_hamiltonian_code}", + PennyLaneDeprecationWarning, + ) if not isinstance(obj, Operator) or not obj.has_generator: return False try: @@ -2474,6 +2518,38 @@ def gen_is_multi_term_hamiltonian(obj): def __getattr__(name): """To facilitate StatePrep rename""" + if name == "Observable": + from ._deprecated_observable import Observable # pylint: disable=import-outside-toplevel + + warnings.warn( + "Observable is deprecated and will be removed in v0.43. " + "A generic Operator class should be used instead. " + "If defining an Operator, set the is_hermitian property to True. " + "If checking if an Operator is Hermitian, check the is_hermitian property. ", + PennyLaneDeprecationWarning, + ) + return Observable + if name == "AnyWires": + warnings.warn( + "AnyWires is deprecated and will be removed in v0.43. " + " If your operation accepts any number of wires, set num_wires=None instead.", + PennyLaneDeprecationWarning, + ) + return _WiresEnum.AllWires + if name == "AllWires": + warnings.warn( + "AllWires is deprecated and will be removed in v0.43. " + " If your operation accepts any number of wires, set num_wires=None instead.", + PennyLaneDeprecationWarning, + ) + return _WiresEnum.AllWires + if name == "WiresEnum": + warnings.warn( + "WiresEnum is deprecated and will be removed in v0.43. " + " If your operation accepts any number of wires, set num_wires=None instead.", + PennyLaneDeprecationWarning, + ) + return _WiresEnum if name == "StatePrep": return StatePrepBase raise AttributeError(f"module 'pennylane.operation' has no attribute '{name}'") diff --git a/pennylane/ops/channel.py b/pennylane/ops/channel.py index 837ee72a83a..7dcc344a8fe 100644 --- a/pennylane/ops/channel.py +++ b/pennylane/ops/channel.py @@ -19,7 +19,7 @@ import warnings from pennylane import math as np -from pennylane.operation import AnyWires, Channel +from pennylane.operation import Channel from pennylane.wires import Wires, WiresLike @@ -559,9 +559,6 @@ class PauliError(Channel): [0.70710678, 0. ]]) """ - num_wires = AnyWires - """int: Number of wires that the operator acts on.""" - num_params = 2 """int: Number of trainable parameters that the operator depends on.""" @@ -713,7 +710,6 @@ class QubitChannel(Channel): id (str or None): String representing the operation (optional) """ - num_wires = AnyWires grad_method = None def __init__(self, K_list, wires: WiresLike, id=None): diff --git a/pennylane/ops/cv.py b/pennylane/ops/cv.py index 43258bb47fc..0be58a5cdba 100644 --- a/pennylane/ops/cv.py +++ b/pennylane/ops/cv.py @@ -42,7 +42,7 @@ from scipy.linalg import block_diag from pennylane import math as qml_math -from pennylane.operation import AnyWires, CVObservable, CVOperation +from pennylane.operation import CVObservable, CVOperation from .identity import I, Identity # pylint: disable=unused-import from .meta import Snapshot # pylint: disable=unused-import @@ -667,7 +667,6 @@ class InterferometerUnitary(CVOperation): """ num_params = 1 - num_wires = AnyWires grad_method = None grad_recipe = None @@ -833,7 +832,6 @@ class GaussianState(CVOperation): """ num_params = 2 - num_wires = AnyWires grad_method = "F" def __init__(self, V, r, wires, id=None): @@ -952,7 +950,6 @@ def circuit(): """ num_params = 1 - num_wires = AnyWires grad_method = "F" def __init__(self, state, wires, id=None): @@ -1001,7 +998,6 @@ class FockDensityMatrix(CVOperation): """ num_params = 1 - num_wires = AnyWires grad_method = "F" def __init__(self, state, wires, id=None): @@ -1133,7 +1129,6 @@ class TensorN(CVObservable): """ num_params = 0 - num_wires = AnyWires ev_order = None def __init__(self, wires): @@ -1326,7 +1321,6 @@ class PolyXP(CVObservable): """ num_params = 1 - num_wires = AnyWires grad_method = "F" ev_order = 2 @@ -1385,7 +1379,6 @@ class FockStateProjector(CVObservable): """ num_params = 1 - num_wires = AnyWires grad_method = None ev_order = None diff --git a/pennylane/ops/functions/eigvals.py b/pennylane/ops/functions/eigvals.py index 5f71d62dc72..92af5b4853d 100644 --- a/pennylane/ops/functions/eigvals.py +++ b/pennylane/ops/functions/eigvals.py @@ -113,7 +113,7 @@ def circuit(theta): if not isinstance(op, qml.operation.Operator): if not isinstance(op, (qml.tape.QuantumScript, qml.QNode)) and not callable(op): raise TransformError("Input is not an Operator, tape, QNode, or quantum function") - return _eigvals_tranform(op, k=k, which=which) + return _eigvals_transform(op, k=k, which=which) if isinstance(op, qml.SparseHamiltonian): sparse_matrix = op.sparse_matrix() @@ -129,7 +129,7 @@ def circuit(theta): @partial(transform, is_informative=True) -def _eigvals_tranform( +def _eigvals_transform( tape: QuantumScript, k=1, which="SA" ) -> tuple[QuantumScriptBatch, PostprocessingFn]: def processing_fn(res): diff --git a/pennylane/ops/functions/generator.py b/pennylane/ops/functions/generator.py index d93d503997b..b54480fc952 100644 --- a/pennylane/ops/functions/generator.py +++ b/pennylane/ops/functions/generator.py @@ -120,7 +120,7 @@ def generator(op: qml.operation.Operator, format="prefactor"): ``'observable'``, or ``'hamiltonian'``. See below for more details. Returns: - .Operator or tuple[.Observable, float]: The returned generator, with format/type + .Operator or tuple[.Operator, float]: The returned generator, with format/type dependent on the ``format`` argument. * ``"prefactor"``: Return the generator as ``(obs, prefactor)`` (representing diff --git a/pennylane/ops/identity.py b/pennylane/ops/identity.py index d7bb811f573..25285b9fc4a 100644 --- a/pennylane/ops/identity.py +++ b/pennylane/ops/identity.py @@ -21,13 +21,7 @@ from scipy import sparse import pennylane as qml -from pennylane.operation import ( - AllWires, - AnyWires, - CVObservable, - Operation, - SparseMatrixUndefinedError, -) +from pennylane.operation import CVObservable, Operation, SparseMatrixUndefinedError from pennylane.wires import WiresLike @@ -52,8 +46,6 @@ class Identity(CVObservable, Operation): """ num_params = 0 - num_wires = AnyWires - """int: Number of wires that the operator acts on.""" grad_method = None """Gradient computation method.""" @@ -215,6 +207,10 @@ def adjoint(self): def pow(self, z): return [I(wires=self.wires)] + def queue(self, context=qml.QueuingManager): + context.append(self) + return self + I = Identity r"""The Identity operator @@ -299,9 +295,6 @@ def circuit(): """ - num_wires = AllWires - """int: Number of wires that the operator acts on.""" - num_params = 1 """int: Number of trainable parameters that the operator depends on.""" diff --git a/pennylane/ops/meta.py b/pennylane/ops/meta.py index 8339c930e99..dd3519c1500 100644 --- a/pennylane/ops/meta.py +++ b/pennylane/ops/meta.py @@ -19,10 +19,10 @@ # pylint:disable=abstract-method,arguments-differ,protected-access,invalid-overridden-method, no-member from copy import copy -from typing import Optional +from typing import Literal, Optional, Sequence, Union import pennylane as qml -from pennylane.operation import AnyWires, Operation +from pennylane.operation import Operation from pennylane.wires import Wires, WiresLike @@ -43,9 +43,6 @@ class Barrier(Operation): num_params = 0 """int: Number of trainable parameters that the operator depends on.""" - num_wires = AnyWires - par_domain = None - def __init__(self, wires: WiresLike = (), only_visual=False, id=None): wires = Wires(wires) self.only_visual = only_visual @@ -117,7 +114,6 @@ class WireCut(Operation): """ num_params = 0 - num_wires = AnyWires grad_method = None def __init__(self, wires: WiresLike = (), id=None): @@ -178,6 +174,9 @@ class Snapshot(Operation): measurements during execution. If None, the measurement defaults to `qml.state` on the available wires. + shots (Literal["workflow"], None, int, Sequence[int]): shots to use for the snapshot. + ``"workflow"`` indicates the same number of shots as for the final measurement. + **Example** .. code-block:: python3 @@ -191,34 +190,46 @@ def circuit(): qml.Snapshot("very_important_state") qml.CNOT(wires=[0, 1]) qml.Snapshot() + m = qml.Snapshot("samples", qml.sample(), shots=5) return qml.expval(qml.X(0)) >>> qml.snapshots(circuit)() {0: 1.0, - 'very_important_state': array([0.70710678+0.j, 0. +0.j, 0.70710678+0.j, 0. +0.j]), - 2: array([0.70710678+0.j, 0. +0.j, 0. +0.j, 0.70710678+0.j]), - 'execution_results': 0.0} + 'very_important_state': array([0.70710678+0.j, 0. +0.j, 0.70710678+0.j, 0. +0.j]), + 2: array([0.70710678+0.j, 0. +0.j, 0. +0.j, 0.70710678+0.j]), + 'samples': array([[1, 1], + [1, 1], + [1, 1], + [1, 1], + [1, 1]]), + 'execution_results': 0.0} .. seealso:: :func:`~.snapshots` """ - num_wires = AnyWires num_params = 0 grad_method = None @classmethod - def _primitive_bind_call(cls, tag=None, measurement=None): + def _primitive_bind_call(cls, tag=None, measurement=None, shots="workflow"): if measurement is None: - return cls._primitive.bind(measurement=measurement, tag=tag) - return cls._primitive.bind(measurement, tag=tag) - - def __init__(self, tag: Optional[str] = None, measurement=None): + return cls._primitive.bind(measurement=measurement, tag=tag, shots=shots) + return cls._primitive.bind(measurement, tag=tag, shots=shots) + + def __init__( + self, + tag: Optional[str] = None, + measurement=None, + shots: Union[Literal["workflow"], None, int, Sequence[int]] = "workflow", + ): if tag and not isinstance(tag, str): raise ValueError("Snapshot tags can only be of type 'str'") self.tag = tag if measurement is None: measurement = qml.state() + if isinstance(measurement, qml.measurements.StateMP) and shots == "workflow": + shots = None # always use analytic with state if isinstance(measurement, qml.measurements.MidMeasureMP): raise ValueError("Mid-circuit measurements can not be used in snapshots.") if isinstance(measurement, qml.measurements.MeasurementProcess): @@ -230,17 +241,20 @@ def __init__(self, tag: Optional[str] = None, measurement=None): ) self.hyperparameters["measurement"] = measurement - super().__init__(wires=[]) + self.hyperparameters["shots"] = ( + shots if shots == "workflow" else qml.measurements.Shots(shots) + ) + super().__init__(wires=measurement.wires) def label(self, decimals=None, base_label=None, cache=None): return "|Snap|" def _flatten(self): - return (self.hyperparameters["measurement"],), (self.tag,) + return (self.hyperparameters["measurement"],), (self.tag, self.hyperparameters["shots"]) @classmethod def _unflatten(cls, data, metadata): - return cls(tag=metadata[0], measurement=data[0]) + return cls(tag=metadata[0], measurement=data[0], shots=metadata[1]) # pylint: disable=W0613 @staticmethod @@ -248,14 +262,16 @@ def compute_decomposition(*params, wires=None, **hyperparameters): return [] def _controlled(self, _): - return Snapshot(tag=self.tag, measurement=self.hyperparameters["measurement"]) + return Snapshot(tag=self.tag, **self.hyperparameters) def adjoint(self): - return Snapshot(tag=self.tag, measurement=self.hyperparameters["measurement"]) + return Snapshot(tag=self.tag, **self.hyperparameters) def map_wires(self, wire_map: dict[Hashable, Hashable]) -> "Snapshot": new_measurement = self.hyperparameters["measurement"].map_wires(wire_map) - return Snapshot(tag=self.tag, measurement=new_measurement) + return Snapshot( + tag=self.tag, measurement=new_measurement, shots=self.hyperparameters["shots"] + ) # Since measurements are captured as variables in plxpr with the capture module, @@ -264,5 +280,5 @@ def map_wires(self, wire_map: dict[Hashable, Hashable]) -> "Snapshot": if Snapshot._primitive: # pylint: disable=protected-access @Snapshot._primitive.def_impl # pylint: disable=protected-access - def _(measurement, tag=None): - return type.__call__(Snapshot, tag=tag, measurement=measurement) + def _(measurement, tag=None, shots="workflow"): + return type.__call__(Snapshot, tag=tag, measurement=measurement, shots=shots) diff --git a/pennylane/ops/op_math/adjoint.py b/pennylane/ops/op_math/adjoint.py index fe5bf1d91a4..d787e34aeb2 100644 --- a/pennylane/ops/op_math/adjoint.py +++ b/pennylane/ops/op_math/adjoint.py @@ -18,9 +18,10 @@ from typing import Callable, overload import pennylane as qml +from pennylane._deprecated_observable import Observable from pennylane.compiler import compiler from pennylane.math import conj, moveaxis, transpose -from pennylane.operation import Observable, Operation, Operator +from pennylane.operation import Operation, Operator from pennylane.queuing import QueuingManager from .symbolicop import SymbolicOp diff --git a/pennylane/ops/op_math/composite.py b/pennylane/ops/op_math/composite.py index 19fee35a5b7..1cdfdb379e9 100644 --- a/pennylane/ops/op_math/composite.py +++ b/pennylane/ops/op_math/composite.py @@ -100,6 +100,8 @@ def _check_batching(self): self._batch_size = batch_sizes.pop() if batch_sizes else None def __repr__(self): + if len(self) == 0: + return f"{type(self).__name__}()" return f" {self._op_symbol} ".join( [f"({op})" if op.arithmetic_depth > 0 else f"{op}" for op in self] ) diff --git a/pennylane/ops/op_math/condition.py b/pennylane/ops/op_math/condition.py index d268afd4206..28c8d589bf5 100644 --- a/pennylane/ops/op_math/condition.py +++ b/pennylane/ops/op_math/condition.py @@ -23,7 +23,7 @@ from pennylane.capture import FlatFn from pennylane.compiler import compiler from pennylane.measurements import MeasurementValue, MidMeasureMP, get_mcm_predicates -from pennylane.operation import AnyWires, Operation, Operator +from pennylane.operation import Operation, Operator from pennylane.ops.op_math.symbolicop import SymbolicOp @@ -88,8 +88,6 @@ class Conditional(SymbolicOp, Operation): can be useful for some applications where the instance has to be identified """ - num_wires = AnyWires - def __init__(self, expr, then_op: Type[Operation], id=None): self.hyperparameters["meas_val"] = expr self._name = f"Conditional({then_op.name})" diff --git a/pennylane/ops/op_math/controlled_ops.py b/pennylane/ops/op_math/controlled_ops.py index edc2406c44c..c8ffe4bf15b 100644 --- a/pennylane/ops/op_math/controlled_ops.py +++ b/pennylane/ops/op_math/controlled_ops.py @@ -24,10 +24,9 @@ import pennylane as qml from pennylane.decomposition import add_decomps, register_resources -from pennylane.operation import AnyWires, Wires from pennylane.ops.qubit.parametric_ops_single_qubit import stack_last from pennylane.typing import TensorLike -from pennylane.wires import WiresLike +from pennylane.wires import Wires, WiresLike from .controlled import ControlledOp from .controlled_decompositions import decompose_mcx @@ -94,9 +93,6 @@ class ControlledQubitUnitary(ControlledOp): >>> qml.ControlledQubitUnitary(U, wires=[0, 1, 2, 3], control_values=[False, True, True]) """ - num_wires = AnyWires - """int: Number of wires that the operator acts on.""" - num_params = 1 """int: Number of trainable parameters that the operator depends on.""" @@ -1319,9 +1315,6 @@ class MultiControlledX(ControlledOp): is_self_inverse = True """bool: Whether or not the operator is self-inverse.""" - num_wires = AnyWires - """int: Number of wires the operation acts on.""" - num_params = 0 """int: Number of trainable parameters that the operator depends on.""" diff --git a/pennylane/ops/op_math/decompositions/unitary_decompositions.py b/pennylane/ops/op_math/decompositions/unitary_decompositions.py index 7167e17e248..ee23839b38f 100644 --- a/pennylane/ops/op_math/decompositions/unitary_decompositions.py +++ b/pennylane/ops/op_math/decompositions/unitary_decompositions.py @@ -679,8 +679,9 @@ def _decompose_3_cnots(U, wires, initial_phase): # -╭V- = -╭X--RZ(d)--╭C---------╭X--╭SWAP- # -╰V- = -╰C--RY(b)--╰X--RY(a)--╰C--╰SWAP- - RZd = ops.RZ.compute_matrix(math.cast_like(delta, 1j)) - RYb = ops.RY.compute_matrix(beta) + EPS = math.finfo(delta.dtype).eps + RZd = ops.RZ.compute_matrix(math.cast_like(delta + 5 * EPS, 1j)) + RYb = ops.RY.compute_matrix(beta + 5 * EPS) RYa = ops.RY.compute_matrix(alpha) V_mats = [ diff --git a/pennylane/ops/op_math/exp.py b/pennylane/ops/op_math/exp.py index 52ac51c1700..4b1a7e8ef52 100644 --- a/pennylane/ops/op_math/exp.py +++ b/pennylane/ops/op_math/exp.py @@ -23,7 +23,6 @@ from pennylane import math from pennylane.math import expand_matrix from pennylane.operation import ( - AnyWires, DecompositionUndefinedError, GeneratorUndefinedError, Operation, @@ -297,11 +296,11 @@ def _smart_decomposition(self, coeff, base): for op_name in qml.ops.qubit.__all__: # pylint:disable=no-member op_class = getattr(qml.ops.qubit, op_name) # pylint:disable=no-member if op_class.has_generator: - if op_class.num_wires == AnyWires: + if op_class.num_wires is None: has_generator_types_anywires.append(op_class) elif op_class.num_wires == len(base.wires): has_generator_types.append(op_class) - # Ensure op_class.num_wires == base.num_wires before op_class.num_wires == AnyWires + # Ensure op_class.num_wires == base.num_wires before op_class.num_wires is None has_generator_types.extend(has_generator_types_anywires) for op_class in has_generator_types: diff --git a/pennylane/ops/op_math/linear_combination.py b/pennylane/ops/op_math/linear_combination.py index f4ab50683c9..24d0fcf9fc0 100644 --- a/pennylane/ops/op_math/linear_combination.py +++ b/pennylane/ops/op_math/linear_combination.py @@ -16,13 +16,14 @@ """ import itertools import numbers +import warnings # pylint: disable=too-many-arguments, protected-access, too-many-instance-attributes from copy import copy from typing import Union import pennylane as qml -from pennylane.operation import Observable, Operator +from pennylane.operation import Operator from .sum import Sum @@ -39,7 +40,7 @@ class LinearCombination(Sum): Args: coeffs (tensor_like): coefficients of the ``LinearCombination`` expression - observables (Iterable[Observable]): observables in the ``LinearCombination`` expression, of same length as ``coeffs`` + observables (Iterable[Operator]): observables in the ``LinearCombination`` expression, of same length as ``coeffs`` grouping_type (str): If not ``None``, compute and store information on how to group commuting observables upon initialization. This information may be accessed when a :class:`~.QNode` containing this ``LinearCombination`` is executed on devices. The string refers to the type of binary relation between Pauli words. @@ -103,7 +104,6 @@ class LinearCombination(Sum): using the :func:`compute_grouping ` method. """ - num_wires = qml.operation.AnyWires grad_method = "A" # supports analytic gradients batch_size = None ndim_params = None # could be (0,) * len(coeffs), but it is not needed. Define at class-level @@ -127,6 +127,7 @@ def __init__( observables: list[Operator], grouping_type=None, method="lf", + *, _grouping_indices=None, _pauli_rep=None, id=None, @@ -190,7 +191,7 @@ def ops(self): """Return the operators defining the LinearCombination. Returns: - Iterable[Observable]): observables in the LinearCombination expression + Iterable[Operator]): observables in the LinearCombination expression """ return self._ops @@ -346,7 +347,11 @@ def compare(self, other): >>> ob1.compare(ob2) False """ - + warnings.warn( + "The compare method is deprecated and will be removed in v0.43." + " op1 == op2 or qml.equal should be used instead.", + qml.exceptions.PennyLaneDeprecationWarning, + ) if isinstance(other, (Operator)): if (pr1 := self.pauli_rep) is not None and (pr2 := other.pauli_rep) is not None: pr1.simplify() @@ -357,9 +362,7 @@ def compare(self, other): op2 = other.simplify() return qml.equal(op1, op2) - raise ValueError( - "Can only compare a LinearCombination, and a LinearCombination/Observable/Tensor." - ) + raise ValueError("Can only compare a LinearCombination and an Operator.") def __matmul__(self, other: Operator) -> Operator: """The product operation between Operator objects.""" @@ -396,7 +399,7 @@ def __matmul__(self, other: Operator) -> Operator: return NotImplemented def __add__(self, H: Union[numbers.Number, Operator]) -> Operator: - r"""The addition operation between a LinearCombination and a LinearCombination/Tensor/Observable.""" + r"""The addition operation between a LinearCombination and an Operator.""" ops = copy(self.ops) self_coeffs = self.coeffs @@ -419,7 +422,12 @@ def __add__(self, H: Union[numbers.Number, Operator]) -> Operator: ops.append(H) return qml.ops.LinearCombination(coeffs, ops) + return NotImplemented + def __sub__(self, H: Operator) -> Operator: + r"""The subtraction operation between a LinearCombination and an Operator.""" + if isinstance(H, Operator): + return self + qml.s_prod(-1.0, H, lazy=False) return NotImplemented __radd__ = __add__ @@ -435,12 +443,6 @@ def __mul__(self, a: Union[int, float, complex]) -> "LinearCombination": __rmul__ = __mul__ - def __sub__(self, H: Observable) -> Observable: - r"""The subtraction operation between a LinearCombination and a LinearCombination/Observable.""" - if isinstance(H, (LinearCombination, Observable)): - return self + qml.s_prod(-1.0, H, lazy=False) - return NotImplemented - def queue( self, context: Union[qml.QueuingManager, qml.queuing.AnnotatedQueue] = qml.QueuingManager ): @@ -551,7 +553,7 @@ class Hamiltonian: Args: coeffs (tensor_like): coefficients of the Hamiltonian expression - observables (Iterable[Observable]): observables in the Hamiltonian expression, of same length as coeffs + observables (Iterable[Operator]): observables in the Hamiltonian expression, of same length as coeffs grouping_type (str): If not None, compute and store information on how to group commuting observables upon initialization. This information may be accessed when QNodes containing this Hamiltonian are executed on devices. The string refers to the type of binary relation between Pauli words. diff --git a/pennylane/ops/op_math/pow.py b/pennylane/ops/op_math/pow.py index 80cb4a17ab5..08b06887d61 100644 --- a/pennylane/ops/op_math/pow.py +++ b/pennylane/ops/op_math/pow.py @@ -21,10 +21,10 @@ import pennylane as qml from pennylane import math as qmlmath +from pennylane._deprecated_observable import Observable from pennylane.operation import ( AdjointUndefinedError, DecompositionUndefinedError, - Observable, Operation, PowUndefinedError, SparseMatrixUndefinedError, diff --git a/pennylane/ops/op_math/sum.py b/pennylane/ops/op_math/sum.py index e7544302f38..21bc2f9cd6a 100644 --- a/pennylane/ops/op_math/sum.py +++ b/pennylane/ops/op_math/sum.py @@ -280,12 +280,16 @@ def grouping_indices(self, value): @handle_recursion_error def __str__(self): """String representation of the Sum.""" + if len(self) == 0: + return f"{type(self).__name__}()" ops = self.operands return " + ".join(f"{str(op)}" if i == 0 else f"{str(op)}" for i, op in enumerate(ops)) @handle_recursion_error def __repr__(self): """Terminal representation for Sum""" + if len(self) == 0: + return "Sum()" # post-processing the flat str() representation # We have to do it like this due to the possible # nesting of Sums, e.g. X(0) + X(1) + X(2) is a sum(sum(X(0), X(1)), X(2)) diff --git a/pennylane/ops/qubit/arithmetic_ops.py b/pennylane/ops/qubit/arithmetic_ops.py index 2a474049f59..b872a5a4f9f 100644 --- a/pennylane/ops/qubit/arithmetic_ops.py +++ b/pennylane/ops/qubit/arithmetic_ops.py @@ -23,7 +23,7 @@ import pennylane as qml from pennylane.decomposition import add_decomps, register_resources -from pennylane.operation import AnyWires, FlatPytree, Operation +from pennylane.operation import FlatPytree, Operation from pennylane.ops import Identity from pennylane.typing import TensorLike from pennylane.wires import Wires, WiresLike @@ -401,7 +401,6 @@ class IntegerComparator(Operation): """ is_self_inverse: bool = True - num_wires = AnyWires num_params: int = 0 """int: Number of trainable parameters that the operator depends on.""" diff --git a/pennylane/ops/qubit/matrix_ops.py b/pennylane/ops/qubit/matrix_ops.py index 7b4c7daf0a5..8c3600ef295 100644 --- a/pennylane/ops/qubit/matrix_ops.py +++ b/pennylane/ops/qubit/matrix_ops.py @@ -39,7 +39,7 @@ transpose, zeros, ) -from pennylane.operation import AnyWires, DecompositionUndefinedError, FlatPytree, Operation +from pennylane.operation import DecompositionUndefinedError, FlatPytree, Operation from pennylane.ops.op_math.decompositions.unitary_decompositions import ( rot_decomp_rule, two_qubit_decomp_rule, @@ -135,9 +135,6 @@ class QubitUnitary(Operation): 0.0 """ - num_wires = AnyWires - """int: Number of wires that the operator acts on.""" - num_params = 1 """int: Number of trainable parameters that the operator depends on.""" @@ -381,9 +378,6 @@ class DiagonalQubitUnitary(Operation): wires (Sequence[int] or int): the wire(s) the operation acts on """ - num_wires = AnyWires - """int: Number of wires that the operator acts on.""" - num_params = 1 """int: Number of trainable parameters that the operator depends on.""" @@ -628,9 +622,6 @@ class BlockEncode(Operation): num_params = 1 """int: Number of trainable parameters that the operator depends on.""" - num_wires = AnyWires - """int: Number of wires that the operator acts on.""" - ndim_params = (2,) """tuple[int]: Number of dimensions per trainable parameter that the operator depends on.""" diff --git a/pennylane/ops/qubit/non_parametric_ops.py b/pennylane/ops/qubit/non_parametric_ops.py index 9dbbe03513a..b6102340410 100644 --- a/pennylane/ops/qubit/non_parametric_ops.py +++ b/pennylane/ops/qubit/non_parametric_ops.py @@ -25,8 +25,9 @@ from scipy import sparse import pennylane as qml +from pennylane._deprecated_observable import Observable from pennylane.decomposition import add_decomps, register_resources -from pennylane.operation import Observable, Operation +from pennylane.operation import Operation from pennylane.typing import TensorLike from pennylane.wires import Wires, WiresLike @@ -50,6 +51,8 @@ class Hadamard(Observable, Operation): wires (Sequence[int] or int): the wire the operation acts on """ + is_hermitian = True + num_wires = 1 """int: Number of wires that the operator acts on.""" @@ -58,8 +61,6 @@ class Hadamard(Observable, Operation): resource_keys = set() - _queue_category = "_ops" - def __init__(self, wires: WiresLike, id: Optional[str] = None): super().__init__(wires=wires, id=id) @@ -266,6 +267,8 @@ class PauliX(Observable, Operation): wires (Sequence[int] or int): the wire the operation acts on """ + is_hermitian = True + num_wires = 1 """int: Number of wires that the operator acts on.""" @@ -479,6 +482,8 @@ class PauliY(Observable, Operation): wires (Sequence[int] or int): the wire the operation acts on """ + is_hermitian = True + num_wires = 1 """int: Number of wires that the operator acts on.""" @@ -691,6 +696,7 @@ class PauliZ(Observable, Operation): wires (Sequence[int] or int): the wire the operation acts on """ + is_hermitian = True num_wires = 1 num_params = 0 """int: Number of trainable parameters that the operator depends on.""" @@ -703,8 +709,6 @@ class PauliZ(Observable, Operation): resource_keys = set() - _queue_category = "_ops" - @property def pauli_rep(self): if self._pauli_rep is None: @@ -1788,10 +1792,12 @@ def compute_decomposition(wires: WiresLike) -> list[qml.operation.Operator]: ] def pow(self, z: Union[int, float]) -> list[qml.operation.Operator]: - z_mod2 = z % 2 - if abs(z_mod2 - 0.5) < 1e-6: + z_mod4 = z % 4 + if abs(z_mod4 - 0.5) < 1e-6: return [SISWAP(wires=self.wires)] - return super().pow(z_mod2) + if abs(z_mod4 - 2) < 1e-6: + return [qml.Z(wires=self.wires[0]), qml.Z(wires=self.wires[1])] + return super().pow(z_mod4) def _iswap_decomp_resources(): @@ -1962,8 +1968,12 @@ def compute_decomposition(wires: WiresLike) -> list[qml.operation.Operator]: ] def pow(self, z: Union[int, float]) -> list[qml.operation.Operator]: - z_mod4 = z % 4 - return [ISWAP(wires=self.wires)] if z_mod4 == 2 else super().pow(z_mod4) + z_mod8 = z % 8 + if abs(z_mod8 - 2) < 1e-6: + return [ISWAP(wires=self.wires)] + if abs(z_mod8 - 4) < 1e-6: + return [qml.Z(wires=self.wires[0]), qml.Z(wires=self.wires[1])] + return super().pow(z_mod8) def _siswap_decomp_resources(): diff --git a/pennylane/ops/qubit/observables.py b/pennylane/ops/qubit/observables.py index b14cd1fd53f..2626aa75a02 100644 --- a/pennylane/ops/qubit/observables.py +++ b/pennylane/ops/qubit/observables.py @@ -25,7 +25,8 @@ from scipy.sparse import csr_matrix, spmatrix import pennylane as qml -from pennylane.operation import AnyWires, Observable, Operation +from pennylane._deprecated_observable import Observable +from pennylane.operation import Operation from pennylane.typing import TensorLike from pennylane.wires import Wires, WiresLike @@ -58,7 +59,9 @@ class Hermitian(Observable): id (str or None): String representing the operation (optional) """ - num_wires = AnyWires + _queue_category = None + + is_hermitian = True num_params = 1 """int: Number of trainable parameters that the operator depends on.""" @@ -297,7 +300,8 @@ class SparseHamiltonian(Observable): >>> H_sparse = qml.SparseHamiltonian(Hmat, wires) """ - num_wires = AnyWires + _queue_category = None + is_hermitian = True num_params = 1 """int: Number of trainable parameters that the operator depends on.""" @@ -445,8 +449,8 @@ class Projector(Observable): """ + is_hermitian = True name = "Projector" - num_wires = AnyWires num_params = 1 """int: Number of trainable parameters that the operator depends on.""" @@ -692,6 +696,7 @@ def __init__(self, state: TensorLike, wires: WiresLike, id: Optional[str] = None def __new__(cls, *_, **__): # pylint: disable=arguments-differ return object.__new__(cls) + # pylint: disable=unused-argument def label( self, decimals: Optional[int] = None, diff --git a/pennylane/ops/qubit/parametric_ops_multi_qubit.py b/pennylane/ops/qubit/parametric_ops_multi_qubit.py index a7b06ce9a2f..d5c66169926 100644 --- a/pennylane/ops/qubit/parametric_ops_multi_qubit.py +++ b/pennylane/ops/qubit/parametric_ops_multi_qubit.py @@ -26,7 +26,7 @@ import pennylane as qml from pennylane.decomposition import add_decomps, register_resources from pennylane.math import expand_matrix -from pennylane.operation import AnyWires, FlatPytree, Operation +from pennylane.operation import FlatPytree, Operation from pennylane.typing import TensorLike from pennylane.wires import Wires, WiresLike @@ -61,7 +61,6 @@ class MultiRZ(Operation): id (str or None): String representing the operation (optional) """ - num_wires = AnyWires num_params = 1 """int: Number of trainable parameters that the operator depends on.""" @@ -281,7 +280,6 @@ class PauliRot(Operation): 0.8775825618903724 """ - num_wires = AnyWires num_params = 1 """int: Number of trainable parameters that the operator depends on.""" @@ -661,7 +659,6 @@ class PCPhase(Operation): [0. +0.j 0. +0.j 0. +0.j 0.33-0.94j]] """ - num_wires = AnyWires num_params = 1 """int: Number of trainable parameters that the operator depends on.""" ndim_params = (0,) diff --git a/pennylane/ops/qubit/special_unitary.py b/pennylane/ops/qubit/special_unitary.py index 4cc5b0ae84b..bd79759e208 100644 --- a/pennylane/ops/qubit/special_unitary.py +++ b/pennylane/ops/qubit/special_unitary.py @@ -24,7 +24,7 @@ import pennylane as qml from pennylane.decomposition import add_decomps, register_resources -from pennylane.operation import AnyWires, FlatPytree, Operation +from pennylane.operation import FlatPytree, Operation from pennylane.ops.qubit.parametric_ops_multi_qubit import PauliRot from pennylane.typing import TensorLike from pennylane.wires import WiresLike @@ -232,7 +232,7 @@ class SpecialUnitary(Operation): .. seealso:: For more details on using this operator in applications, see the - :doc:`SU(N) gate demo `. + `SU(N) gate demo `__. .. warning:: @@ -401,9 +401,6 @@ class SpecialUnitary(Operation): """ - num_wires = AnyWires - """int: Number of wires that the operator acts on.""" - num_params = 1 """int: Number of trainable parameters that the operator depends on.""" diff --git a/pennylane/ops/qubit/state_preparation.py b/pennylane/ops/qubit/state_preparation.py index 7bfc9e521aa..d0268afb2f2 100644 --- a/pennylane/ops/qubit/state_preparation.py +++ b/pennylane/ops/qubit/state_preparation.py @@ -26,7 +26,7 @@ import pennylane as qml from pennylane import math from pennylane.decomposition import add_decomps, register_resources -from pennylane.operation import AnyWires, Operation, Operator, StatePrepBase +from pennylane.operation import Operation, Operator, StatePrepBase from pennylane.templates.state_preparations import MottonenStatePreparation from pennylane.typing import TensorLike from pennylane.wires import WireError, Wires, WiresLike @@ -337,7 +337,6 @@ def circuit(state=None): """ - num_wires = AnyWires num_params = 1 """int: Number of trainable parameters that the operator depends on.""" @@ -626,7 +625,6 @@ def circuit(): [0.+0.j 0.+0.j 0.+0.j 0.+0.j]] """ - num_wires = AnyWires num_params = 1 """int: Number of trainable parameters that the operator depends on.""" diff --git a/pennylane/ops/qutrit/channel.py b/pennylane/ops/qutrit/channel.py index 9dd62f6553b..dbfa0188529 100644 --- a/pennylane/ops/qutrit/channel.py +++ b/pennylane/ops/qutrit/channel.py @@ -19,7 +19,7 @@ import numpy as np from pennylane import math -from pennylane.operation import AnyWires, Channel +from pennylane.operation import Channel QUDIT_DIM = 3 @@ -478,7 +478,6 @@ class QutritChannel(Channel): id (str or None): String representing the operation (optional) """ - num_wires = AnyWires grad_method = None def __init__(self, K_list, wires=None, id=None): diff --git a/pennylane/ops/qutrit/matrix_ops.py b/pennylane/ops/qutrit/matrix_ops.py index 217c46f9fac..e7356143151 100644 --- a/pennylane/ops/qutrit/matrix_ops.py +++ b/pennylane/ops/qutrit/matrix_ops.py @@ -19,7 +19,7 @@ import warnings import pennylane as qml -from pennylane.operation import AnyWires, Operation +from pennylane.operation import Operation from pennylane.wires import Wires @@ -50,9 +50,6 @@ class QutritUnitary(Operation): [0.70710678+0.j 0.70710678+0.j 0. +0.j] """ - num_wires = AnyWires - """int: Number of wires that the operator acts on.""" - num_params = 1 """int: Number of trainable parameters that the operator depends on.""" @@ -186,9 +183,6 @@ class ControlledQutritUnitary(QutritUnitary): >>> qml.ControlledQutritUnitary(U, control_wires=[0, 1, 2], wires=3, control_values='012') """ - num_wires = AnyWires - """int: Number of wires that the operator acts on.""" - num_params = 1 """int: Number of trainable parameters that the operator depends on.""" diff --git a/pennylane/ops/qutrit/observables.py b/pennylane/ops/qutrit/observables.py index e8ed7cf02b2..381c7055f76 100644 --- a/pennylane/ops/qutrit/observables.py +++ b/pennylane/ops/qutrit/observables.py @@ -17,7 +17,7 @@ import numpy as np import pennylane as qml # pylint: disable=unused-import -from pennylane.operation import Observable +from pennylane._deprecated_observable import Observable from pennylane.ops.qubit import Hermitian from pennylane.ops.qutrit import QutritUnitary @@ -197,10 +197,15 @@ class GellMann(Observable): """ + is_hermitian = True num_wires = 1 num_params = 0 """int: Number of trainable parameters the operator depends on""" + def queue(self, context=qml.QueuingManager): + """Append the operator to the Operator queue.""" + return self + def __init__(self, wires, index=1, id=None): if not isinstance(index, int) or index < 1 or index > 8: raise ValueError( diff --git a/pennylane/ops/qutrit/state_preparation.py b/pennylane/ops/qutrit/state_preparation.py index 3d66b41d4e8..0046b1628fe 100644 --- a/pennylane/ops/qutrit/state_preparation.py +++ b/pennylane/ops/qutrit/state_preparation.py @@ -19,7 +19,7 @@ import numpy as np from pennylane import math -from pennylane.operation import AnyWires, StatePrepBase +from pennylane.operation import StatePrepBase from pennylane.templates.state_preparations import QutritBasisStatePreparation from pennylane.wires import WireError, Wires @@ -64,7 +64,6 @@ class QutritBasisState(StatePrepBase): [0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 1.+0.j] """ - num_wires = AnyWires num_params = 1 """int: Number of trainable parameters that the operator depends on.""" diff --git a/pennylane/optimize/qng.py b/pennylane/optimize/qng.py index c55a5cb06bd..44778d9bd3d 100644 --- a/pennylane/optimize/qng.py +++ b/pennylane/optimize/qng.py @@ -143,7 +143,7 @@ class QNGOptimizer(GradientDescentOptimizer): .. seealso:: - See the :doc:`quantum natural gradient example ` + See the `quantum natural gradient example `_ for more details on the Fubini-Study metric tensor and this optimization class. Keyword Args: diff --git a/pennylane/optimize/qnspsa.py b/pennylane/optimize/qnspsa.py index 848f8147a24..ad44618fada 100644 --- a/pennylane/optimize/qnspsa.py +++ b/pennylane/optimize/qnspsa.py @@ -77,7 +77,7 @@ class QNSPSAOptimizer: "Simultaneous Perturbation Stochastic Approximation of the Quantum Fisher Information." `Quantum, 5, 567 `_, 2021. - You can also find a walkthrough of the implementation in this :doc:`tutorial `. + You can also find a walkthrough of the implementation in this `tutorial `__. **Examples:** diff --git a/pennylane/optimize/shot_adaptive.py b/pennylane/optimize/shot_adaptive.py index 72ce55fc836..87b33943d96 100644 --- a/pennylane/optimize/shot_adaptive.py +++ b/pennylane/optimize/shot_adaptive.py @@ -213,7 +213,7 @@ def qnode_weighted_random_sampling(qnode, coeffs, observables, shots, argnums, * Args: qnode (.QNode): A QNode that returns the expectation value of a Hamiltonian. coeffs (List[float]): The coefficients of the Hamiltonian being measured - observables (List[Observable]): The terms of the Hamiltonian being measured + observables (List[Operator]]): The terms of the Hamiltonian being measured shots (int): The number of shots used to estimate the Hamiltonian expectation value. These shots are distributed over the terms in the Hamiltonian, as per a Multinomial distribution. diff --git a/pennylane/pauli/dla/center.py b/pennylane/pauli/dla/center.py index 916cd12a61f..a924d8c5450 100644 --- a/pennylane/pauli/dla/center.py +++ b/pennylane/pauli/dla/center.py @@ -44,7 +44,7 @@ def center( Returns: List[Union[Operator, PauliSentence]]: Center of ``g`` - .. seealso:: :func:`~lie_closure`, :func:`~structure_constants`, :class:`~pennylane.pauli.PauliVSpace`, :doc:`Introduction to Dynamical Lie Algebras for quantum practitioners ` + .. seealso:: :func:`~lie_closure`, :func:`~structure_constants`, :class:`~pennylane.pauli.PauliVSpace`, `Introduction to Dynamical Lie Algebras for quantum practitioners `__ **Example** diff --git a/pennylane/pauli/dla/lie_closure.py b/pennylane/pauli/dla/lie_closure.py index 7b83c11fcbb..c25b1cc069e 100644 --- a/pennylane/pauli/dla/lie_closure.py +++ b/pennylane/pauli/dla/lie_closure.py @@ -59,7 +59,7 @@ def lie_closure( Union[list[:class:`~.PauliSentence`], list[:class:`~.Operator`], np.ndarray]: a basis of either :class:`~.PauliSentence`, :class:`~.Operator`, or ``np.ndarray`` instances that is closed under commutators (Lie closure). - .. seealso:: :func:`~structure_constants`, :func:`~center`, :class:`~pennylane.pauli.PauliVSpace`, :doc:`Introduction to Dynamical Lie Algebras for quantum practitioners ` + .. seealso:: :func:`~structure_constants`, :func:`~center`, :class:`~pennylane.pauli.PauliVSpace`, `Introduction to Dynamical Lie Algebras for quantum practitioners `__ **Example** diff --git a/pennylane/pauli/dla/structure_constants.py b/pennylane/pauli/dla/structure_constants.py index c1da0e6e949..2e6011569ae 100644 --- a/pennylane/pauli/dla/structure_constants.py +++ b/pennylane/pauli/dla/structure_constants.py @@ -59,7 +59,7 @@ def structure_constants( Returns: TensorLike: The adjoint representation of shape ``(d, d, d)``, corresponding to indices ``(gamma, alpha, beta)``. - .. seealso:: :func:`~lie_closure`, :func:`~center`, :class:`~pennylane.pauli.PauliVSpace`, :doc:`Introduction to Dynamical Lie Algebras for quantum practitioners ` + .. seealso:: :func:`~lie_closure`, :func:`~center`, :class:`~pennylane.pauli.PauliVSpace`, `Introduction to Dynamical Lie Algebras for quantum practitioners `__ **Example** diff --git a/pennylane/pauli/grouping/group_observables.py b/pennylane/pauli/grouping/group_observables.py index 81f97a25609..cc76991caf0 100644 --- a/pennylane/pauli/grouping/group_observables.py +++ b/pennylane/pauli/grouping/group_observables.py @@ -292,7 +292,7 @@ def pauli_partitions_from_graph(self) -> list[list]: using ``Rustworkx`` graph colouring algorithms based on binary relation determined by ``self.grouping_type``. Returns: - list[list[Observable]]: List of partitions of the Pauli observables made up of mutually (anti-)commuting terms. + list[list[Operator]]]: List of partitions of the Pauli observables made up of mutually (anti-)commuting terms. """ # Get the observables from the indices. itemgetter outperforms list comprehension pauli_partitions = items_partitions_from_idx_partitions( @@ -393,7 +393,7 @@ def compute_partition_indices( and graph colouring method. Args: - observables (list[Observable]): A list of Pauli Observables to be partitioned. + observables (list[Operator]): A list of Pauli operators to be partitioned. grouping_type (str): The type of binary relation between Pauli observables. It can be ``'qwc'``, ``'commuting'``, or ``'anticommuting'``. Defaults to ``'qwc'``. method (str): The graph colouring heuristic to use in solving minimum clique cover. @@ -475,7 +475,7 @@ def group_observables( graph using graph-colouring heuristic algorithms. Args: - observables (list[Operator]): a list of Pauli word ``Observable`` instances (Pauli + observables (list[Operator]): a list of Pauli word ``Operator`` instances (Pauli operation instances and tensor products thereof) coefficients (TensorLike): A tensor or list of coefficients. If not specified, output ``partitioned_coeffs`` is not returned. @@ -488,8 +488,8 @@ def group_observables( Returns: tuple: - * list[list[Observable]]: A list of the obtained groupings. Each grouping - is itself a list of Pauli word ``Observable`` instances. + * list[list[Operator]]: A list of the obtained groupings. Each grouping + is itself a list of Pauli word ``Operator`` instances. * list[TensorLike]: A list of coefficient groupings. Each coefficient grouping is itself a tensor or list of the grouping's corresponding coefficients. This is only returned if coefficients are specified. diff --git a/pennylane/pauli/grouping/optimize_measurements.py b/pennylane/pauli/grouping/optimize_measurements.py index 6678e69f65e..5802a404539 100644 --- a/pennylane/pauli/grouping/optimize_measurements.py +++ b/pennylane/pauli/grouping/optimize_measurements.py @@ -16,7 +16,7 @@ corresponding necessary circuit post-rotations for a given list of Pauli words. """ -from pennylane.operation import Observable +from pennylane.operation import Operator from pennylane.pauli.utils import diagonalize_qwc_groupings from pennylane.typing import Sequence @@ -24,7 +24,7 @@ def optimize_measurements( - observables: Sequence[Observable], + observables: Sequence[Operator], coefficients: Sequence[float] = None, grouping: str = "qwc", colouring_method: str = "lf", @@ -41,7 +41,7 @@ def optimize_measurements( fully-commuting measurement-partitioning approaches respectively. Args: - observables (list[Observable]): a list of Pauli words (Pauli operation instances and tensors + observables (list[Operator]): a list of Pauli words (Pauli operation instances and tensors instances thereof) coefficients (list[float]): a list of float coefficients, for instance the weights of the Pauli words comprising a Hamiltonian @@ -55,7 +55,7 @@ def optimize_measurements( * list[callable]: a list of the post-rotation templates, one for each partition - * list[list[Observable]]: A list of the obtained groupings. Each + * list[list[Operator]]: A list of the obtained groupings. Each grouping is itself a list of Pauli words diagonal in the measurement basis. * list[list[float]]: A list of coefficient groupings. Each diff --git a/pennylane/pauli/utils.py b/pennylane/pauli/utils.py index f1839add9af..58e989e64ac 100644 --- a/pennylane/pauli/utils.py +++ b/pennylane/pauli/utils.py @@ -66,7 +66,7 @@ def is_pauli_word(observable): .. Warning:: This function will only confirm that all operators are Pauli or Identity operators, - and not whether the Observable is mathematically a Pauli word. + and not whether the observable is mathematically a Pauli word. If an Observable consists of multiple Pauli operators targeting the same wire, the function will return ``True`` regardless of any complex coefficients. @@ -247,7 +247,7 @@ def pauli_to_binary(pauli_word, n_qubits=None, wire_map=None, check_is_pauli_wor wire_map = wire_map or {w: i for i, w in enumerate(pauli_word.wires)} if check_is_pauli_word and not is_pauli_word(pauli_word): - raise TypeError(f"Expected a Pauli word Observable instance, instead got {pauli_word}.") + raise TypeError(f"Expected a Pauli word Operator instance, instead got {pauli_word}.") pw = next(iter(pauli_word.pauli_rep)) @@ -274,7 +274,7 @@ def pauli_to_binary(pauli_word, n_qubits=None, wire_map=None, check_is_pauli_wor def binary_to_pauli(binary_vector, wire_map=None): # pylint: disable=too-many-branches - """Converts a binary vector of even dimension to an Observable instance. + """Converts a binary vector of even dimension to an Operator instance. This functions follows the convention that the first half of binary vector components specify PauliX placements while the last half specify PauliZ placements. @@ -395,7 +395,7 @@ def pauli_word_to_string(pauli_word, wire_map=None): 'X' Args: - pauli_word (Union[Observable, Prod, SProd, Sum]): an observable, either a single-qubit observable + pauli_word (Operator): an observable, either a single-qubit observable representing a Pauli group element, or a tensor product of single-qubit observables. wire_map (dict[Union[str, int], int]): dictionary containing all wire labels used in the Pauli word as keys, and unique integer labels as their values @@ -446,7 +446,7 @@ def string_to_pauli_word(pauli_string, wire_map=None): the Pauli word as keys, and unique integer labels as their values Returns: - .Observable: The Pauli word representing of ``pauli_string`` on the wires + .Operator: The Pauli word representing of ``pauli_string`` on the wires enumerated in the wire map. **Example** @@ -518,7 +518,7 @@ def pauli_word_to_matrix(pauli_word, wire_map=None): product at the correct positions. Args: - pauli_word (Union[Observable, Prod, SProd, Sum]): an observable, either a single-qubit observable + pauli_word (Operator): an observable, either a single-qubit observable representing a Pauli group element, or a tensor product of single-qubit observables. wire_map (dict[Union[str, int], int]): dictionary containing all wire labels used in the Pauli word as keys, and unique integer labels as their values @@ -656,7 +656,7 @@ def are_pauli_words_qwc(lst_pauli_words): number of distinct wire labels used to represent the Pauli words. Args: - lst_pauli_words (list[Observable]): List of observables (assumed to be valid Pauli words). + lst_pauli_words (list[Operator]): List of observables (assumed to be valid Pauli words). Returns: (bool): True if they are all qubit-wise commuting, false otherwise. If any of the provided @@ -1051,7 +1051,7 @@ def diagonalize_qwc_pauli_words( """Diagonalizes a list of mutually qubit-wise commutative Pauli words. Args: - qwc_grouping (list[Observable]): a list of observables containing mutually + qwc_grouping (list[Operator]): a list of observables containing mutually qubit-wise commutative Pauli words Returns: @@ -1059,7 +1059,7 @@ def diagonalize_qwc_pauli_words( * list[Operation]: an instance of the qwc_rotation template which diagonalizes the qubit-wise commuting grouping - * list[Observable]: list of Pauli string observables diagonal in + * list[Operator]: list of Pauli string observables diagonal in the computational basis Raises: @@ -1106,7 +1106,7 @@ def diagonalize_qwc_groupings(qwc_groupings): """Diagonalizes a list of qubit-wise commutative groupings of Pauli strings. Args: - qwc_groupings (list[list[Observable]]): a list of mutually qubit-wise commutative groupings + qwc_groupings (list[list[Operator]]): a list of mutually qubit-wise commutative groupings of Pauli string observables Returns: @@ -1115,7 +1115,7 @@ def diagonalize_qwc_groupings(qwc_groupings): * list[list[Operation]]: a list of instances of the qwc_rotation template which diagonalizes the qubit-wise commuting grouping, order corresponding to qwc_groupings - * list[list[Observable]]: a list of QWC groupings diagonalized in the + * list[list[Operator]]: a list of QWC groupings diagonalized in the computational basis, order corresponding to qwc_groupings **Example** diff --git a/pennylane/pulse/__init__.py b/pennylane/pulse/__init__.py index dce117a8dfe..829ddefa27a 100644 --- a/pennylane/pulse/__init__.py +++ b/pennylane/pulse/__init__.py @@ -30,7 +30,7 @@ `jax.readthedocs.io `_. For a demonstration of the basic pulse functionality in PennyLane and running a ctrl-VQE example, see our demo on -:doc:`differentiable pulse programming `. +`differentiable pulse programming `__. Overview -------- diff --git a/pennylane/pulse/hardware_hamiltonian.py b/pennylane/pulse/hardware_hamiltonian.py index 154e8da54d6..a4593c640b8 100644 --- a/pennylane/pulse/hardware_hamiltonian.py +++ b/pennylane/pulse/hardware_hamiltonian.py @@ -285,7 +285,7 @@ class HardwareHamiltonian(ParametrizedHamiltonian): coeffs (Union[float, callable]): coefficients of the Hamiltonian expression, which may be constants or parametrized functions. All functions passed as ``coeffs`` must have two arguments, the first one being the trainable parameters and the second one being time. - observables (Iterable[Observable]): observables in the Hamiltonian expression, of same + observables (Iterable[Operator]): observables in the Hamiltonian expression, of same length as ``coeffs`` Keyword Args: @@ -303,7 +303,7 @@ class HardwareHamiltonian(ParametrizedHamiltonian): """ - # pylint: disable=too-many-arguments + # pylint: disable=too-many-arguments, too-many-positional-arguments def __init__( self, coeffs, diff --git a/pennylane/pulse/parametrized_evolution.py b/pennylane/pulse/parametrized_evolution.py index 240280c4304..1e4f74debec 100644 --- a/pennylane/pulse/parametrized_evolution.py +++ b/pennylane/pulse/parametrized_evolution.py @@ -23,7 +23,7 @@ from typing import Union import pennylane as qml -from pennylane.operation import AnyWires, Operation +from pennylane.operation import Operation from pennylane.ops import functions from pennylane.typing import TensorLike @@ -365,7 +365,6 @@ def circuit(param, time): """ _name = "ParametrizedEvolution" - num_wires = AnyWires grad_method = "A" # pylint: disable=too-many-arguments diff --git a/pennylane/qaoa/cycle.py b/pennylane/qaoa/cycle.py index 05310a44916..4e8228299a3 100644 --- a/pennylane/qaoa/cycle.py +++ b/pennylane/qaoa/cycle.py @@ -414,16 +414,16 @@ def loss_hamiltonian(graph: Union[nx.Graph, rx.PyGraph, rx.PyDiGraph]) -> qml.op def _square_hamiltonian_terms( - coeffs: Iterable[float], ops: Iterable[qml.operation.Observable] -) -> tuple[list[float], list[qml.operation.Observable]]: + coeffs: Iterable[float], ops: Iterable[qml.operation.Operator] +) -> tuple[list[float], list[qml.operation.Operator]]: """Calculates the coefficients and observables that compose the squared Hamiltonian. Args: coeffs (Iterable[float]): coeffients of the input Hamiltonian - ops (Iterable[qml.operation.Observable]): observables of the input Hamiltonian + ops (Iterable[qml.operation.Operator]): observables of the input Hamiltonian Returns: - Tuple[List[float], List[qml.operation.Observable]]: The list of coefficients and list of observables + Tuple[List[float], List[qml.operation.Operator]]: The list of coefficients and list of observables of the squared Hamiltonian. """ squared_coeffs, squared_ops = [], [] diff --git a/pennylane/qchem/factorization.py b/pennylane/qchem/factorization.py index 481ea744cdf..bc5833c8116 100644 --- a/pennylane/qchem/factorization.py +++ b/pennylane/qchem/factorization.py @@ -530,7 +530,7 @@ def basis_rotation(one_electron, two_electron, tol_factor=1.0e-5, **factorizatio :func:`~.factorize` method for all the available options with ``compressed=True``. Returns: - tuple(list[array[float]], list[list[Observable]], list[array[float]]): Tuple containing + tuple(list[array[float]], list[list[Operator]], list[array[float]]): Tuple containing grouped coefficients, grouped observables and basis rotation transformation matrices. **Example** @@ -677,7 +677,7 @@ def _chemist_transform(one_body_tensor=None, two_body_tensor=None, spatial_basis This converts the input two-body tensor :math:`h_{pqrs}` that constructs :math:`\sum_{pqrs} h_{pqrs} a^\dagger_p a^\dagger_q a_r a_s` to a transformed two-body tensor :math:`V_{pqrs}` that follows the chemists' convention to construct :math:`\sum_{pqrs} V_{pqrs} a^\dagger_p a_q a^\dagger_r a_s` - in the spatial basis. During the tranformation, some extra one-body terms come out. These are returned as a one-body tensor :math:`T_{pq}` in the + in the spatial basis. During the transformation, some extra one-body terms come out. These are returned as a one-body tensor :math:`T_{pq}` in the chemists' notation either as is or after summation with the input one-body tensor :math:`h_{pq}`, if provided. Args: diff --git a/pennylane/qchem/tapering.py b/pennylane/qchem/tapering.py index d9df538874c..3e4bb622cb8 100644 --- a/pennylane/qchem/tapering.py +++ b/pennylane/qchem/tapering.py @@ -186,7 +186,7 @@ def paulix_ops(generators, num_qubits): # pylint: disable=protected-access num_qubits (int): number of wires required to define the Hamiltonian Return: - list[Observable]: list of single-qubit Pauli-X operators which will be used to build the + list[Operator]: list of single-qubit Pauli-X operators which will be used to build the Clifford operators :math:`U`. **Example** diff --git a/pennylane/qnn/__init__.py b/pennylane/qnn/__init__.py index 6bc35152fd9..c6e836e0f01 100644 --- a/pennylane/qnn/__init__.py +++ b/pennylane/qnn/__init__.py @@ -17,7 +17,7 @@ .. note:: - Check out our :doc:`Torch ` tutorials for further details. + Check out our `Keras `__ and `Torch `__ tutorials for further details. .. rubric:: Classes diff --git a/pennylane/qnn/cost.py b/pennylane/qnn/cost.py index aacdd0120e2..c7a879c5a19 100644 --- a/pennylane/qnn/cost.py +++ b/pennylane/qnn/cost.py @@ -35,7 +35,7 @@ class SquaredErrorLoss: where ``params`` are the trainable weights of the variational circuit, and ``kwargs`` are any additional keyword arguments that need to be passed to the template. - observables (Iterable[.Observable]): observables to measure during the + observables (Iterable[.Operator]): observables to measure during the final step of each circuit device (Device, Sequence[Device]): Corresponding device(s) where the resulting function should be executed. This can either be a single device, or a list @@ -87,6 +87,7 @@ def ansatz(phis, **kwargs): :doc:`optimizer `. """ + # pylint: disable=too-many-positional-arguments def __init__( self, ansatz, diff --git a/pennylane/resource/first_quantization.py b/pennylane/resource/first_quantization.py index 6db0345d41c..0bf38eb6665 100644 --- a/pennylane/resource/first_quantization.py +++ b/pennylane/resource/first_quantization.py @@ -19,7 +19,7 @@ import numpy as np import scipy as sp -from pennylane.operation import AnyWires, Operation +from pennylane.operation import Operation class FirstQuantization(Operation): @@ -79,7 +79,6 @@ class FirstQuantization(Operation): error distribution takes place inside the functions. """ - num_wires = AnyWires grad_method = None def __init__( diff --git a/pennylane/resource/second_quantization.py b/pennylane/resource/second_quantization.py index f43211afca6..0c47ef85884 100644 --- a/pennylane/resource/second_quantization.py +++ b/pennylane/resource/second_quantization.py @@ -18,7 +18,7 @@ # pylint: disable=no-self-use, too-many-arguments, too-many-instance-attributes, too-many-positional-arguments import numpy as np -from pennylane.operation import AnyWires, Operation +from pennylane.operation import Operation from pennylane.qchem import factorize @@ -87,7 +87,6 @@ class DoubleFactorization(Operation): of [`PRX Quantum 2, 030305 (2021) `_]. """ - num_wires = AnyWires grad_method = None def __init__( diff --git a/pennylane/shadows/__init__.py b/pennylane/shadows/__init__.py index 79cd2709ee0..7fa3017142c 100644 --- a/pennylane/shadows/__init__.py +++ b/pennylane/shadows/__init__.py @@ -15,7 +15,7 @@ Overview -------- -This module contains functionality for performing :doc:`classical shadows ` measurements. +This module contains functionality for performing `classical shadows `__ measurements. .. currentmodule:: pennylane @@ -49,7 +49,7 @@ .. note:: As per `arXiv:2103.07510 `_, when computing multiple expectation values it is advisable to directly estimate the desired observables by simultaneously measuring qubit-wise-commuting terms. One way of doing this in PennyLane is via :class:`~pennylane.Hamiltonian` and setting ``grouping_type="qwc"``. For more details on this topic, see the PennyLane demo - on :doc:`estimating expectation values with classical shadows `. + on `estimating expectation values with classical shadows `__. A :class:`ClassicalShadow` is a classical description of a quantum state that is capable of reproducing expectation values of local Pauli observables, see `arXiv:2002.08953 `_. diff --git a/pennylane/shadows/classical_shadow.py b/pennylane/shadows/classical_shadow.py index f63a0077e62..09a22ac2bb7 100644 --- a/pennylane/shadows/classical_shadow.py +++ b/pennylane/shadows/classical_shadow.py @@ -47,7 +47,7 @@ class ClassicalShadow: .. note:: As per `arXiv:2103.07510 `_, when computing multiple expectation values it is advisable to directly estimate the desired observables by simultaneously measuring qubit-wise-commuting terms. One way of doing this in PennyLane is via :class:`~pennylane.Hamiltonian` and setting ``grouping_type="qwc"``. For more details on this topic, see our demo - on :doc:`estimating expectation values with classical shadows `. + on `estimating expectation values with classical shadows `__. Args: bits (tensor): recorded measurement outcomes in random Pauli bases. @@ -56,7 +56,7 @@ class ClassicalShadow: they appear in the columns of ``bits`` and ``recipes``. If None, defaults to ``range(n)``, where ``n`` is the number of measured wires. - .. seealso:: Demo on :doc:`Estimating observables with classical shadows in the Pauli basis `, :func:`~.pennylane.classical_shadow` + .. seealso:: Demo on `Estimating observables with classical shadows in the Pauli basis `__, :func:`~.pennylane.classical_shadow` **Example** @@ -286,7 +286,7 @@ def expval(self, H, k=1): save quantum circuit executions. Args: - H (qml.Observable): Observable to compute the expectation value + H (qml.operation.Operator): Observable to compute the expectation value k (int): Number of equal parts to split the shadow's measurements to compute the median of means. ``k=1`` (default) corresponds to simply taking the mean over all measurements. Returns: diff --git a/pennylane/tape/operation_recorder.py b/pennylane/tape/operation_recorder.py index bfbb6339645..49c2c4190dd 100644 --- a/pennylane/tape/operation_recorder.py +++ b/pennylane/tape/operation_recorder.py @@ -39,7 +39,7 @@ class OperationRecorder(QuantumScript, AnnotatedQueue): Alternatively, the :attr:`~.OperationRecorder.queue` attribute can be used - to directly access the applied :class:`~.Operation` and :class:`~.Observable` + to directly access the applied :class:`~.Operation` and :class:`~.Operator` objects. """ diff --git a/pennylane/tape/qscript.py b/pennylane/tape/qscript.py index c101f0b8fe9..ce2d2851a1e 100644 --- a/pennylane/tape/qscript.py +++ b/pennylane/tape/qscript.py @@ -27,7 +27,7 @@ import pennylane as qml from pennylane.measurements import MeasurementProcess from pennylane.measurements.shots import Shots, ShotsLike -from pennylane.operation import _UNSET_BATCH_SIZE, Observable, Operation, Operator +from pennylane.operation import _UNSET_BATCH_SIZE, Operation, Operator from pennylane.pytrees import register_pytree from pennylane.queuing import AnnotatedQueue, process_queue from pennylane.typing import TensorLike @@ -270,11 +270,11 @@ def operations(self) -> list[Operator]: return self._ops @property - def observables(self) -> list[Union[MeasurementProcess, Observable]]: + def observables(self) -> list[Union[MeasurementProcess, Operator]]: """Returns the observables on the quantum script. Returns: - list[.MeasurementProcess, .Observable]]: list of observables + list[.MeasurementProcess, .Operator]]: list of observables **Example** @@ -491,7 +491,7 @@ def obs_sharing_wires(self) -> list[Operator]: i.e., that do not have their own unique set of wires. Returns: - list[~.Observable]: list of observables with shared wires. + list[~.Operator]: list of observables with shared wires. """ if self._obs_sharing_wires is None: @@ -516,7 +516,7 @@ def _update_observables(self): identifying any observables that share wires. Sets: - _obs_sharing_wires (list[~.Observable]): Observables that share wires with + _obs_sharing_wires (list[~.Operator]): Observables that share wires with any other observable _obs_sharing_wires_id (list[int]): Indices of the measurements that contain the observables in _obs_sharing_wires diff --git a/pennylane/templates/embeddings/angle.py b/pennylane/templates/embeddings/angle.py index 6cb7eafd6d5..8f5093a5f41 100644 --- a/pennylane/templates/embeddings/angle.py +++ b/pennylane/templates/embeddings/angle.py @@ -16,7 +16,7 @@ """ # pylint: disable-msg=too-many-branches,too-many-arguments,protected-access import pennylane as qml -from pennylane.operation import AnyWires, Operation +from pennylane.operation import Operation from pennylane.ops import RX, RY, RZ ROT = {"X": RX, "Y": RY, "Z": RZ} @@ -72,7 +72,6 @@ def circuit(feature_vector): """ - num_wires = AnyWires grad_method = None def _flatten(self): diff --git a/pennylane/templates/embeddings/displacement.py b/pennylane/templates/embeddings/displacement.py index 2e99414800c..68a20fac998 100644 --- a/pennylane/templates/embeddings/displacement.py +++ b/pennylane/templates/embeddings/displacement.py @@ -16,7 +16,7 @@ """ # pylint: disable-msg=too-many-branches,too-many-arguments,protected-access import pennylane as qml -from pennylane.operation import AnyWires, Operation +from pennylane.operation import Operation class DisplacementEmbedding(Operation): @@ -97,7 +97,6 @@ def circuit(feature_vector): 2: ─╰DisplacementEmbedding(M0)──────────┤ """ - num_wires = AnyWires grad_method = None @classmethod diff --git a/pennylane/templates/embeddings/iqp.py b/pennylane/templates/embeddings/iqp.py index 1a9eb46a6b7..e7a0cb07e6f 100644 --- a/pennylane/templates/embeddings/iqp.py +++ b/pennylane/templates/embeddings/iqp.py @@ -19,7 +19,7 @@ from itertools import combinations import pennylane as qml -from pennylane.operation import AnyWires, Operation +from pennylane.operation import Operation from pennylane.wires import Wires @@ -165,7 +165,6 @@ def circuit(features, pattern): """ - num_wires = AnyWires grad_method = None def __init__(self, features, wires, n_repeats=1, pattern=None, id=None): diff --git a/pennylane/templates/embeddings/qaoaembedding.py b/pennylane/templates/embeddings/qaoaembedding.py index 466f6a71de3..c2c0d1e3f53 100644 --- a/pennylane/templates/embeddings/qaoaembedding.py +++ b/pennylane/templates/embeddings/qaoaembedding.py @@ -16,7 +16,7 @@ """ # pylint: disable-msg=too-many-branches,too-many-arguments,protected-access, consider-using-enumerate import pennylane as qml -from pennylane.operation import AnyWires, Operation +from pennylane.operation import Operation class QAOAEmbedding(Operation): @@ -157,7 +157,6 @@ def circuit(weights, f=None): """ - num_wires = AnyWires grad_method = None def __init__(self, features, weights, wires, local_field="Y", id=None): diff --git a/pennylane/templates/embeddings/squeezing.py b/pennylane/templates/embeddings/squeezing.py index afacc2e1284..4fa9fb6b85b 100644 --- a/pennylane/templates/embeddings/squeezing.py +++ b/pennylane/templates/embeddings/squeezing.py @@ -16,7 +16,7 @@ """ # pylint: disable-msg=too-many-branches,too-many-arguments,protected-access import pennylane as qml -from pennylane.operation import AnyWires, Operation +from pennylane.operation import Operation class SqueezingEmbedding(Operation): @@ -99,7 +99,6 @@ def circuit(feature_vector): """ - num_wires = AnyWires grad_method = None @classmethod diff --git a/pennylane/templates/layers/basic_entangler.py b/pennylane/templates/layers/basic_entangler.py index 8b2a13f0cd1..c1d943cbc9b 100644 --- a/pennylane/templates/layers/basic_entangler.py +++ b/pennylane/templates/layers/basic_entangler.py @@ -16,7 +16,7 @@ """ # pylint: disable=consider-using-enumerate,too-many-arguments import pennylane as qml -from pennylane.operation import AnyWires, Operation +from pennylane.operation import Operation class BasicEntanglerLayers(Operation): @@ -122,7 +122,6 @@ def circuit(weights): ``ValueError: Wrong number of parameters``. """ - num_wires = AnyWires grad_method = None def __init__(self, weights, wires=None, rotation=None, id=None): diff --git a/pennylane/templates/layers/cv_neural_net.py b/pennylane/templates/layers/cv_neural_net.py index f5b9517d314..54b33e39562 100644 --- a/pennylane/templates/layers/cv_neural_net.py +++ b/pennylane/templates/layers/cv_neural_net.py @@ -16,7 +16,7 @@ """ # pylint: disable-msg=too-many-branches,too-many-arguments,protected-access,arguments-differ import pennylane as qml -from pennylane.operation import AnyWires, Operation +from pennylane.operation import Operation class CVNeuralNetLayers(Operation): @@ -81,7 +81,6 @@ def circuit(): """ - num_wires = AnyWires grad_method = None def __init__( diff --git a/pennylane/templates/layers/gate_fabric.py b/pennylane/templates/layers/gate_fabric.py index 5f3a412477b..272fea7a65f 100644 --- a/pennylane/templates/layers/gate_fabric.py +++ b/pennylane/templates/layers/gate_fabric.py @@ -18,7 +18,7 @@ import numpy as np import pennylane as qml -from pennylane.operation import AnyWires, Operation +from pennylane.operation import Operation class GateFabric(Operation): @@ -172,7 +172,6 @@ def ansatz(weights): """ - num_wires = AnyWires grad_method = None def __init__(self, weights, wires, init_state, include_pi=False, id=None): diff --git a/pennylane/templates/layers/particle_conserving_u1.py b/pennylane/templates/layers/particle_conserving_u1.py index e140e685aa9..e8eb57f684d 100644 --- a/pennylane/templates/layers/particle_conserving_u1.py +++ b/pennylane/templates/layers/particle_conserving_u1.py @@ -18,7 +18,7 @@ import numpy as np import pennylane as qml -from pennylane.operation import AnyWires, Operation +from pennylane.operation import Operation def decompose_ua(phi, wires=None): @@ -243,7 +243,6 @@ def cost_fn(params): params = np.random.random(size=shape) """ - num_wires = AnyWires grad_method = None def __init__(self, weights, wires, init_state=None, id=None): diff --git a/pennylane/templates/layers/particle_conserving_u2.py b/pennylane/templates/layers/particle_conserving_u2.py index f24df4ada7e..4f607080a9a 100644 --- a/pennylane/templates/layers/particle_conserving_u2.py +++ b/pennylane/templates/layers/particle_conserving_u2.py @@ -16,7 +16,7 @@ """ # pylint: disable-msg=too-many-branches,too-many-arguments,protected-access import pennylane as qml -from pennylane.operation import AnyWires, Operation +from pennylane.operation import Operation def u2_ex_gate(phi, wires=None): @@ -151,7 +151,6 @@ def cost_fn(params): params = np.random.random(size=shape) """ - num_wires = AnyWires grad_method = None def __init__(self, weights, wires, init_state=None, id=None): diff --git a/pennylane/templates/layers/random.py b/pennylane/templates/layers/random.py index c36e649ae31..4f7241ecdf9 100644 --- a/pennylane/templates/layers/random.py +++ b/pennylane/templates/layers/random.py @@ -19,7 +19,7 @@ import numpy as np import pennylane as qml -from pennylane.operation import AnyWires, Operation +from pennylane.operation import Operation class RandomLayers(Operation): @@ -167,7 +167,6 @@ def circuit_rnd(weights): weights = np.random.random(size=shape) """ - num_wires = AnyWires grad_method = None def __init__( diff --git a/pennylane/templates/layers/simplified_two_design.py b/pennylane/templates/layers/simplified_two_design.py index 3d32a1d134f..02d12b54111 100644 --- a/pennylane/templates/layers/simplified_two_design.py +++ b/pennylane/templates/layers/simplified_two_design.py @@ -16,7 +16,7 @@ """ # pylint: disable-msg=too-many-branches,too-many-arguments,protected-access import pennylane as qml -from pennylane.operation import AnyWires, Operation +from pennylane.operation import Operation class SimplifiedTwoDesign(Operation): @@ -99,7 +99,6 @@ def circuit(init_weights, weights): """ - num_wires = AnyWires grad_method = None def __init__(self, initial_layer_weights, weights, wires, id=None): diff --git a/pennylane/templates/layers/strongly_entangling.py b/pennylane/templates/layers/strongly_entangling.py index 1ca30848cb2..38e2158d870 100644 --- a/pennylane/templates/layers/strongly_entangling.py +++ b/pennylane/templates/layers/strongly_entangling.py @@ -16,7 +16,7 @@ """ # pylint: disable-msg=too-many-branches,too-many-arguments,protected-access import pennylane as qml -from pennylane.operation import AnyWires, Operation +from pennylane.operation import Operation class StronglyEntanglingLayers(Operation): @@ -130,7 +130,6 @@ def circuit(parameters): """ - num_wires = AnyWires grad_method = None def __init__(self, weights, wires, ranges=None, imprimitive=None, id=None): diff --git a/pennylane/templates/state_preparations/arbitrary_state_preparation.py b/pennylane/templates/state_preparations/arbitrary_state_preparation.py index 16d47a52ec6..3b8dbddb227 100644 --- a/pennylane/templates/state_preparations/arbitrary_state_preparation.py +++ b/pennylane/templates/state_preparations/arbitrary_state_preparation.py @@ -18,7 +18,7 @@ import functools import pennylane as qml -from pennylane.operation import AnyWires, Operation +from pennylane.operation import Operation @functools.lru_cache() @@ -80,7 +80,6 @@ def vqe(weights): """ - num_wires = AnyWires grad_method = None def __init__(self, weights, wires, id=None): diff --git a/pennylane/templates/state_preparations/basis_qutrit.py b/pennylane/templates/state_preparations/basis_qutrit.py index f0a394ba5d0..56f95a1e253 100644 --- a/pennylane/templates/state_preparations/basis_qutrit.py +++ b/pennylane/templates/state_preparations/basis_qutrit.py @@ -18,7 +18,7 @@ import numpy as np import pennylane as qml -from pennylane.operation import AnyWires, Operation +from pennylane.operation import Operation class QutritBasisStatePreparation(Operation): @@ -54,7 +54,6 @@ def circuit(basis_state, obs): """ num_params = 1 - num_wires = AnyWires grad_method = None def __init__(self, basis_state, wires, id=None): diff --git a/pennylane/templates/state_preparations/mottonen.py b/pennylane/templates/state_preparations/mottonen.py index b86092c93af..9571632cb46 100644 --- a/pennylane/templates/state_preparations/mottonen.py +++ b/pennylane/templates/state_preparations/mottonen.py @@ -14,83 +14,110 @@ r""" Contains the MottonenStatePreparation template. """ +from typing import Optional + import numpy as np import pennylane as qml -from pennylane.operation import AnyWires, Operation +from pennylane.operation import Operation +from pennylane.typing import TensorLike -# pylint: disable=len-as-condition,arguments-out-of-order,consider-using-enumerate def gray_code(rank): - """Generates the Gray code of given rank. + """Generates the + `Gray code `__ + of given rank, as numeric output. Args: rank (int): rank of the Gray code (i.e. number of bits) - """ - - def gray_code_recurse(g, rank): - k = len(g) - if rank <= 0: - return - - for i in range(k - 1, -1, -1): - char = "1" + g[i] - g.append(char) - for i in range(k - 1, -1, -1): - g[i] = "0" + g[i] - - gray_code_recurse(g, rank - 1) - - g = ["0", "1"] - gray_code_recurse(g, rank - 1) - - return g - - -def _matrix_M_entry(row, col): - """Returns one entry for the matrix that maps alpha to theta. - - See Eq. (3) in `Möttönen et al. (2004) `_. - - Args: - row (int): one-based row number - col (int): one-based column number Returns: - (float): transformation matrix entry at given row and column + np.ndarray[int]: Array of ``2**rank`` integers that make up the Gray code. """ - # (col >> 1) ^ col is the Gray code of col - b_and_g = row & ((col >> 1) ^ col) - sum_of_ones = 0 - while b_and_g > 0: - if b_and_g & 0b1: - sum_of_ones += 1 + g = np.array([0, 1]) + for i in range(1, rank): + g = np.concatenate([g, g[::-1] + 2**i]) + return g - b_and_g = b_and_g >> 1 - return (-1) ** sum_of_ones +_walsh_hadamard_matrix = np.array([[1, 1], [1, -1]]) / 2 +_cnot_matrix = np.array([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 0, 1], [0, 0, 1, 0]]).reshape((2,) * 4) -def compute_theta(alpha): - """Maps the angles alpha of the multi-controlled rotations decomposition of a uniformly controlled rotation - to the rotation angles used in the Gray code implementation. +def compute_theta(alpha: TensorLike, num_qubits: Optional[int] = None): + r"""Maps the input angles ``alpha`` of the multi-controlled rotations decomposition of a + uniformly controlled rotation to the rotation angles used in the + `Gray code `__ implementation. + This function uses the fact that the transformation given by Eq. (3) in + `Möttönen et al. (2004) `_ is equal to a Walsh-Hadamard + transform followed by some permutations, which can be expressed as a ladder of CNOT gates + applied to the angles, when interpreting them as a quantum state. Args: - alpha (tensor_like): alpha parameters + alpha (tensor_like): The array or tensor to be transformed. Must have a length that + is a power of two. + num_qubits (int): Number of qubits. If not given, it will be computed from ``alpha``. + If given, it should match the trailing dimension of ``alpha``. Returns: - (tensor_like): rotation angles theta + tensor_like: The transformed tensor with the same shape as the input ``alpha``. + + Due to the execution of the transform as a sequence of tensor multiplications + with shapes ``(2, 2), (2, 2,... 2)->(2, 2,... 2)``, the theoretical scaling of this + method is the same as the one for the + `Fast Walsh-Hadamard transform `__: + On :math:`n` qubits, there are :math:`n` calls to ``tensordot``, each multiplying a ``(2, 2)`` + matrix to a ``(2,)*num_qubits`` vector, with a single axis being contracted. This means + that there are :math:`n` operations with a floating point operation count of + ``4 * 2**(num_qubits-1)``, where ``4`` is the cost of a single ``(2, 2) @ (2,)`` contraction + and ``2**(n-1)`` is the number of copies due to the non-contracted :math:`n-1` axes. + Due to the large internal speedups of compiled matrix multiplication and compatibility + with autodifferentiation frameworks, the approach taken here is favourable over a manual + realization of the FWHT unless memory limitations restrict the creation of intermediate + arrays, which would make in-place techniques favourable. + + Similarly, the permutation can be applied by contracting the angles with the reshaped CNOT + matrix. """ - ln = alpha.shape[-1] - - M_trans = np.zeros(shape=(ln, ln)) - for i in range(len(M_trans)): - for j in range(len(M_trans[0])): - M_trans[i, j] = _matrix_M_entry(j, i) - - theta = qml.math.transpose(qml.math.dot(M_trans, qml.math.transpose(alpha))) - - return theta / ln + orig_shape = qml.math.shape(alpha) + num_qubits = num_qubits or int(qml.math.log2(orig_shape[-1])) + if num_qubits == 0: + # No processing occurs for num_qubits=0 + return alpha + # Reshape the array so that we may apply the Hadamard transform to each axis individually + if broadcasted := len(orig_shape) > 1: + new_shape = (orig_shape[0],) + (2,) * num_qubits + else: + new_shape = (2,) * num_qubits + alpha = qml.math.reshape(alpha, new_shape) + # Apply Hadamard transform to each axis, shifted by one for broadcasting + for i in range(broadcasted, num_qubits + broadcasted): + alpha = qml.math.tensordot(_walsh_hadamard_matrix, alpha, axes=[[1], [i]]) + # The axes are now in the ordering [qubit n-1, qubit n-2, ..., qubit 1, qubit 0, batch] + if num_qubits > 1: + # If there is more than one qubit, we need to reorder the angles, according to applying + # the CNOT ladder [CNOT([i, i+1]) for i in range(num_qubits-1)] + # The first CNOT thus targets the zeroth and first qubit, axes n-1 and n-2 (see above) + alpha = qml.math.tensordot( + _cnot_matrix, alpha, axes=[[2, 3], [num_qubits - 1, num_qubits - 2]] + ) + # The axes are now ordered as [qubit 0, qubit 1, qubit n-1, qubit n-2, ..., qubit 2, batch] + # Following CNOTs use the same axes: the next control qubit (previous target qubit) always + # is in position ``1`` and the next target qubit always is the last qubit axis + # (``num_qubits-1``). For example, the first loop iteration moves the axes into positions + # [qubit 1, qubit 2, qubit 0, qubit n-1, qubit n-2, ... ,qubit 3, batch] + # and the iteration after that moves them to + # [qubit 2, qubit 3, qubit 1, qubit 0, qubit n-1, qubit n-2, ... ,qubit 4, batch] + for i in range(broadcasted + 1, num_qubits + broadcasted - 1): + alpha = qml.math.tensordot(_cnot_matrix, alpha, axes=[[2, 3], [1, num_qubits - 1]]) + + # In the end, we exchange the first two axes because we have the axes ordering + # [qubit n-2, qubit n-1, qubit n-3, qubit n-4, ... qubit 1, qubit 0, batch] + alpha = qml.math.moveaxis(alpha, 0, 1) + # Finally, the axis ordering has to be flipped entirely, moving the batch to the front + # and the qubits into the right ordering, [batch, qubit 0, qubit 1, ..., qubit n-1] + # For num_qubits=1 we just exchange the single qubit axis and the batching axis + return qml.math.reshape(qml.math.transpose(alpha), orig_shape) def _apply_uniform_rotation_dagger(gate, alpha, control_wires, target_wire): @@ -118,10 +145,10 @@ def _apply_uniform_rotation_dagger(gate, alpha, control_wires, target_wire): list[.Operator]: sequence of operators defined by this function """ op_list = [] - theta = compute_theta(alpha) - gray_code_rank = len(control_wires) + theta = compute_theta(alpha, num_qubits=gray_code_rank) + if gray_code_rank == 0: if ( qml.math.is_abstract(theta) @@ -132,19 +159,19 @@ def _apply_uniform_rotation_dagger(gate, alpha, control_wires, target_wire): return op_list code = gray_code(gray_code_rank) - num_selections = len(code) - - control_indices = [ - int(np.log2(int(code[i], 2) ^ int(code[(i + 1) % num_selections], 2))) - for i in range(num_selections) - ] - + control_indices = np.log2(code ^ np.roll(code, -1)).astype(int) + + # For abstract or differentiated theta we will never skip a rotation. Likewise if there is at + # least one non-zero angle (per batch if batched) for all rotations. + skip_none = qml.math.is_abstract(theta) or qml.math.requires_grad(theta) + if not skip_none: + nonzero = ( + (theta != 0.0) if qml.math.ndim(theta) == 1 else qml.math.any(theta != 0.0, axis=0) + ) + skip_none = qml.math.all(nonzero) for i, control_index in enumerate(control_indices): - if ( - qml.math.is_abstract(theta) - or qml.math.requires_grad(theta) - or qml.math.all(theta[..., i] != 0.0) - ): + # If we do not _never_ skip, we might skip _some_ rotation + if skip_none or qml.math.all(theta[..., i] != 0.0): op_list.append(gate(theta[..., i], wires=[target_wire])) op_list.append(qml.CNOT(wires=[control_wires[control_index], target_wire])) return op_list @@ -286,7 +313,6 @@ def circuit(state): """ - num_wires = AnyWires grad_method = None ndim_params = (1,) diff --git a/pennylane/templates/state_preparations/superposition.py b/pennylane/templates/state_preparations/superposition.py index 2d377e3c1f7..6a59f82b0b0 100644 --- a/pennylane/templates/state_preparations/superposition.py +++ b/pennylane/templates/state_preparations/superposition.py @@ -16,7 +16,7 @@ """ import pennylane as qml -from pennylane.operation import AnyWires, Operation +from pennylane.operation import Operation def _assign_states(basis_list): @@ -205,7 +205,6 @@ def circuit(): with the number of qubits. """ - num_wires = AnyWires grad_method = None ndim_params = (1,) diff --git a/pennylane/templates/subroutines/all_singles_doubles.py b/pennylane/templates/subroutines/all_singles_doubles.py index 7329753fcaa..035351a1edd 100644 --- a/pennylane/templates/subroutines/all_singles_doubles.py +++ b/pennylane/templates/subroutines/all_singles_doubles.py @@ -20,7 +20,7 @@ import numpy as np import pennylane as qml -from pennylane.operation import AnyWires, Operation +from pennylane.operation import Operation from pennylane.ops import BasisState from pennylane.wires import Wires @@ -116,7 +116,6 @@ def circuit(weights, hf_state, singles, doubles): circuit(params, hf_state, singles=singles, doubles=doubles) """ - num_wires = AnyWires grad_method = None def __init__(self, weights, wires, hf_state, singles=None, doubles=None, id=None): diff --git a/pennylane/templates/subroutines/approx_time_evolution.py b/pennylane/templates/subroutines/approx_time_evolution.py index 38819490e70..ac920fe49f6 100644 --- a/pennylane/templates/subroutines/approx_time_evolution.py +++ b/pennylane/templates/subroutines/approx_time_evolution.py @@ -18,7 +18,7 @@ import copy import pennylane as qml -from pennylane.operation import AnyWires, Operation +from pennylane.operation import Operation from pennylane.ops import PauliRot from pennylane.wires import Wires @@ -116,7 +116,6 @@ def circuit(time): tensor([-0.41614684 -0.41614684], requires_grad=True) """ - num_wires = AnyWires grad_method = None def _flatten(self): diff --git a/pennylane/templates/subroutines/arbitrary_unitary.py b/pennylane/templates/subroutines/arbitrary_unitary.py index 3527bc0145b..bb081e15a8c 100644 --- a/pennylane/templates/subroutines/arbitrary_unitary.py +++ b/pennylane/templates/subroutines/arbitrary_unitary.py @@ -15,7 +15,7 @@ Contains the ArbitraryUnitary template. """ import pennylane as qml -from pennylane.operation import AnyWires, Operation +from pennylane.operation import Operation from pennylane.ops import PauliRot _PAULIS = ["I", "X", "Y", "Z"] @@ -92,7 +92,6 @@ def arbitrary_nearest_neighbour_interaction(weights, wires): wires (Iterable): wires that the template acts on """ - num_wires = AnyWires grad_method = None num_params = 1 ndim_params = (1,) diff --git a/pennylane/templates/subroutines/basis_rotation.py b/pennylane/templates/subroutines/basis_rotation.py index f82399aaffc..ec6f165b780 100644 --- a/pennylane/templates/subroutines/basis_rotation.py +++ b/pennylane/templates/subroutines/basis_rotation.py @@ -16,7 +16,7 @@ """ import pennylane as qml -from pennylane.operation import AnyWires, Operation +from pennylane.operation import Operation from pennylane.qchem.givens_decomposition import givens_decomposition @@ -95,7 +95,6 @@ class BasisRotation(Operation): """ - num_wires = AnyWires grad_method = None @classmethod diff --git a/pennylane/templates/subroutines/commuting_evolution.py b/pennylane/templates/subroutines/commuting_evolution.py index ea47db426fd..0a5fcf4909c 100644 --- a/pennylane/templates/subroutines/commuting_evolution.py +++ b/pennylane/templates/subroutines/commuting_evolution.py @@ -18,7 +18,8 @@ import copy import pennylane as qml -from pennylane.operation import AnyWires, Operation +from pennylane import math +from pennylane.operation import Operation from pennylane.wires import Wires @@ -106,7 +107,6 @@ def circuit(time): 0.6536436208636115 """ - num_wires = AnyWires grad_method = None def _flatten(self): @@ -131,7 +131,7 @@ def __init__(self, hamiltonian, time, frequencies=None, shifts=None, id=None): f"hamiltonian must be a linear combination of pauli words. Got {hamiltonian}" ) - trainable_hamiltonian = qml.operation.is_trainable(hamiltonian) + trainable_hamiltonian = any(math.requires_grad(d) for d in hamiltonian.data) if frequencies is not None and not trainable_hamiltonian: c, s = generate_shift_rule(frequencies, shifts).T recipe = qml.math.stack([c, qml.math.ones_like(c), s]).T diff --git a/pennylane/templates/subroutines/fable.py b/pennylane/templates/subroutines/fable.py index d9637676738..490b1d1c93b 100644 --- a/pennylane/templates/subroutines/fable.py +++ b/pennylane/templates/subroutines/fable.py @@ -19,7 +19,7 @@ import numpy as np import pennylane as qml -from pennylane.operation import AnyWires, Operation +from pennylane.operation import Operation from pennylane.templates.state_preparations.mottonen import compute_theta, gray_code from pennylane.wires import Wires @@ -73,9 +73,6 @@ def example_circuit(): of the input matrix are within :math:`[-1, 1]`. Apply a subnormalization factor if needed. """ - num_wires = AnyWires - """int: Number of wires that the operator acts on.""" - num_params = 1 """int: Number of trainable parameters that the operator depends on.""" @@ -148,13 +145,9 @@ def compute_decomposition(input_matrix, wires, tol=0): # pylint:disable=argumen wires_i = wires[1 : 1 + len(wires) // 2][::-1] wires_j = wires[1 + len(wires) // 2 : len(wires)][::-1] - code = gray_code((2 * qml.math.log2(len(input_matrix)))) - n_selections = len(code) + code = gray_code(int((2 * qml.math.log2(len(input_matrix))))) + control_wires = np.log2(code ^ np.roll(code, -1)).astype(int) - control_wires = [ - int(qml.math.log2(int(code[i], 2) ^ int(code[(i + 1) % n_selections], 2))) - for i in range(n_selections) - ] wire_map = dict(enumerate(wires_j + wires_i)) for w in wires_i: diff --git a/pennylane/templates/subroutines/fermionic_double_excitation.py b/pennylane/templates/subroutines/fermionic_double_excitation.py index 797189dbe54..5e1a6ba573e 100644 --- a/pennylane/templates/subroutines/fermionic_double_excitation.py +++ b/pennylane/templates/subroutines/fermionic_double_excitation.py @@ -20,7 +20,7 @@ import numpy as np import pennylane as qml -from pennylane.operation import AnyWires, Operation +from pennylane.operation import Operation from pennylane.ops import CNOT, RX, RZ, Hadamard from pennylane.wires import Wires @@ -497,7 +497,6 @@ def circuit(weight, wires1=None, wires2=None): """ - num_wires = AnyWires grad_method = "A" parameter_frequencies = [(0.5, 1.0)] diff --git a/pennylane/templates/subroutines/fermionic_single_excitation.py b/pennylane/templates/subroutines/fermionic_single_excitation.py index b915d1226cc..83e6917b28c 100644 --- a/pennylane/templates/subroutines/fermionic_single_excitation.py +++ b/pennylane/templates/subroutines/fermionic_single_excitation.py @@ -18,7 +18,7 @@ import numpy as np import pennylane as qml -from pennylane.operation import AnyWires, Operation +from pennylane.operation import Operation from pennylane.ops import CNOT, RX, RZ, Hadamard @@ -113,7 +113,6 @@ def circuit(weight, wires=None): """ - num_wires = AnyWires grad_method = "A" parameter_frequencies = [(0.5, 1.0)] diff --git a/pennylane/templates/subroutines/flip_sign.py b/pennylane/templates/subroutines/flip_sign.py index fa2a3e863ff..d99dd4259b3 100644 --- a/pennylane/templates/subroutines/flip_sign.py +++ b/pennylane/templates/subroutines/flip_sign.py @@ -16,7 +16,7 @@ """ import pennylane as qml -from pennylane.operation import AnyWires, Operation +from pennylane.operation import Operation class FlipSign(Operation): @@ -64,8 +64,6 @@ def circuit(): """ - num_wires = AnyWires - def _flatten(self): hyperparameters = (("n", tuple(self.hyperparameters["arr_bin"])),) return tuple(), (self.wires, hyperparameters) diff --git a/pennylane/templates/subroutines/grover.py b/pennylane/templates/subroutines/grover.py index 9beb67fd6b2..27b1a7ee13d 100644 --- a/pennylane/templates/subroutines/grover.py +++ b/pennylane/templates/subroutines/grover.py @@ -17,7 +17,7 @@ import numpy as np import pennylane as qml -from pennylane.operation import AnyWires, Operation +from pennylane.operation import Operation from pennylane.ops import GlobalPhase, Hadamard, MultiControlledX, PauliZ from pennylane.wires import Wires, WiresLike @@ -100,7 +100,6 @@ def GroverSearch(num_iterations=1): """ - num_wires = AnyWires grad_method = None def __repr__(self): diff --git a/pennylane/templates/subroutines/hilbert_schmidt.py b/pennylane/templates/subroutines/hilbert_schmidt.py index fab182447db..30abb12f05d 100644 --- a/pennylane/templates/subroutines/hilbert_schmidt.py +++ b/pennylane/templates/subroutines/hilbert_schmidt.py @@ -16,7 +16,7 @@ """ # pylint: disable-msg=too-many-arguments import pennylane as qml -from pennylane.operation import AnyWires, Operation +from pennylane.operation import Operation class HilbertSchmidt(Operation): @@ -97,7 +97,6 @@ def cost_hst(parameters, v_function, v_wires, u_tape): """ - num_wires = AnyWires grad_method = None def _flatten(self): diff --git a/pennylane/templates/subroutines/interferometer.py b/pennylane/templates/subroutines/interferometer.py index 1b58774ecdc..2fd3f7a3fe7 100644 --- a/pennylane/templates/subroutines/interferometer.py +++ b/pennylane/templates/subroutines/interferometer.py @@ -17,7 +17,7 @@ from itertools import product import pennylane as qml -from pennylane.operation import AnyWires, CVOperation +from pennylane.operation import CVOperation # pylint: disable-msg=too-many-branches,too-many-arguments,protected-access from pennylane.ops import Beamsplitter, Rotation @@ -159,7 +159,6 @@ def circuit(params): ──╰BS(0.20,0.00)──R(0.62)─┤ """ - num_wires = AnyWires grad_method = None def __init__( diff --git a/pennylane/templates/subroutines/kupccgsd.py b/pennylane/templates/subroutines/kupccgsd.py index 3e8da9b1ffb..0095ac0a9cd 100644 --- a/pennylane/templates/subroutines/kupccgsd.py +++ b/pennylane/templates/subroutines/kupccgsd.py @@ -20,7 +20,7 @@ import numpy as np import pennylane as qml -from pennylane.operation import AnyWires, Operation +from pennylane.operation import Operation from pennylane.wires import Wires @@ -204,7 +204,6 @@ def ansatz(weights): """ - num_wires = AnyWires grad_method = None def _flatten(self): diff --git a/pennylane/templates/subroutines/permute.py b/pennylane/templates/subroutines/permute.py index 7e0327f3d8c..65f707577d3 100644 --- a/pennylane/templates/subroutines/permute.py +++ b/pennylane/templates/subroutines/permute.py @@ -16,7 +16,7 @@ """ import copy -from pennylane.operation import AnyWires, Operation +from pennylane.operation import Operation from pennylane.ops import SWAP from pennylane.wires import Wires @@ -143,7 +143,6 @@ def circuit(): def __repr__(self): return f"Permute({self.hyperparameters['permutation']}, wires={self.wires.tolist()})" - num_wires = AnyWires grad_method = None def __init__(self, permutation, wires, id=None): diff --git a/pennylane/templates/subroutines/qft.py b/pennylane/templates/subroutines/qft.py index db74de273a0..2020ad2e0ed 100644 --- a/pennylane/templates/subroutines/qft.py +++ b/pennylane/templates/subroutines/qft.py @@ -22,7 +22,7 @@ import pennylane as qml from pennylane.decomposition import add_decomps, register_resources -from pennylane.operation import AnyWires, Operation +from pennylane.operation import Operation from pennylane.wires import Wires, WiresLike @@ -130,7 +130,6 @@ def scFT_add(m, k, n_wires): [1 0 1 0] """ - num_wires = AnyWires grad_method = None resource_keys = { "num_wires", diff --git a/pennylane/templates/subroutines/qmc.py b/pennylane/templates/subroutines/qmc.py index 50a764bc0d1..5ad97e1fcd6 100644 --- a/pennylane/templates/subroutines/qmc.py +++ b/pennylane/templates/subroutines/qmc.py @@ -20,7 +20,7 @@ import numpy as np import pennylane as qml -from pennylane.operation import AnyWires, Operation +from pennylane.operation import Operation from pennylane.ops import QubitUnitary from pennylane.wires import Wires @@ -341,7 +341,6 @@ def circuit(): 0.4327096457464369 """ - num_wires = AnyWires grad_method = None @classmethod diff --git a/pennylane/templates/subroutines/qpe.py b/pennylane/templates/subroutines/qpe.py index 0e2e0af44ba..20114b76a49 100644 --- a/pennylane/templates/subroutines/qpe.py +++ b/pennylane/templates/subroutines/qpe.py @@ -18,7 +18,7 @@ import copy import pennylane as qml -from pennylane.operation import AnyWires, Operator +from pennylane.operation import Operator from pennylane.queuing import QueuingManager from pennylane.resource.error import ErrorOperation, SpectralNormError from pennylane.wires import Wires @@ -141,7 +141,6 @@ def circuit(): """ - num_wires = AnyWires grad_method = None # pylint: disable=no-member diff --git a/pennylane/templates/subroutines/qsvt.py b/pennylane/templates/subroutines/qsvt.py index 49625cb13af..3bf89938c7b 100644 --- a/pennylane/templates/subroutines/qsvt.py +++ b/pennylane/templates/subroutines/qsvt.py @@ -16,20 +16,95 @@ """ # pylint: disable=too-many-arguments import copy -from typing import Literal +from typing import Literal, Sequence, Union import numpy as np from numpy.polynomial import Polynomial, chebyshev import pennylane as qml -from pennylane.operation import AnyWires, Operation +from pennylane.operation import Operation, Operator from pennylane.ops.op_math import adjoint from pennylane.queuing import QueuingManager +from pennylane.typing import TensorLike from pennylane.wires import Wires -# pylint: disable=too-many-branches, unused-argument -def qsvt(A, poly, encoding_wires=None, block_encoding=None, **kwargs): +def _pauli_rep_process(A, poly, encoding_wires, block_encoding): + if block_encoding not in ["prepselprep", "qubitization", None]: + raise ValueError( + f"block_encoding = {block_encoding} not supported for A of type {type(A)}. " + "When A is a Hamiltonian or has a Pauli decomposition, block_encoding should " + "take the value 'prepselprep' or 'qubitization'. Otherwise, please provide the " + "matrix of the Hamiltonian as input. For more details, see the 'qml.matrix' function." + ) + + if any(wire in qml.wires.Wires(encoding_wires) for wire in A.wires): + raise ValueError( + f"Control wires in '{block_encoding}' should be different from the hamiltonian wires" + ) + + encoding = ( + qml.Qubitization(A, control=encoding_wires) + if block_encoding == "qubitization" + else qml.PrepSelPrep(A, control=encoding_wires) + ) + + angles = qml.poly_to_angles(poly, "QSVT") + + projectors = [ + qml.PCPhase(angle, dim=2 ** len(A.wires), wires=encoding_wires + A.wires) + for angle in angles + ] + return encoding, projectors + + +def _tensorlike_process(A, poly, encoding_wires, block_encoding): + if block_encoding not in ["embedding", "fable", None]: + raise ValueError( + f"block_encoding = {block_encoding} not supported for A of type {type(A)}." + "When A is a matrix block_encoding should take the value 'embedding' or 'fable'. " + "Otherwise, please provide an input with a Pauli decomposition. For more details, " + "see the 'qml.pauli_decompose' function." + ) + + A = qml.math.atleast_2d(A) + max_dimension = 1 if len(qml.math.array(A).shape) == 0 else max(A.shape) + + if block_encoding == "fable": + if qml.math.linalg.norm(max_dimension * qml.math.ravel(A), np.inf) > 1: + raise ValueError( + "The subnormalization factor should be lower than 1. Ensure that the product" + " of the maximum dimension of A and its square norm is less than 1." + ) + + # FABLE encodes A / 2^n, need to rescale to obtain desired block-encoding + + fable_norm = int(np.ceil(np.log2(max_dimension))) + encoding = qml.FABLE(2**fable_norm * A, wires=encoding_wires) + angles = qml.poly_to_angles(poly, "QSVT") + + projectors = [qml.PCPhase(angle, dim=len(A), wires=encoding_wires) for angle in angles] + + else: + c, r = qml.math.shape(A) + + angles = qml.poly_to_angles(poly, "QSVT") + projectors = [] + for idx, phi in enumerate(angles): + dim = c if idx % 2 else r + projectors.append(qml.PCPhase(phi, dim=dim, wires=encoding_wires)) + + encoding = qml.BlockEncode(A, wires=encoding_wires) + + return encoding, projectors + + +def qsvt( + A: Union[Operator, TensorLike], + poly: TensorLike, + encoding_wires: Sequence, + block_encoding: Literal[None, "prepselprep", "qubitization", "embedding", "fable"] = None, +): r""" Implements the Quantum Singular Value Transformation (QSVT) for a matrix or Hamiltonian ``A``, using a polynomial defined by ``poly`` and a block encoding specified by ``block_encoding``. @@ -193,67 +268,11 @@ def circuit(): """ - projectors = [] - # If the input A is a Hamiltonian if hasattr(A, "pauli_rep"): - - if block_encoding not in ["prepselprep", "qubitization", None]: - raise ValueError( - "block_encoding = {block_encoding} not supported for A of type {type(A)}. When A is a Hamiltonian or has a Pauli decomposition, block_encoding should take the value 'prepselprep' or 'qubitization'. Otherwise, please provide the matrix of the Hamiltonian as input. For more details, see the 'qml.matrix' function." - ) - - if any(wire in qml.wires.Wires(encoding_wires) for wire in A.wires): - raise ValueError( - f"Control wires in '{block_encoding}' should be different from the hamiltonian wires" - ) - - encoding = ( - qml.Qubitization(A, control=encoding_wires) - if block_encoding == "qubitization" - else qml.PrepSelPrep(A, control=encoding_wires) - ) - - angles = qml.poly_to_angles(poly, "QSVT") - - projectors = [ - qml.PCPhase(angle, dim=2 ** len(A.wires), wires=encoding_wires + A.wires) - for angle in angles - ] - + encoding, projectors = _pauli_rep_process(A, poly, encoding_wires, block_encoding) else: - - if block_encoding not in ["embedding", "fable", None]: - raise ValueError( - "block_encoding = {block_encoding} not supported for A of type {type(A)}. When A is a matrix block_encoding should take the value 'embedding' or 'fable'. Otherwise, please provide an input with a Pauli decomposition. For more details, see the 'qml.pauli_decompose' function." - ) - - A = qml.math.atleast_2d(A) - max_dimension = 1 if len(qml.math.array(A).shape) == 0 else max(A.shape) - - if block_encoding == "fable": - if qml.math.linalg.norm(max_dimension * qml.math.ravel(A), np.inf) > 1: - raise ValueError( - "The subnormalization factor should be lower than 1. Ensure that the product of the maximum dimension of A and its square norm is less than 1." - ) - - # FABLE encodes A / 2^n, need to rescale to obtain desired block-encoding - - fable_norm = int(np.ceil(np.log2(max_dimension))) - encoding = qml.FABLE(2**fable_norm * A, wires=encoding_wires) - angles = qml.poly_to_angles(poly, "QSVT") - - projectors = [qml.PCPhase(angle, dim=len(A), wires=encoding_wires) for angle in angles] - - else: - c, r = qml.math.shape(A) - - angles = qml.poly_to_angles(poly, "QSVT") - for idx, phi in enumerate(angles): - dim = c if idx % 2 else r - projectors.append(qml.PCPhase(phi, dim=dim, wires=encoding_wires)) - - encoding = qml.BlockEncode(A, wires=encoding_wires) + encoding, projectors = _tensorlike_process(A, poly, encoding_wires, block_encoding) return QSVT(encoding, projectors) @@ -429,9 +448,6 @@ def circuit(): -2.79501771e-01-4.82849614e-02j, 0.00000000e+00+0.00000000e+00j]) """ - num_wires = AnyWires - """int: Number of wires that the operator acts on.""" - grad_method = None """Gradient computation method.""" @@ -439,9 +455,10 @@ def _flatten(self): data = (self.hyperparameters["UA"], self.hyperparameters["projectors"]) return data, tuple() + # pylint: disable=arguments-differ @classmethod - def _primitive_bind_call(cls, *args, **kwargs): - return cls._primitive.bind(*args, **kwargs) + def _primitive_bind_call(cls, UA, projectors, **kwargs): # kwarg is id + return cls._primitive.bind(UA, *projectors, **kwargs) @classmethod def _unflatten(cls, data, _) -> "QSVT": @@ -624,6 +641,14 @@ def compute_matrix(*args, **kwargs): return mat +# pylint: disable=protected-access +if QSVT._primitive is not None: + + @QSVT._primitive.def_impl + def _(UA, *projectors, **kwargs): # kwarg might be id + return type.__call__(QSVT, UA, projectors, **kwargs) + + def _complementary_poly(poly_coeffs): r""" Computes the complementary polynomial Q given a polynomial P. diff --git a/pennylane/templates/subroutines/select.py b/pennylane/templates/subroutines/select.py index 4bab8074b64..66a54f09033 100644 --- a/pennylane/templates/subroutines/select.py +++ b/pennylane/templates/subroutines/select.py @@ -64,8 +64,6 @@ class Select(Operation): """ - num_wires = qml.operation.AnyWires - def _flatten(self): return (self.ops), (self.control) diff --git a/pennylane/templates/subroutines/select_pauli_rot.py b/pennylane/templates/subroutines/select_pauli_rot.py index 1f7341625ae..07ae7c52712 100644 --- a/pennylane/templates/subroutines/select_pauli_rot.py +++ b/pennylane/templates/subroutines/select_pauli_rot.py @@ -16,7 +16,7 @@ """ import pennylane as qml -from pennylane.operation import AnyWires, Operation +from pennylane.operation import Operation from pennylane.templates.state_preparations.mottonen import _apply_uniform_rotation_dagger @@ -79,7 +79,6 @@ def circuit(): 0. +0.j 0. +0.j 0. +0.j 0. +0.j] """ - num_wires = AnyWires grad_method = None ndim_params = (1,) @@ -91,7 +90,7 @@ def __init__( self.hyperparameters["target_wire"] = qml.wires.Wires(target_wire) self.hyperparameters["rot_axis"] = rot_axis - if qml.math.shape(angles)[0] != 2 ** len(control_wires): + if qml.math.shape(angles)[-1] != 2 ** len(control_wires): raise ValueError("Number of angles must be 2^(len(control_wires))") if rot_axis not in ["X", "Y", "Z"]: diff --git a/pennylane/templates/subroutines/uccsd.py b/pennylane/templates/subroutines/uccsd.py index b0328669081..a55b6d31757 100644 --- a/pennylane/templates/subroutines/uccsd.py +++ b/pennylane/templates/subroutines/uccsd.py @@ -20,7 +20,7 @@ import numpy as np import pennylane as qml -from pennylane.operation import AnyWires, Operation +from pennylane.operation import Operation from pennylane.ops import BasisState from pennylane.wires import Wires @@ -174,7 +174,6 @@ def circuit(params, wires, s_wires, d_wires, hf_state): """ - num_wires = AnyWires grad_method = None def __init__( diff --git a/pennylane/templates/swapnetworks/ccl2.py b/pennylane/templates/swapnetworks/ccl2.py index 6e2e45fc6a4..12487842665 100644 --- a/pennylane/templates/swapnetworks/ccl2.py +++ b/pennylane/templates/swapnetworks/ccl2.py @@ -20,7 +20,7 @@ import numpy as np import pennylane as qml -from pennylane.operation import AnyWires, Operation +from pennylane.operation import Operation from pennylane.ops import SWAP, FermionicSWAP @@ -88,7 +88,6 @@ class TwoLocalSwapNetwork(Operation): """ - num_wires = AnyWires grad_method = None @classmethod diff --git a/pennylane/templates/tensornetworks/mera.py b/pennylane/templates/tensornetworks/mera.py index 1adfcad2322..a6024e0bc4f 100644 --- a/pennylane/templates/tensornetworks/mera.py +++ b/pennylane/templates/tensornetworks/mera.py @@ -21,7 +21,7 @@ import numpy as np import pennylane as qml -from pennylane.operation import AnyWires, Operation +from pennylane.operation import Operation def compute_indices(wires, n_block_wires): @@ -168,7 +168,6 @@ def circuit(template_weights): """ - num_wires = AnyWires grad_method = None @property diff --git a/pennylane/templates/tensornetworks/mps.py b/pennylane/templates/tensornetworks/mps.py index 4f7922e6836..67d60b634c1 100644 --- a/pennylane/templates/tensornetworks/mps.py +++ b/pennylane/templates/tensornetworks/mps.py @@ -18,7 +18,7 @@ import warnings import pennylane as qml -from pennylane.operation import AnyWires, Operation +from pennylane.operation import Operation def compute_indices_MPS(wires, n_block_wires, offset=None): @@ -160,9 +160,6 @@ def circuit(): """ - num_wires = AnyWires - par_domain = "A" - @classmethod def _primitive_bind_call( cls, diff --git a/pennylane/templates/tensornetworks/ttn.py b/pennylane/templates/tensornetworks/ttn.py index 40029f4284f..dfb2a54de98 100644 --- a/pennylane/templates/tensornetworks/ttn.py +++ b/pennylane/templates/tensornetworks/ttn.py @@ -20,7 +20,7 @@ import numpy as np import pennylane as qml -from pennylane.operation import AnyWires, Operation +from pennylane.operation import Operation def compute_indices(wires, n_block_wires): @@ -139,7 +139,6 @@ def circuit(template_weights): """ - num_wires = AnyWires grad_method = None @property diff --git a/pennylane/transforms/diagonalize_measurements.py b/pennylane/transforms/diagonalize_measurements.py index 79729748403..9ce3874e386 100644 --- a/pennylane/transforms/diagonalize_measurements.py +++ b/pennylane/transforms/diagonalize_measurements.py @@ -46,7 +46,7 @@ def diagonalize_measurements(tape, supported_base_obs=_default_supported_obs, to Args: tape (QNode or QuantumScript or Callable): The quantum circuit to modify the measurements of. - supported_base_obs (Optional, Iterable(Observable)): A list of supported base observable classes. + supported_base_obs (Optional, Iterable(Operator)): A list of supported base observable classes. Allowed observables are ``qml.X``, ``qml.Y``, ``qml.Z``, ``qml.Hadamard`` and ``qml.Identity``. Z and Identity are always treated as supported, regardless of input. If no list is provided, the transform will diagonalize everything into the Z basis. If a list is provided, only @@ -235,7 +235,7 @@ def _diagonalize_subset_of_pauli_obs(tape, supported_base_obs, to_eigvals=False) Args: tape: the observable to be diagonalized - supported_base_obs (Optional, Iterable(Observable)): A list of supported base observable classes. + supported_base_obs (Optional, Iterable(Operator)): A list of supported base observable classes. Allowed observables are ``qml.X``, ``qml.Y``, ``qml.Z``, ``qml.Hadamard`` and ``qml.Identity``. Z and Identity are always treated as supported, regardless of input. If no list is provided, the transform will diagonalize everything into the Z basis. If a list is provided, only diff --git a/pennylane/transforms/sign_expand/sign_expand.py b/pennylane/transforms/sign_expand/sign_expand.py index ae15299b4b4..dcb9ec5047b 100644 --- a/pennylane/transforms/sign_expand/sign_expand.py +++ b/pennylane/transforms/sign_expand/sign_expand.py @@ -68,7 +68,7 @@ def evolve_under(ops, coeffs, time, controls): Evolves under the given Hamiltonian deconstructed into its Pauli words Args: - ops (List[Observables]): List of Pauli words that comprise the Hamiltonian + ops (List[Operator): List of Pauli words that comprise the Hamiltonian coeffs (List[int]): List of the respective coefficients of the Pauliwords of the Hamiltonian time (float): At what time to evaluate these Pauliwords """ diff --git a/pennylane/transforms/tape_expand.py b/pennylane/transforms/tape_expand.py index cf96fdf1b55..0eb6c262c20 100644 --- a/pennylane/transforms/tape_expand.py +++ b/pennylane/transforms/tape_expand.py @@ -18,21 +18,13 @@ import warnings import pennylane as qml -from pennylane.operation import ( - gen_is_multi_term_hamiltonian, - has_gen, - has_grad_method, - has_nopar, - has_unitary_gen, - is_measurement, - is_trainable, - not_tape, -) +from pennylane import math +from pennylane.measurements import MeasurementProcess def _update_trainable_params(tape): params = tape.get_parameters(trainable_only=False) - tape.trainable_params = qml.math.get_trainable_indices(params) + tape.trainable_params = math.get_trainable_indices(params) def create_expand_fn(depth, stop_at=None, device=None, docstring=None): @@ -65,7 +57,8 @@ def create_expand_fn(depth, stop_at=None, device=None, docstring=None): steps, which can be controlled with the argument ``depth``. The stopping criterion is easy to write as - >>> stop_at = ~(qml.operation.has_multipar & qml.operation.is_trainable) + >>> def stop_at(obj): + ... return not (len(op.data) > 1 and any(qml.math.requires_grad(d) for d in obj.data)) Then the expansion function can be obtained via @@ -97,7 +90,10 @@ def create_expand_fn(depth, stop_at=None, device=None, docstring=None): if stop_at is None: stop_at = device.stopping_condition else: - stop_at &= device.stopping_condition + orig_stop_at = stop_at + + def stop_at(obj): + return orig_stop_at(obj) and device.stopping_condition(obj) def expand_fn(tape, depth=depth, **kwargs): with qml.QueuingManager.stop_recording(): @@ -133,9 +129,21 @@ def expand_fn(tape, depth=depth, **kwargs): .QuantumTape: the expanded tape """ + +def _multipar_stopping_fn(obj): + try: + return ( + isinstance(obj, MeasurementProcess) + or len(obj.data) == 0 + or (obj.has_generator and len(obj.generator().terms()[0]) == 1) + ) + except qml.operation.TermsUndefinedError: + return True + + expand_multipar = create_expand_fn( depth=None, - stop_at=not_tape | is_measurement | has_nopar | (has_gen & ~gen_is_multi_term_hamiltonian), + stop_at=_multipar_stopping_fn, docstring=_expand_multipar_doc, ) @@ -156,13 +164,14 @@ def expand_fn(tape, depth=depth, **kwargs): .QuantumTape: the expanded tape """ + +def _trainable_multipar_stopping_fn(obj): + return _multipar_stopping_fn(obj) or not any(math.requires_grad(d) for d in obj.data) + + expand_trainable_multipar = create_expand_fn( depth=None, - stop_at=not_tape - | is_measurement - | has_nopar - | (~is_trainable) - | (has_gen & ~gen_is_multi_term_hamiltonian), + stop_at=_trainable_multipar_stopping_fn, docstring=_expand_trainable_multipar_doc, ) @@ -177,17 +186,12 @@ def create_expand_trainable_multipar(tape, use_tape_argnum=False): trainable_par_info = [tape.par_info[i] for i in tape.trainable_params] trainable_ops = [info["op"] for info in trainable_par_info] - @qml.BooleanFn - def _is_trainable(obj): - return obj in trainable_ops + def _argnum_trainable_multipar(obj): + return _multipar_stopping_fn(obj) or obj not in trainable_ops return create_expand_fn( depth=None, - stop_at=not_tape - | is_measurement - | has_nopar - | (~_is_trainable) - | (has_gen & ~gen_is_multi_term_hamiltonian), + stop_at=_argnum_trainable_multipar, docstring=_expand_trainable_multipar_doc, ) @@ -209,9 +213,18 @@ def _is_trainable(obj): .QuantumTape: the expanded tape """ + +def _expand_nonunitary_gen_stop_at(obj): + return ( + isinstance(obj, MeasurementProcess) + or len(obj.data) == 0 + or (obj.has_generator and obj in qml.ops.qubit.attributes.has_unitary_generator) + ) + + expand_nonunitary_gen = create_expand_fn( depth=None, - stop_at=not_tape | is_measurement | has_nopar | (has_gen & has_unitary_gen), + stop_at=_expand_nonunitary_gen_stop_at, docstring=_expand_nonunitary_gen_doc, ) @@ -232,9 +245,18 @@ def _is_trainable(obj): .QuantumTape: the expanded tape """ + +def _stop_at_expand_invalid_trainable(obj): + return ( + isinstance(obj, MeasurementProcess) + or not any(math.requires_grad(d) for d in obj.data) + or obj.grad_method is not None + ) + + expand_invalid_trainable = create_expand_fn( depth=None, - stop_at=not_tape | is_measurement | (~is_trainable) | has_grad_method, + stop_at=_stop_at_expand_invalid_trainable, docstring=_expand_invalid_trainable_doc, ) diff --git a/pennylane/workflow/get_best_diff_method.py b/pennylane/workflow/get_best_diff_method.py index 2fd78b52dcf..bad0a077a46 100644 --- a/pennylane/workflow/get_best_diff_method.py +++ b/pennylane/workflow/get_best_diff_method.py @@ -39,7 +39,7 @@ def get_best_diff_method(qnode: QNode): .. seealso:: For a detailed comparison of the backpropagation and parameter-shift methods, - refer to the :doc:`quantum gradients with backpropagation example `. + refer to the `quantum gradients with backpropagation example `__. Args: qnode (.QNode): the qnode to get the 'best' differentiation method for. diff --git a/setup.py b/setup.py index b65d80b52e8..1595dbc121a 100644 --- a/setup.py +++ b/setup.py @@ -75,7 +75,6 @@ "Development Status :: 4 - Beta", "Environment :: Console", "Intended Audience :: Science/Research", - "License :: OSI Approved :: Apache Software License", "Natural Language :: English", "Operating System :: POSIX", "Operating System :: MacOS :: MacOS X", diff --git a/tests/capture/test_templates.py b/tests/capture/test_templates.py index 0e16c0ba17f..a89b130ff0f 100644 --- a/tests/capture/test_templates.py +++ b/tests/capture/test_templates.py @@ -544,28 +544,27 @@ def qfunc(): def test_qsvt(self): """Test the primitive bind call of QSVT.""" - A = np.array([[0.1]]) - block_encode = qml.BlockEncode(A, wires=[0, 1]) - shifts = [qml.PCPhase(i + 0.1, dim=1, wires=[0, 1]) for i in range(3)] - - def qfunc(block_encode): + def qfunc(A): + block_encode = qml.BlockEncode(A, wires=[0, 1]) + shifts = [qml.PCPhase(i + 0.1, dim=1, wires=[0, 1]) for i in range(3)] qml.QSVT(block_encode, projectors=shifts) + A = np.array([[0.1]]) # Validate inputs - qfunc(block_encode) + qfunc(A) # Actually test primitive bind - jaxpr = jax.make_jaxpr(qfunc)(block_encode) + jaxpr = jax.make_jaxpr(qfunc)(A) - assert len(jaxpr.eqns) == 2 + assert len(jaxpr.eqns) == 5 - # due to flattening and unflattening BlockEncode assert jaxpr.eqns[0].primitive == qml.BlockEncode._primitive - eqn = jaxpr.eqns[1] + eqn = jaxpr.eqns[-1] assert eqn.primitive == qml.QSVT._primitive - assert eqn.invars == jaxpr.eqns[0].outvars - assert eqn.params == {"projectors": shifts} + for i in range(4): + assert eqn.invars[i] == jaxpr.eqns[i].outvars[0] + assert eqn.params == {} assert len(eqn.outvars) == 1 assert isinstance(eqn.outvars[0], jax.core.DropVar) @@ -573,6 +572,8 @@ def qfunc(block_encode): jax.core.eval_jaxpr(jaxpr.jaxpr, jaxpr.consts, A) assert len(q) == 1 + block_encode = qml.BlockEncode(A, wires=[0, 1]) + shifts = [qml.PCPhase(i + 0.1, dim=1, wires=[0, 1]) for i in range(3)] assert q.queue[0] == qml.QSVT(block_encode, shifts) def test_mps_prep(self): diff --git a/tests/devices/default_qubit/test_default_qubit.py b/tests/devices/default_qubit/test_default_qubit.py index 879636d31db..79341835ac3 100644 --- a/tests/devices/default_qubit/test_default_qubit.py +++ b/tests/devices/default_qubit/test_default_qubit.py @@ -2215,6 +2215,22 @@ def qfunc(x, y): assert qml.math.allclose(grad, grad_jit) + def test_snapshot_with_defer_measurement(self): + """Test that snapshots can be taken with defer_measurements.""" + + dev = qml.device("default.qubit") + + @qml.qnode(dev) + def func(): + qml.Hadamard(wires=0) + qml.measure(0) + qml.Snapshot("label") + return qml.probs(wires=0) + + snapshots = qml.snapshots(func)() + assert snapshots["label"].shape == (4,) + assert qml.math.allclose(snapshots["execution_results"], np.array([0.5, 0.5])) + @pytest.mark.parametrize("max_workers", max_workers_list) def test_broadcasted_parameter(max_workers): diff --git a/tests/devices/qubit/test_apply_operation.py b/tests/devices/qubit/test_apply_operation.py index 494bfe0fe8c..c520d4e55ab 100644 --- a/tests/devices/qubit/test_apply_operation.py +++ b/tests/devices/qubit/test_apply_operation.py @@ -770,6 +770,17 @@ def test_measurement(self, ml_framework): assert debugger.snapshots[0].shape == () assert debugger.snapshots[0] == qml.devices.qubit.measure(measurement, initial_state) + def test_override_shots(self, ml_framework): + """Test that shots can be overridden for one measurement.""" + + initial_state = qml.math.asarray(np.array([1.0, 0.0]), like=ml_framework) + + debugger = Debugger() + op = qml.Snapshot("tag", qml.sample(wires=0), shots=50) + _ = apply_operation(op, initial_state, debugger=debugger) + + assert debugger.snapshots["tag"].shape == (50,) + def test_batched_state(self, ml_framework): """Test that batched states create batched snapshots.""" initial_state = qml.math.asarray([[1.0, 0.0], [0.0, 0.1]], like=ml_framework) diff --git a/tests/devices/qubit/test_initialize_state.py b/tests/devices/qubit/test_initialize_state.py index bee54cc6bc5..92110411365 100644 --- a/tests/devices/qubit/test_initialize_state.py +++ b/tests/devices/qubit/test_initialize_state.py @@ -29,8 +29,6 @@ class TestInitializeState: class DefaultPrep(StatePrepBase): """A dummy class that assumes it was given a state vector.""" - num_wires = qml.operation.AllWires - def __init__(self, *args, **kwargs): self.dtype = kwargs.pop("dtype", None) super().__init__(*args, **kwargs) diff --git a/tests/devices/qubit/test_measure.py b/tests/devices/qubit/test_measure.py index 8ddc076d028..4fda12db209 100644 --- a/tests/devices/qubit/test_measure.py +++ b/tests/devices/qubit/test_measure.py @@ -100,7 +100,7 @@ def test_sum_sum_of_terms_when_backprop(self): def test_no_sparse_matrix(self): """Tests Hamiltonians/Sums containing observables that do not have a sparse matrix.""" - class DummyOp(qml.operation.Observable): # pylint: disable=too-few-public-methods + class DummyOp(qml.operation.Operator): # pylint: disable=too-few-public-methods num_wires = 1 S1 = qml.Hamiltonian([0.5, 0.5], [qml.X(0), DummyOp(wires=1)]) diff --git a/tests/devices/qubit_mixed/test_qubit_mixed_apply_operation.py b/tests/devices/qubit_mixed/test_qubit_mixed_apply_operation.py index 13d16195378..cbc0c9af3b3 100644 --- a/tests/devices/qubit_mixed/test_qubit_mixed_apply_operation.py +++ b/tests/devices/qubit_mixed/test_qubit_mixed_apply_operation.py @@ -817,13 +817,12 @@ def test_snapshot_with_shots_and_measurement( if isinstance(measurement, qml.measurements.SampleMP): len_measured_wires = len(measurement.wires) assert ( - snapshot_result[0].shape == (1000, len_measured_wires) + snapshot_result.shape == (1000, len_measured_wires) if not is_state_batched else (2, 1000, len_measured_wires) ) assert set(np.unique(snapshot_result)) <= {0, 1} elif isinstance(measurement, qml.measurements.CountsMP): - snapshot_result = snapshot_result[0] if is_state_batched: snapshot_result = snapshot_result[0] assert isinstance(snapshot_result, dict) diff --git a/tests/devices/qubit_mixed/test_qubit_mixed_initialize_state.py b/tests/devices/qubit_mixed/test_qubit_mixed_initialize_state.py index c93b76352a2..bb3184a2dab 100644 --- a/tests/devices/qubit_mixed/test_qubit_mixed_initialize_state.py +++ b/tests/devices/qubit_mixed/test_qubit_mixed_initialize_state.py @@ -50,8 +50,6 @@ class TestInitializeState: class DefaultPrep(StatePrepBase): """A dummy class that assumes it was given a state vector.""" - num_wires = qml.operation.AllWires - def __init__(self, *args, **kwargs): self.dtype = kwargs.pop("dtype", None) super().__init__(*args, **kwargs) diff --git a/tests/devices/qubit_mixed/test_qubit_mixed_measure.py b/tests/devices/qubit_mixed/test_qubit_mixed_measure.py index 11d1585cfd3..86d9fc1fac7 100644 --- a/tests/devices/qubit_mixed/test_qubit_mixed_measure.py +++ b/tests/devices/qubit_mixed/test_qubit_mixed_measure.py @@ -151,7 +151,7 @@ def test_sparse_method_for_density_matrix(self): def test_no_sparse_matrix(self): """Tests Hamiltonians/Sums containing observables that do not have a sparse matrix.""" - class DummyOp(qml.operation.Observable): # pylint: disable=too-few-public-methods + class DummyOp(qml.operation.Operator): # pylint: disable=too-few-public-methods num_wires = 1 S1 = qml.Hamiltonian([0.5, 0.5], [qml.X(0), DummyOp(wires=1)]) @@ -170,7 +170,7 @@ class DummyOp(qml.operation.Observable): # pylint: disable=too-few-public-metho def test_hamiltonian_no_sparse_matrix_in_second_term(self): """Tests when not all terms of a Hamiltonian have sparse matrices, excluding the first term.""" - class DummyOp(qml.operation.Observable): # Custom observable with no sparse matrix + class DummyOp(qml.operation.Operator): # Custom observable with no sparse matrix num_wires = 1 H = qml.Hamiltonian([0.5, 0.5, 0.5], [qml.PauliX(0), DummyOp(wires=1), qml.PauliZ(2)]) @@ -180,7 +180,7 @@ class DummyOp(qml.operation.Observable): # Custom observable with no sparse mat def test_sum_no_sparse_matrix(self): """Tests when not all terms in a Sum observable have sparse matrices.""" - class DummyOp(qml.operation.Observable): # Custom observable with no sparse matrix + class DummyOp(qml.operation.Operator): # Custom observable with no sparse matrix num_wires = 1 S = qml.sum(qml.PauliX(0), DummyOp(wires=1)) diff --git a/tests/devices/qutrit_mixed/test_qutrit_mixed_initialize_state.py b/tests/devices/qutrit_mixed/test_qutrit_mixed_initialize_state.py index d682deb6827..a7749f3987a 100644 --- a/tests/devices/qutrit_mixed/test_qutrit_mixed_initialize_state.py +++ b/tests/devices/qutrit_mixed/test_qutrit_mixed_initialize_state.py @@ -29,8 +29,6 @@ class TestInitializeState: class DefaultPrep(StatePrepBase): """A dummy class that assumes it was given a state vector.""" - num_wires = qml.operation.AllWires - def state_vector(self, wire_order=None): return self.parameters[0] diff --git a/tests/devices/test_default_mixed.py b/tests/devices/test_default_mixed.py index 70995d4cf63..6491eef97e8 100644 --- a/tests/devices/test_default_mixed.py +++ b/tests/devices/test_default_mixed.py @@ -96,3 +96,67 @@ def test_execute_no_diff_method(self): assert ( processed_config.interface is Interface.NUMPY ), "The interface should be set to numpy for an invalid gradient method" + + +# pylint: disable=too-few-public-methods +class TestDefaultMixedTrainability: + """Integration tests for DefaultMixed trainable parameters""" + + @pytest.mark.integration + @pytest.mark.all_interfaces + @pytest.mark.parametrize("interface", ML_INTERFACES) + def test_trainable_params_interface(self, interface): + """Test that the trainable parameters are correctly identified""" + dev = DefaultMixed(wires=2) + param = qml.math.array(0.5, like=interface) + + # Make a trainable, parametrized circuit + def circuit(x): + qml.RX(x, wires=0) + qml.RY(x, wires=1) + return qml.expval(qml.PauliZ(0)) + + # Create a QNode with the specified interface + circuit = qml.QNode( + circuit, + dev, + interface=interface, + ) + # Execute the circuit + result = circuit(param) + # Check that the result is a tensor with the correct interface + assert isinstance(result, qml.typing.TensorLike) + assert qml.math.get_interface(result) == interface + + @pytest.mark.integration + @pytest.mark.all_interfaces + @pytest.mark.parametrize("interface", ["torch", "autograd"]) + def test_trainable_initial_state(self, interface): + """Test that the trainable parameters are correctly applied to initial state""" + num_qubits = 2 + dev = DefaultMixed(wires=num_qubits) + state = qml.math.array( + [ + 1.0, + ], + like=interface, + requires_grad=True, + ) + + # Make a trainable, parametrized circuit + def circuit_StatePrep(state): + qml.StatePrep(state=state, wires=list(range(num_qubits)), normalize=True, pad_with=0) + + return [qml.expval(qml.PauliZ(wires=q)) for q in range(num_qubits)] + + # Create a QNode with the specified interface + circuit = qml.QNode( + circuit_StatePrep, + dev, + interface=interface, + ) + # Execute the circuit + result = circuit(state) + # Check that the result is a tensor with the correct interface + assert isinstance(result, qml.typing.TensorLike) + assert qml.math.get_deep_interface(result) == interface diff --git a/tests/devices/test_legacy_device.py b/tests/devices/test_legacy_device.py index aa79347e000..14f8fead915 100644 --- a/tests/devices/test_legacy_device.py +++ b/tests/devices/test_legacy_device.py @@ -266,18 +266,10 @@ def test_supports_observable_exception(self, mock_device): with pytest.raises( ValueError, - match="The given observable must either be a pennylane.Observable class or a string.", + match="The given observable must either be a pennylane.operation.Operator class or a string.", ): dev.supports_observable(3) - operation = qml.CNOT - - with pytest.raises( - ValueError, - match="The given observable must either be a pennylane.Observable class or a string.", - ): - dev.supports_observable(operation) - @pytest.mark.parametrize("supported_multi_term_obs", ["Hamiltonian", "LinearCombination"]) @pytest.mark.parametrize("obs_type", [qml.ops.LinearCombination, qml.Hamiltonian]) def test_all_multi_term_obs_supported_linear_combination( diff --git a/tests/devices/test_null_qubit.py b/tests/devices/test_null_qubit.py index 577cd4efe1b..e0570e569ba 100644 --- a/tests/devices/test_null_qubit.py +++ b/tests/devices/test_null_qubit.py @@ -13,6 +13,8 @@ # limitations under the License. """Tests for null.qubit.""" +import json +import os from collections import defaultdict as dd import numpy as np @@ -61,6 +63,73 @@ def test_debugger_attribute(): assert dev._debugger is None +def test_resource_tracking_attribute(): + """Test NullQubit track_resources attribute""" + # pylint: disable=protected-access + assert NullQubit()._track_resources is False + assert NullQubit(track_resources=True)._track_resources is True + + dev = NullQubit(track_resources=True) + + def small_circ(params): + qml.X(0) + qml.H(0) + + qml.Barrier() + + # Add a more complex operation to check that the innermost operation is counted + op = qml.T(0) + op = qml.adjoint(op) + op = qml.ctrl(op, control=1, control_values=[1]) + + qml.ctrl(qml.S(0), control=[1, 2], control_values=[1, 1]) + + qml.CNOT([0, 1]) + qml.Barrier() + + qml.ctrl(qml.IsingXX(0, [0, 1]), control=2, control_values=[1]) + qml.adjoint(qml.S(0)) + + qml.RX(params[0], wires=0) + qml.RX(params[0] * 2, wires=1) + + return qml.expval(qml.PauliZ(0)) + + qnode = qml.QNode(small_circ, dev, diff_method="backprop") + + inputs = qml.numpy.array([0.5]) + + qnode(inputs) + + # Check that resource tracking doesn't interfere with backprop + assert qml.grad(qnode)(inputs) == 0 + + RESOURCES_FNAME = "__pennylane_resources_data.json" + assert os.path.exists(RESOURCES_FNAME) + + with open(RESOURCES_FNAME, "r") as f: + stats = f.read() + + os.remove(RESOURCES_FNAME) + + assert stats == json.dumps( + { + "num_wires": 3, + "num_gates": 9, + "gate_types": { + "PauliX": 1, + "Hadamard": 1, + "C(Adj(T))": 1, + "2C(S)": 1, + "CNOT": 1, + "C(IsingXX)": 1, + "Adj(S)": 1, + "RX": 2, + }, + } + ) + + @pytest.mark.parametrize("shots", (None, 10)) def test_supports_operator_without_decomp(shots): """Test that null.qubit automatically supports any operation without a decomposition.""" diff --git a/tests/devices/test_qubit_device.py b/tests/devices/test_qubit_device.py index 3ba64315918..209135c14bc 100644 --- a/tests/devices/test_qubit_device.py +++ b/tests/devices/test_qubit_device.py @@ -648,7 +648,7 @@ def test_no_eigval_error(self, mock_qubit_device_with_original_statistics): dev = mock_qubit_device_with_original_statistics() # observable with no eigenvalue representation defined - class MyObs(qml.operation.Observable): + class MyObs(qml.operation.Operator): num_wires = 1 def eigvals(self): @@ -727,7 +727,7 @@ def test_no_eigval_error(self, mock_qubit_device_with_original_statistics): dev = mock_qubit_device_with_original_statistics() # pylint: disable=too-few-public-methods - class MyObs(qml.operation.Observable): + class MyObs(qml.operation.Operator): """Observable with no eigenvalue representation defined.""" num_wires = 1 @@ -799,7 +799,7 @@ def test_no_eigval_error(self, mock_qubit_device_with_original_statistics): dev = mock_qubit_device_with_original_statistics() dev._samples = np.array([[1, 0], [0, 0]]) - class MyObs(qml.operation.Observable): + class MyObs(qml.operation.Operator): """Observable with no eigenvalue representation defined.""" num_wires = 1 @@ -869,7 +869,7 @@ def test_no_eigval_error(self, mock_qubit_device_with_original_statistics): dev = mock_qubit_device_with_original_statistics() dev._samples = np.array([[[1, 0], [1, 1]], [[1, 1], [0, 0]], [[0, 1], [1, 0]]]) - class MyObs(qml.operation.Observable): + class MyObs(qml.operation.Operator): """Observable with no eigenvalue representation defined.""" num_wires = 1 diff --git a/tests/devices/test_qutrit_device.py b/tests/devices/test_qutrit_device.py index 4409d8b5f39..c5f10696c1f 100644 --- a/tests/devices/test_qutrit_device.py +++ b/tests/devices/test_qutrit_device.py @@ -434,9 +434,8 @@ def test_no_eigval_error(self, mock_qutrit_device_with_original_statistics): dev = mock_qutrit_device_with_original_statistics(wires=2) dev._samples = np.array([[1, 0], [0, 2]]) - class SomeObservable(qml.operation.Observable): + class SomeObservable(qml.operation.Operator): num_wires = 1 - return_type = "Sample" obs = SomeObservable(wires=0) with pytest.raises(qml.operation.EigvalsUndefinedError, match="Cannot compute samples"): @@ -705,7 +704,7 @@ def test_no_eigval_error(self, mock_qutrit_device_with_original_statistics): dev = mock_qutrit_device_with_original_statistics() # observable with no eigenvalue representation defined - class MyObs(qml.operation.Observable): + class MyObs(qml.operation.Operator): num_wires = 1 def eigvals(self): @@ -766,7 +765,7 @@ def test_no_eigval_error(self, mock_qutrit_device_with_original_statistics): dev = mock_qutrit_device_with_original_statistics() # observable with no eigenvalue representation defined - class MyObs(qml.operation.Observable): + class MyObs(qml.operation.Operator): num_wires = 1 def eigvals(self): diff --git a/tests/fermi/test_bravyi_kitaev.py b/tests/fermi/test_bravyi_kitaev.py index a78e0cb3735..09cb90386a5 100644 --- a/tests/fermi/test_bravyi_kitaev.py +++ b/tests/fermi/test_bravyi_kitaev.py @@ -476,7 +476,7 @@ def test_bravyi_kitaev_for_identity_ps(): ), ) def test_bravyi_kitaev_for_null_operator_fermi_word_ps(operator): - """Test that the parity_tranform function works when the result is 0""" + """Test that the parity_transform function works when the result is 0""" # in PauliSentence return format, returns None assert bravyi_kitaev(operator, 4, ps=True).simplify() is None diff --git a/tests/fermi/test_parity_mapping.py b/tests/fermi/test_parity_mapping.py index 87ac986bcb7..6b93bde2812 100644 --- a/tests/fermi/test_parity_mapping.py +++ b/tests/fermi/test_parity_mapping.py @@ -486,7 +486,7 @@ def test_parity_transform_for_identity_ps(): ), ) def test_parity_transform_for_null_operator_fermi_word_ps(operator): - """Test that the parity_tranform function works when the result is 0""" + """Test that the parity_transform function works when the result is 0""" # in PauliSentence return format, returns None assert parity_transform(operator, 4, ps=True).simplify() is None diff --git a/tests/ftqc/test_cond_measure.py b/tests/ftqc/test_cond_measure.py index 1e74ede8782..78a3bc16992 100644 --- a/tests/ftqc/test_cond_measure.py +++ b/tests/ftqc/test_cond_measure.py @@ -178,16 +178,26 @@ def test_mismatched_wires_raises_error(self): cond_measure(m, partial(measure_y, wires=0), partial(measure_x, wires=1))() @pytest.mark.jax - def test_program_capture_raises_error(self): - """Test that a clear error is raised when executing with program capture""" - try: - qml.capture.enable() - - with pytest.raises(NotImplementedError, match="not compatible with program capture"): - m = qml.measure(0) - cond_measure(m, measure_x, measure_y)(0) - finally: - qml.capture.disable() + @pytest.mark.usefixtures("enable_disable_plxpr") + def test_program_capture(self): + """Test that program capture works as expected with cond_measure""" + import jax + + def func(): + m = qml.measure(0) + cond_measure(m, measure_x, measure_y)(0) + + plxpr = jax.make_jaxpr(func)() + + cond_eq = plxpr.eqns[1] + assert "cond" in str(cond_eq) + cond_branches = cond_eq.params["jaxpr_branches"] + assert len(cond_branches) == 2 + for branch, angle in zip(cond_branches, [0, 1.57]): + branch_str = str(branch) + assert "measure_in_basis" in branch_str + assert "plane=XY" in branch_str + assert str(angle) in branch_str class TestWorkflows: diff --git a/tests/ftqc/test_ftqc_graph_state_prep.py b/tests/ftqc/test_ftqc_graph_state_prep.py index f936cf26b79..b254217824b 100644 --- a/tests/ftqc/test_ftqc_graph_state_prep.py +++ b/tests/ftqc/test_ftqc_graph_state_prep.py @@ -19,7 +19,7 @@ import pytest import pennylane as qml -from pennylane.ftqc import GraphStatePrep, QubitGraph, generate_lattice +from pennylane.ftqc import GraphStatePrep, QubitGraph, generate_lattice, make_graph_state from pennylane.ops.functions import assert_valid @@ -289,6 +289,93 @@ def test_wires_graph_mismatch(self, one_qubit_ops, two_qubit_ops): ) +class TestMakeGraphState: + """Test the helper-function make_graph_state that allows graph state preparation + to be capture compatible""" + + def test_make_graph_state_no_capture(self, mocker): + """Test that the default behaviour for make_graph_state works as expected""" + + lattice = generate_lattice([2, 2], "square") + q_graph = QubitGraph(lattice.graph) + + spy = mocker.spy(qml.ftqc.graph_state_preparation, "GraphStatePrep") + + with qml.queuing.AnnotatedQueue() as q: + make_graph_state(q_graph, [0, 1, 2, 3]) + + assert len(q) == 1 + assert isinstance(q.queue[0], GraphStatePrep) + spy.assert_called_with( + graph=q_graph, one_qubit_ops=qml.H, two_qubit_ops=qml.CZ, wires=[0, 1, 2, 3] + ) + + def test_passing_operations_no_capture(self, mocker): + """Test that gates for the graph state can be specified in make_graph_state, + and are passed to GraphStatePrep""" + + lattice = generate_lattice([2, 2], "square") + q_graph = QubitGraph(lattice.graph) + + spy = mocker.spy(qml.ftqc.graph_state_preparation, "GraphStatePrep") + + with qml.queuing.AnnotatedQueue() as q: + make_graph_state(q_graph, [0, 1, 2, 3], one_qubit_ops=qml.X, two_qubit_ops=qml.CNOT) + + assert len(q) == 1 + assert isinstance(q.queue[0], GraphStatePrep) + spy.assert_called_with( + graph=q_graph, one_qubit_ops=qml.X, two_qubit_ops=qml.CNOT, wires=[0, 1, 2, 3] + ) + + @pytest.mark.jax + @pytest.mark.usefixtures("enable_disable_plxpr") + def test_make_graph_state_with_capture(self, mocker): + """Test that make_graph_state adds the decomposed graph state to the plxpr""" + import jax + + spy = mocker.spy(qml.ftqc.graph_state_preparation.GraphStatePrep, "compute_decomposition") + + lattice = generate_lattice([2, 2], "square") + q_graph = QubitGraph(lattice.graph) + wires = [0, 1, 2, 3] + + def func(): + make_graph_state(q_graph, wires) + + plxpr = jax.make_jaxpr(func)() + + # the operator queue looks correct + assert len(plxpr.eqns) == 8 + for eq in plxpr.eqns[:4]: + assert "Hadamard" in str(eq) + for eq in plxpr.eqns[4:]: + assert "CZ" in str(eq) + + # the queue was generated by the expected method + spy.assert_called_with([0, 1, 2, 3], q_graph, qml.H, qml.CZ) + + @pytest.mark.jax + @pytest.mark.usefixtures("enable_disable_plxpr") + def test_passing_operations_with_capture(self, mocker): + """Test that gates for the graph state can be specified in make_graph_state, + and are passed to compute_decomposition when capture is enabled""" + import jax + + lattice = generate_lattice([2, 2], "square") + q_graph = QubitGraph(lattice.graph) + + spy = mocker.spy(qml.ftqc.graph_state_preparation.GraphStatePrep, "compute_decomposition") + + def func(): + make_graph_state(q_graph, [0, 1, 2, 3], one_qubit_ops=qml.X, two_qubit_ops=qml.CNOT) + + plxpr = jax.make_jaxpr(func)() + + assert len(plxpr.eqns) == 8 + spy.assert_called_with([0, 1, 2, 3], q_graph, qml.X, qml.CNOT) + + class TestGraphStateInvariantUnderInternalGraphOrdering: """Additional tests of graph-state preparation to ensure that the resulting state is invariant under the internal ordering of the nodes and edges in the graph. diff --git a/tests/ftqc/test_parametric_mid_measure.py b/tests/ftqc/test_parametric_mid_measure.py index 070dcff83e3..3dc222b6416 100644 --- a/tests/ftqc/test_parametric_mid_measure.py +++ b/tests/ftqc/test_parametric_mid_measure.py @@ -463,6 +463,20 @@ def test_error_is_raised_if_too_many_wires(self, func): ): func([0, 1]) + @pytest.mark.jax + @pytest.mark.usefixtures("enable_disable_plxpr") + @pytest.mark.parametrize( + "func", [partial(measure_arbitrary_basis, angle=-0.8, plane="XY"), measure_x, measure_y] + ) + def test_error_is_raised_if_too_many_wires_capture(self, func): + """Test that a QuanutmFunctionError is raised if too many wires are passed when using capture""" + + with pytest.raises( + qml.QuantumFunctionError, + match="Only a single qubit can be measured in the middle of the circuit", + ): + func([0, 1]) + @pytest.mark.parametrize("reset", [True, False]) @pytest.mark.parametrize("postselect", [0, 1, None]) def test_measure_z_dispatches_to_measure(self, reset, postselect): @@ -480,6 +494,100 @@ def test_measure_z_dispatches_to_measure(self, reset, postselect): assert mp.postselect == postselect assert isinstance(mp, MidMeasureMP) + # pylint: disable=too-many-positional-arguments, too-many-arguments + @pytest.mark.jax + @pytest.mark.usefixtures("enable_disable_plxpr") + @pytest.mark.parametrize( + "meas_func, angle, plane", [(measure_x, 0.0, "XY"), (measure_y, 1.5707, "XY")] + ) + @pytest.mark.parametrize( + "wire, reset, postselect", ((2, True, None), (3, False, 0), (0, True, 1)) + ) + def test_x_and_y_with_program_capture(self, meas_func, angle, plane, wire, reset, postselect): + """Test that the measure_ functions are captured as expected""" + import jax + + def circ(): + m = meas_func(wire, reset=reset, postselect=postselect) + qml.cond(m, qml.X, qml.Y)(0) + return qml.expval(qml.Z(2)) + + plxpr = jax.make_jaxpr(circ)() + captured_measurement = str(plxpr.eqns[0]) + + # measurement is captured as epxected + assert "measure_in_basis" in captured_measurement + assert f"plane={plane}" in captured_measurement + assert f"postselect={postselect}" in captured_measurement + assert f"reset={reset}" in captured_measurement + + # last section is parameters + dynamic_params = captured_measurement.rsplit("] ", maxsplit=1)[-1] + assert str(angle) in dynamic_params + assert str(wire) in dynamic_params + + # measurement value is assigned and passed forward + conditional = str(plxpr.eqns[1]) + assert "cond" in conditional + assert captured_measurement[:8] == "a:bool[]" + assert "lambda ; a:i64[]" in conditional + + @pytest.mark.jax + @pytest.mark.usefixtures("enable_disable_plxpr") + @pytest.mark.parametrize("angle, plane", [(1.23, "XY"), (1.5707, "YZ"), (-0.34, "ZX")]) + @pytest.mark.parametrize( + "wire, reset, postselect", ((2, True, None), (3, False, 0), (0, True, 1)) + ) + def test_arbitrary_basis_with_program_capture(self, angle, plane, wire, reset, postselect): + """Test that the measure_ functions are captured as expected""" + import jax + import networkx as nx + + def circ(): + m = measure_arbitrary_basis( + wire, angle=angle, plane=plane, reset=reset, postselect=postselect + ) + qml.cond(m, qml.X, qml.Y)(0) + qml.ftqc.make_graph_state(nx.grid_graph((4,)), [0, 1, 2, 3]) + return qml.expval(qml.Z(2)) + + plxpr = jax.make_jaxpr(circ)() + captured_measurement = str(plxpr.eqns[0]) + + # measurement is captured as epxected + assert "measure_in_basis" in captured_measurement + assert f"plane={plane}" in captured_measurement + assert f"postselect={postselect}" in captured_measurement + assert f"reset={reset}" in captured_measurement + + # last section is parameters + dynamic_params = captured_measurement.rsplit("] ", maxsplit=1)[-1] + assert str(angle) in dynamic_params + assert str(wire) in dynamic_params + + # measurement value is assigned and passed forward + conditional = str(plxpr.eqns[1]) + assert "cond" in conditional + assert captured_measurement[:8] == "a:bool[]" + assert "lambda ; a:i64[]" in conditional + + @pytest.mark.jax + @pytest.mark.usefixtures("enable_disable_plxpr") + @pytest.mark.parametrize( + "func, kwargs", + [ + (measure_x, {"wires": 2}), + (measure_y, {"wires": 2}), + (measure_arbitrary_basis, {"wires": 2, "angle": 1.2, "plane": "XY"}), + ], + ) + def test_calling_functions_with_capture_enabled(self, func, kwargs): + """Test that the functions can still be called and return a measurement value + with capture enabled.""" + + m = func(**kwargs) + assert isinstance(m, MeasurementValue) + class TestDrawParametricMidMeasure: @pytest.mark.matplotlib @@ -808,3 +916,41 @@ def circ(): assert np.allclose(circ(), [np.cos(2.345), -1], atol=0.03) else: assert np.allclose(circ(), [np.cos(2.345), -1]) + + @pytest.mark.parametrize("mcm_method, shots", [("tree-traversal", None), ("one-shot", 10000)]) + def test_diagonalize_mcms_returns_parametrized_mcms(self, mcm_method, shots): + """Test that when diagonalizing, parametrized mid-circuit measurements can be returned + by the QNode""" + + dev = qml.device("default.qubit", shots=shots) + + @diagonalize_mcms + @qml.qnode(dev, mcm_method=mcm_method) + def circ(): + m0 = measure_x(0) + m1 = measure_y(1) + m2 = measure_arbitrary_basis(2, angle=1.23, plane="XY") + + return qml.expval(m0), qml.expval(m1), qml.expval(m2) + + circ() + + @pytest.mark.parametrize("mcm_method, shots", [("tree-traversal", None), ("one-shot", 10000)]) + def test_diagonalize_mcms_returns_cond_measure_result(self, mcm_method, shots): + """Test that when diagonalizing, the MeasurementValue output by cond_measure can be returned + by the QNode""" + + if mcm_method == "one-shot": + pytest.xfail(reason="not implemented yet") # sc-90607 + + dev = qml.device("default.qubit", shots=shots) + + @diagonalize_mcms + @qml.qnode(dev, mcm_method=mcm_method) + def circ(): + qml.H(0) + m0 = measure_x(0) + m1 = cond_measure(m0, measure_x, measure_y)(1) + return qml.expval(m1) + + circ() diff --git a/tests/ftqc/test_primitives.py b/tests/ftqc/test_primitives.py new file mode 100644 index 00000000000..7668d66227f --- /dev/null +++ b/tests/ftqc/test_primitives.py @@ -0,0 +1,28 @@ +# Copyright 2018-2025 Xanadu Quantum Technologies Inc. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Unit tests for the custom capture primitives for the ftqc module""" +import pytest + +pytest.importorskip("jax") + +# pylint: disable=wrong-import-position +from pennylane.capture.custom_primitives import NonInterpPrimitive +from pennylane.ftqc.primitives import measure_in_basis_prim + + +def test_importing_primitive(): + """Test that the measure_in_basis_prim is accessible from pennylane.ftqc.primitives. + This is mostly for CodeCov.""" + + assert isinstance(measure_in_basis_prim, NonInterpPrimitive) diff --git a/tests/gradients/finite_diff/test_finite_difference.py b/tests/gradients/finite_diff/test_finite_difference.py index 26d53fac917..c349c7adb7e 100644 --- a/tests/gradients/finite_diff/test_finite_difference.py +++ b/tests/gradients/finite_diff/test_finite_difference.py @@ -22,7 +22,6 @@ import pennylane as qml from pennylane import numpy as np from pennylane.gradients import finite_diff, finite_diff_coeffs -from pennylane.operation import AnyWires, Observable def test_float32_warning(): @@ -492,11 +491,9 @@ def __radd__(self, other): return self + other # pylint: disable=too-few-public-methods - class SpecialObservable(Observable): + class SpecialObservable(qml.operation.Operator): """SpecialObservable""" - num_wires = AnyWires - def diagonalizing_gates(self): """Diagonalizing gates""" return [] diff --git a/tests/gradients/finite_diff/test_finite_difference_shot_vec.py b/tests/gradients/finite_diff/test_finite_difference_shot_vec.py index c4b7a879e0c..32e0ed242c2 100644 --- a/tests/gradients/finite_diff/test_finite_difference_shot_vec.py +++ b/tests/gradients/finite_diff/test_finite_difference_shot_vec.py @@ -22,7 +22,6 @@ from pennylane import numpy as np from pennylane.gradients import finite_diff from pennylane.measurements import Shots -from pennylane.operation import AnyWires, Observable # pylint:disable = use-implicit-booleaness-not-comparison,abstract-method @@ -412,11 +411,9 @@ def __radd__(self, other): return self + other # pylint: disable=too-few-public-methods - class SpecialObservable(Observable): + class SpecialObservable(qml.operation.Operator): """SpecialObservable""" - num_wires = AnyWires - def diagonalizing_gates(self): """Diagonalizing gates""" return [] diff --git a/tests/gradients/finite_diff/test_spsa_gradient.py b/tests/gradients/finite_diff/test_spsa_gradient.py index 413bc8ff6ec..64cbde3c6a5 100644 --- a/tests/gradients/finite_diff/test_spsa_gradient.py +++ b/tests/gradients/finite_diff/test_spsa_gradient.py @@ -22,7 +22,6 @@ from pennylane import numpy as pnp from pennylane.gradients import spsa_grad from pennylane.gradients.spsa_gradient import _rademacher_sampler -from pennylane.operation import AnyWires, Observable # pylint:disable = use-implicit-booleaness-not-comparison,abstract-method @@ -555,13 +554,11 @@ def __add__(self, other): new = self.val + (other.val if isinstance(other, self.__class__) else other) return SpecialObject(new) - class SpecialObservable(Observable): + class SpecialObservable(qml.operation.Operator): """SpecialObservable""" # pylint:disable=too-few-public-methods - num_wires = AnyWires - def diagonalizing_gates(self): """Diagonalizing gates""" return [] diff --git a/tests/gradients/finite_diff/test_spsa_gradient_shot_vec.py b/tests/gradients/finite_diff/test_spsa_gradient_shot_vec.py index 30b8f01ee58..8bff71213c8 100644 --- a/tests/gradients/finite_diff/test_spsa_gradient_shot_vec.py +++ b/tests/gradients/finite_diff/test_spsa_gradient_shot_vec.py @@ -25,7 +25,7 @@ from pennylane import numpy as pnp from pennylane.gradients import spsa_grad from pennylane.measurements import Shots -from pennylane.operation import AnyWires, Observable +from pennylane.operation import Operator h_val = 0.1 spsa_shot_vec_tol = 0.33 @@ -453,13 +453,11 @@ def __add__(self, other): new = self.val + (other.val if isinstance(other, self.__class__) else other) return SpecialObject(new) - class SpecialObservable(Observable): + class SpecialObservable(Operator): """SpecialObservable""" # pylint:disable=too-few-public-methods - num_wires = AnyWires - def diagonalizing_gates(self): """Diagonalizing gates""" return [] diff --git a/tests/gradients/parameter_shift/test_parameter_shift.py b/tests/gradients/parameter_shift/test_parameter_shift.py index aafef276c81..ee7639a647d 100644 --- a/tests/gradients/parameter_shift/test_parameter_shift.py +++ b/tests/gradients/parameter_shift/test_parameter_shift.py @@ -27,7 +27,6 @@ _put_zeros_in_pdA2_involutory, ) from pennylane.measurements.shots import Shots -from pennylane.operation import AnyWires, Observable # Constants for TestEvaluateGradient # Coefficients and expectation values @@ -2596,11 +2595,9 @@ def __add__(self, other): return SpecialObject(new) # pylint: disable=too-few-public-methods - class SpecialObservable(Observable): + class SpecialObservable(qml.operation.Operator): """SpecialObservable""" - num_wires = AnyWires - def diagonalizing_gates(self): """Diagonalizing gates""" return [] diff --git a/tests/gradients/parameter_shift/test_parameter_shift_shot_vec.py b/tests/gradients/parameter_shift/test_parameter_shift_shot_vec.py index 6d8fccb0962..ae74938697d 100644 --- a/tests/gradients/parameter_shift/test_parameter_shift_shot_vec.py +++ b/tests/gradients/parameter_shift/test_parameter_shift_shot_vec.py @@ -23,7 +23,6 @@ from pennylane import numpy as np from pennylane.gradients import param_shift from pennylane.measurements import Shots -from pennylane.operation import AnyWires, Observable shot_vec_tol = 10e-3 herm_shot_vec_tol = 0.5 @@ -1936,11 +1935,9 @@ def __add__(self, other): new = self.val + (other.val if isinstance(other, self.__class__) else other) return SpecialObject(new) - class SpecialObservable(Observable): + class SpecialObservable(qml.operation.Operator): """SpecialObservable""" - num_wires = AnyWires - def diagonalizing_gates(self): """Diagonalizing gates""" return [] diff --git a/tests/measurements/test_counts.py b/tests/measurements/test_counts.py index 7ae92769053..b9be55cb1ac 100644 --- a/tests/measurements/test_counts.py +++ b/tests/measurements/test_counts.py @@ -1002,3 +1002,48 @@ def test_process_count_returns_same_count_dictionary(self, all_outcomes): actual = qml.counts(wires=wires, all_outcomes=all_outcomes).process_counts(expected, wires) assert actual == expected + + @pytest.mark.parametrize( + "wire_order, expected_result", [((0, 1), {-1.0: 3, 1.0: 2}), ((1, 0), {1.0: 5})] + ) + def test_with_observable(self, wire_order, expected_result): + """Test that processing counts to get the counts for an eigenvalue of an observable + works as expected for an observable with a single wire.""" + + counts_mp = qml.counts(qml.Z(0)) + + result = counts_mp.process_counts({"00": 2, "10": 3}, wire_order) + + assert result == expected_result + + @pytest.mark.parametrize( + "wire_order, expected_result", + [ + ((0, 1, 2), {-1.0: 4, 1.0: 6}), + ((0, 2, 1), {-1.0: 3, 1.0: 7}), + ((2, 1, 0), {-1.0: 4, 1.0: 6}), + ], + ) + def test_with_observable_multi_wire(self, wire_order, expected_result): + """Test that processing counts to get the counts for an eigenvalue of an observable + works as expected for an observable with a single wire.""" + + counts_mp = qml.counts(qml.Z(0) @ qml.Z(2)) + counts = {"000": 2, "001": 1, "101": 2, "010": 1, "110": 3, "111": 1} + + result = counts_mp.process_counts(counts, wire_order) + + assert result == expected_result + + @pytest.mark.parametrize( + "all_outcomes, expected_result", [(True, {-1.0: 0, 1.0: 5}), (False, {1.0: 5})] + ) + def test_process_counts_with_observable_all_outcomes(self, all_outcomes, expected_result): + """Test that all-outcomes works as expected when returning observable/eigenvalue outcomes + instead of counts in the computational basis""" + + counts_mp = qml.counts(qml.Z(0), all_outcomes=all_outcomes) + + result = counts_mp.process_counts({"00": 2, "10": 3}, [1, 0]) + + assert result == expected_result diff --git a/tests/measurements/test_sample.py b/tests/measurements/test_sample.py index a4a206fcd8a..da02b87e774 100644 --- a/tests/measurements/test_sample.py +++ b/tests/measurements/test_sample.py @@ -326,6 +326,8 @@ def test_numeric_type(self, obs): expected_type = np.float64 elif res.numeric_type == complex: expected_type = np.complex64 + else: + raise ValueError("unexpected numeric type for result") assert expected_type == eigval_type @@ -543,15 +545,37 @@ def test_process_counts_single_wire(self): assert np.array_equal(result, np.array([0, 0, 1, 1, 1])) - def test_process_counts_with_eigen_values(self): + @pytest.mark.parametrize( + "wire_order, expected_result", [((0, 1), [1, 1, -1, -1, -1]), ((1, 0), [1, 1, 1, 1, 1])] + ) + def test_process_counts_with_eigen_values(self, wire_order, expected_result): """Test process_counts method with eigen values.""" sample_mp = qml.sample(qml.Z(0)) counts = {"00": 2, "10": 3} - wire_order = qml.wires.Wires((0, 1)) + wire_order = qml.wires.Wires(wire_order) + + result = sample_mp.process_counts(counts, wire_order) + + assert np.array_equal(result, np.array(expected_result)) + + @pytest.mark.parametrize( + "wire_order, expected_result", + [ + ((0, 1, 2), [1, -1, -1, 1, 1]), + ((0, 2, 1), [1, 1, -1, -1, -1]), + ((1, 2, 0), [1, 1, -1, -1, -1]), + ((2, 0, 1), [1, -1, 1, -1, -1]), + ], + ) + def test_process_counts_with_eigen_values_multiple_wires(self, wire_order, expected_result): + """Test process_counts method with eigen values.""" + sample_mp = qml.sample(qml.Z(0) @ qml.Z(1)) + counts = {"000": 1, "101": 1, "011": 1, "110": 2} + wire_order = qml.wires.Wires(wire_order) result = sample_mp.process_counts(counts, wire_order) - assert np.array_equal(result, np.array([1, 1, -1, -1, -1])) + assert np.array_equal(result, np.array(expected_result)) def test_process_counts_with_inverted_wire_order(self): """Test process_counts method with inverted wire order.""" diff --git a/tests/ops/functions/conftest.py b/tests/ops/functions/conftest.py index db4f689e017..f73779cc6fc 100644 --- a/tests/ops/functions/conftest.py +++ b/tests/ops/functions/conftest.py @@ -22,7 +22,8 @@ import pytest import pennylane as qml -from pennylane.operation import Channel, Observable, Operation, Operator, StatePrepBase +from pennylane._deprecated_observable import Observable +from pennylane.operation import Channel, Operation, Operator, StatePrepBase from pennylane.ops.op_math.adjoint import Adjoint, AdjointObs, AdjointOperation, AdjointOpObs from pennylane.ops.op_math.pow import PowObs, PowOperation, PowOpObs from pennylane.templates.subroutines.trotter import TrotterizedQfunc diff --git a/tests/ops/functions/test_assert_valid.py b/tests/ops/functions/test_assert_valid.py index 65356d28c28..0bfaab2f1c4 100644 --- a/tests/ops/functions/test_assert_valid.py +++ b/tests/ops/functions/test_assert_valid.py @@ -343,9 +343,7 @@ def __init__(self, x, wires): def create_op_instance(c, str_wires=False): """Given an Operator class, create an instance of it.""" n_wires = c.num_wires - if n_wires == qml.operation.AllWires: - n_wires = 0 - elif n_wires == qml.operation.AnyWires: + if n_wires is None: n_wires = 1 wires = qml.wires.Wires(range(n_wires)) diff --git a/tests/ops/functions/test_equal.py b/tests/ops/functions/test_equal.py index 1bebfbe62cc..03e6b16d5ca 100644 --- a/tests/ops/functions/test_equal.py +++ b/tests/ops/functions/test_equal.py @@ -1841,7 +1841,8 @@ def test_adjoint_observable_with_non_adjoint(self): # if we get rid of Observable or stop having adjoint(Observable) be an Observable, # this test is no longer relevant. Ref: https://github.com/PennyLaneAI/pennylane/pull/7107/ - assert isinstance(adj_op, qml.operation.Observable) + with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning): + assert isinstance(adj_op, qml.operation.Observable) assert not qml.equal(adj_op, qml.exp(base, 2)) diff --git a/tests/ops/functions/test_generator.py b/tests/ops/functions/test_generator.py index 8fe72c512e0..d7f9e2c4b3a 100644 --- a/tests/ops/functions/test_generator.py +++ b/tests/ops/functions/test_generator.py @@ -357,7 +357,7 @@ def test_hamiltonian(self): """Test a generator that returns a Hamiltonian""" gen = qml.generator(HamiltonianOp, format="observable")(0.5, wires=[0, 1]) assert isinstance(gen, type(qml.Hamiltonian([], []))) - assert gen.compare(HamiltonianOp(0.5, wires=[0, 1]).generator()) + assert gen == HamiltonianOp(0.5, wires=[0, 1]).generator() def test_hermitian(self): """Test a generator that returns a Hermitian observable @@ -382,25 +382,25 @@ def test_observable_no_coeff(self): """Test a generator that returns an observable with no coefficient is correct""" gen = qml.generator(qml.PhaseShift, format="hamiltonian")(0.5, wires=0) assert isinstance(gen, qml.Hamiltonian) - assert gen.compare(qml.Hamiltonian([1.0], [qml.PhaseShift(0.5, wires=0).generator()])) + assert gen == qml.Hamiltonian([1.0], [qml.PhaseShift(0.5, wires=0).generator()]) def test_observable(self): """Test a generator that returns a single observable is correct""" gen = qml.generator(ObservableOp, format="hamiltonian")(0.5, wires=0) assert isinstance(gen, qml.Hamiltonian) - assert gen.compare(ObservableOp(0.5, wires=0).generator()) + assert gen == qml.Hamiltonian(*ObservableOp(0.5, wires=0).generator().terms()) def test_tensor_observable(self): """Test a generator that returns a tensor observable is correct""" gen = qml.generator(TensorOp, format="hamiltonian")(0.5, wires=[0, 1]) assert isinstance(gen, qml.Hamiltonian) - assert gen.compare(TensorOp(0.5, wires=[0, 1]).generator()) + assert gen == qml.Hamiltonian(*TensorOp(0.5, wires=[0, 1]).generator().terms()) def test_hamiltonian(self): """Test a generator that returns a Hamiltonian""" gen = qml.generator(HamiltonianOp, format="hamiltonian")(0.5, wires=[0, 1]) assert isinstance(gen, qml.Hamiltonian) - assert gen.compare(HamiltonianOp(0.5, wires=[0, 1]).generator()) + assert gen == HamiltonianOp(0.5, wires=[0, 1]).generator() def test_hermitian(self): """Test a generator that returns a Hermitian observable @@ -409,7 +409,7 @@ def test_hermitian(self): assert isinstance(gen, qml.Hamiltonian) expected = qml.pauli_decompose(HermitianOp.H, hide_identity=True) - assert gen.compare(expected) + assert gen == expected def test_sparse_hamiltonian(self): """Test a generator that returns a SparseHamiltonian observable @@ -418,7 +418,7 @@ def test_sparse_hamiltonian(self): assert isinstance(gen, qml.Hamiltonian) expected = qml.pauli_decompose(SparseOp.H.toarray(), hide_identity=True) - assert gen.compare(expected) + assert gen == expected def test_sum(self): """Test a generator that returns a Sum is correct""" @@ -429,7 +429,7 @@ def test_sum(self): [1.0, 0.5], [qml.PauliX(0) @ qml.Identity(1), qml.PauliX(0) @ qml.PauliY(1)] ) - assert gen.compare(expected) + assert gen == expected class TestArithmeticReturn: diff --git a/tests/ops/functions/test_iterative_qpe.py b/tests/ops/functions/test_iterative_qpe.py index 515044d0d7d..110d5a53e2e 100644 --- a/tests/ops/functions/test_iterative_qpe.py +++ b/tests/ops/functions/test_iterative_qpe.py @@ -267,7 +267,7 @@ def f(x): jaxpr = jax.make_jaxpr(f)(1.5) - dev = qml.device("default.qubit", wires=3, seed=seed) + dev = qml.device("default.qubit", wires=5, seed=seed) # hack for single-branch statistics samples = qml.math.vstack([dev.eval_jaxpr(jaxpr.jaxpr, jaxpr.consts, x) for _ in range(5000)]) diff --git a/tests/ops/op_math/test_adjoint.py b/tests/ops/op_math/test_adjoint.py index 75111401317..0676ad33b5a 100644 --- a/tests/ops/op_math/test_adjoint.py +++ b/tests/ops/op_math/test_adjoint.py @@ -48,7 +48,8 @@ def test_plain_operator(self): assert isinstance(op, Adjoint) assert isinstance(op, qml.operation.Operator) assert not isinstance(op, qml.operation.Operation) - assert not isinstance(op, qml.operation.Observable) + with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning): + assert not isinstance(op, qml.operation.Observable) assert not isinstance(op, AdjointOperation) # checking we can call `dir` without problems @@ -69,7 +70,8 @@ class CustomOp(qml.operation.Operation): assert isinstance(op, Adjoint) assert isinstance(op, qml.operation.Operator) assert isinstance(op, qml.operation.Operation) - assert not isinstance(op, qml.operation.Observable) + with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning): + assert not isinstance(op, qml.operation.Observable) assert isinstance(op, AdjointOperation) # check operation-specific properties made it into the mapping @@ -79,10 +81,11 @@ class CustomOp(qml.operation.Operation): def test_observable(self): """Test that when the base is an Observable, Adjoint will also inherit from Observable.""" - # pylint: disable=too-few-public-methods - class CustomObs(qml.operation.Observable): - num_wires = 1 - num_params = 0 + with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning): + # pylint: disable=too-few-public-methods + class CustomObs(qml.operation.Observable): + num_wires = 1 + num_params = 0 base = CustomObs(wires=0) ob = Adjoint(base) @@ -90,11 +93,13 @@ class CustomObs(qml.operation.Observable): assert isinstance(ob, Adjoint) assert isinstance(ob, qml.operation.Operator) assert not isinstance(ob, qml.operation.Operation) - assert isinstance(ob, qml.operation.Observable) + with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning): + assert isinstance(ob, qml.operation.Observable) assert not isinstance(ob, AdjointOperation) # Check some basic observable functionality - assert ob.compare(ob) + with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning): + assert ob.compare(ob) assert isinstance(1.0 * ob @ ob, qml.ops.Prod) # check the dir @@ -1005,9 +1010,10 @@ def test_observable(self): obs = adjoint(base) assert isinstance(obs, Adjoint) - assert isinstance(base, qml.operation.Observable) == isinstance( - obs, qml.operation.Observable - ) + with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning): + assert isinstance(base, qml.operation.Observable) == isinstance( + obs, qml.operation.Observable + ) assert obs.base is base def test_single_op_function(self): diff --git a/tests/ops/op_math/test_composite.py b/tests/ops/op_math/test_composite.py index eeadf019c2f..1368228ed78 100644 --- a/tests/ops/op_math/test_composite.py +++ b/tests/ops/op_math/test_composite.py @@ -279,6 +279,11 @@ def _is_method_with_no_argument(method): class TestMscMethods: """Test dunder and other visualizing methods.""" + def test_empty_repr(self): + """Test __repr__ on an empty composite op.""" + op = ValidOp() + assert repr(op) == "ValidOp()" + @pytest.mark.parametrize("ops_lst, op_rep", tuple((i, j) for i, j in zip(ops, ops_rep))) def test_repr(self, ops_lst, op_rep): """Test __repr__ method.""" diff --git a/tests/ops/op_math/test_controlled.py b/tests/ops/op_math/test_controlled.py index 895e9762424..999b69cb2fd 100644 --- a/tests/ops/op_math/test_controlled.py +++ b/tests/ops/op_math/test_controlled.py @@ -1456,7 +1456,7 @@ def test_controlled_single_scalar_multi_wire_ops(self, name): cls = getattr(qml, name) # Provide up to 6 wires and take as many as the class requires - # This assumes that the class does *not* have `num_wires=qml.operation.AnyWires` + # This assumes that the class does *not* have `num_wires=None` wires = ["wire0", 5, 41, "aux_wire", -1, 9][: cls.num_wires] base = cls(par, wires=wires) op = Controlled(base, "wire1") diff --git a/tests/ops/op_math/test_decompositions.py b/tests/ops/op_math/test_decompositions.py index 750122c7d51..6447f5cc566 100644 --- a/tests/ops/op_math/test_decompositions.py +++ b/tests/ops/op_math/test_decompositions.py @@ -1210,38 +1210,52 @@ def wrapped_decomposition(U): assert check_matrix_equivalence(U, jitted_matrix, atol=1e-7) -def test_two_qubit_decomposition_special_case_discontinuity(): - """Test that two_qubit_decomposition still provides accurate numbers at a special case.""" - - def make_unitary(theta1): - generator = ( - theta1 +# This was the routine used to generate the problematic matrix in the original +# bug report (see https://github.com/PennyLaneAI/pennylane/issues/5308) +def _make_unitary(theta1): + generator = ( + theta1 + / 2 + * ( + np.cos(0.2) / 2 * (np.array([[0, 0, 0, 0], [0, 0, 2, 0], [0, 2, 0, 0], [0, 0, 0, 0]])) + + np.sin(0.2) / 2 - * ( - np.cos(0.2) - / 2 - * (np.array([[0, 0, 0, 0], [0, 0, 2, 0], [0, 2, 0, 0], [0, 0, 0, 0]])) - + np.sin(0.2) - / 2 - * (np.array([[0, 0, 0, 0], [0, 0, 2j, 0], [0, -2j, 0, 0], [0, 0, 0, 0]])) - ) + * (np.array([[0, 0, 0, 0], [0, 0, 2j, 0], [0, -2j, 0, 0], [0, 0, 0, 0]])) ) + ) - def expm(val): - d, U = np.linalg.eigh(-1.0j * val) - return np.dot(U, np.dot(np.diag(np.exp(1.0j * d)), np.conj(U).T)) + def expm(val): + d, U = np.linalg.eigh(-1.0j * val) + return np.dot(U, np.dot(np.diag(np.exp(1.0j * d)), np.conj(U).T)) - mat = expm(-1j * generator) + mat = expm(-1j * generator) - assert np.allclose( - np.dot(np.transpose(np.conj(mat)), mat), np.eye(len(mat)) - ), "mat is not unitary" + assert np.allclose( + np.dot(np.transpose(np.conj(mat)), mat), np.eye(len(mat)) + ), "mat is not unitary" - return mat + return mat + + +@pytest.mark.parametrize( + "U", + [ + _make_unitary(np.pi / 2), + np.array( + [ + [-1, 0, 0, 0], + [0, 1 / np.sqrt(2), 1 / np.sqrt(2), 0], + [0, 1 / np.sqrt(2), -1 / np.sqrt(2), 0], + [0, 0, 0, -1], + ] + ), + ], +) +def test_two_qubit_decomposition_special_case_discontinuity(U): + """Test that two_qubit_decomposition still provides accurate numbers at a special case.""" - mat = make_unitary(np.pi / 2) - decomp_mat = qml.matrix(two_qubit_decomposition, wire_order=(0, 1))(mat, wires=(0, 1)) - assert qml.math.allclose(mat, decomp_mat) + decomp_mat = qml.matrix(two_qubit_decomposition, wire_order=(0, 1))(U, wires=(0, 1)) + assert qml.math.allclose(U, decomp_mat) class TestTwoQubitDecompositionWarnings: diff --git a/tests/ops/op_math/test_exp.py b/tests/ops/op_math/test_exp.py index 5abead84595..1b2f76ac3b8 100644 --- a/tests/ops/op_math/test_exp.py +++ b/tests/ops/op_math/test_exp.py @@ -20,8 +20,6 @@ import pennylane as qml from pennylane import numpy as np from pennylane.operation import ( - AllWires, - AnyWires, DecompositionUndefinedError, GeneratorUndefinedError, ParameterFrequenciesUndefinedError, @@ -469,11 +467,7 @@ def test_generator_decomposition(self, op_name, str_wires): phi = 1.23 - wires = ( - [0, 1, 2] - if op_class.num_wires in {AnyWires, AllWires} - else list(range(op_class.num_wires)) - ) + wires = [0, 1, 2] if op_class.num_wires is None else list(range(op_class.num_wires)) if str_wires: alphabet = ("a", "b", "c", "d", "e", "f", "g") wires = [alphabet[w] for w in wires] diff --git a/tests/ops/op_math/test_linear_combination.py b/tests/ops/op_math/test_linear_combination.py index d80923c3238..284c29b9113 100644 --- a/tests/ops/op_math/test_linear_combination.py +++ b/tests/ops/op_math/test_linear_combination.py @@ -111,7 +111,7 @@ ), ( qml.ops.LinearCombination([-1, 1, 1], [X(0) @ qml.Identity(1), X(0), X(1)]), - qml.ops.LinearCombination([1], [X(1)]), + qml.ops.LinearCombination([0, 1], [qml.X(0), X(1)]), ), ( qml.ops.LinearCombination( @@ -137,18 +137,18 @@ # Simplifies to zero LinearCombination ( qml.ops.LinearCombination([1, -0.5, -0.5], [X(0) @ qml.Identity(1), X(0), X(0)]), - qml.ops.LinearCombination([], []), + qml.ops.LinearCombination([0.0], [qml.X(0)]), ), ( qml.ops.LinearCombination( [1, -1], [X(4) @ qml.Identity(0) @ X(1), X(4) @ X(1)], ), - qml.ops.LinearCombination([], []), + qml.ops.LinearCombination([0.0], [qml.X(4) @ qml.X(1)]), ), ( qml.ops.LinearCombination([0], [qml.Identity(0)]), - qml.ops.LinearCombination([], []), + qml.ops.LinearCombination([0], [qml.I(0)]), ), ] @@ -282,23 +282,33 @@ ( qml.ops.LinearCombination([1, 1.2, 0.1], [X(0), Z(1), X(2)]), X(0) @ qml.Identity(1), - qml.ops.LinearCombination([1.2, 0.1], [Z(1), X(2)]), + qml.ops.LinearCombination( + [1, 1.2, 0.1, 1.0], [qml.X(0), Z(1), qml.X(2), -1 * (qml.X(0) @ qml.I(1))] + ), ), ( qml.ops.LinearCombination([1, 1.2, 0.1], [X("b"), Z(3.1), X(1.6)]), X("b") @ qml.Identity(1), - qml.ops.LinearCombination([1.2, 0.1], [Z(3.1), X(1.6)]), + qml.ops.LinearCombination( + [1, 1.2, 0.1, 1], [X("b"), Z(3.1), X(1.6), -1 * (qml.X("b") @ qml.I(1))] + ), ), # The result is the zero LinearCombination ( qml.ops.LinearCombination([1, 1.2, 0.1], [X(0), Z(1), X(2)]), qml.ops.LinearCombination([1, 1.2, 0.1], [X(0), Z(1), X(2)]), - qml.ops.LinearCombination([], []), + qml.ops.LinearCombination( + [1, 1.2, 0.1, 1], + [X(0), Z(1), X(2), -1 * qml.ops.LinearCombination([1, 1.2, 0.1], [X(0), Z(1), X(2)])], + ), ), ( qml.ops.LinearCombination([1.0, 2.0], [X(4), Z(2)]), qml.ops.LinearCombination([1.0, 2.0], [X(4), Z(2)]), - qml.ops.LinearCombination([], []), + qml.ops.LinearCombination( + [1.0, 2.0, 1.0], + [qml.X(4), qml.Z(2), -1 * qml.ops.LinearCombination([1.0, 2.0], [X(4), Z(2)])], + ), ), # Case where arguments coeffs and ops to the LinearCombination are iterables other than lists ( @@ -585,10 +595,10 @@ def test_integer_coefficients(self): H1, H2, true_res = ( qml.ops.LinearCombination([1, 2], [X(4), Z(2)]), # not failing with float coeffs qml.ops.LinearCombination([1, 2], [X(4), Z(2)]), - qml.ops.LinearCombination([], []), + qml.ops.LinearCombination([0, 0], [qml.X(4), qml.Z(2)]), ) res = H1 - H2 - assert res.compare(true_res) + qml.assert_equal(qml.simplify(res), true_res) # pylint: disable=protected-access @pytest.mark.parametrize("coeffs, ops", valid_LinearCombinations) @@ -670,7 +680,7 @@ def test_LinearCombination_name(self): def test_simplify(self, old_H, new_H): """Tests the simplify method""" old_H = old_H.simplify() - assert old_H.compare(new_H) + qml.assert_equal(old_H, new_H) def test_simplify_while_queueing(self): """Tests that simplifying a LinearCombination in a tape context @@ -699,12 +709,14 @@ def test_simplify_while_queueing(self): @pytest.mark.parametrize("H, op", COMPARE_WITH_OPS) def test_compare_to_simple_ops(self, H, op): - assert H.compare(op) + with pytest.raises(qml.exceptions.PennyLaneDeprecationWarning): + assert H.compare(op) def test_compare_raises_error(self): op = qml.ops.LinearCombination([], []) - with pytest.raises(ValueError, match="Can only compare a LinearCombination"): - _ = op.compare(0) + with pytest.raises(qml.exceptions.PennyLaneDeprecationWarning): + with pytest.raises(ValueError, match="Can only compare a LinearCombination"): + op.compare(0) @pytest.mark.xfail def test_compare_gell_mann(self): @@ -719,50 +731,52 @@ def test_compare_gell_mann(self): [1], [qml.GellMann(wires=2, index=1) @ qml.GellMann(wires=1, index=3)] ) - assert H1.compare(qml.GellMann(wires=2, index=2)) is True - assert H1.compare(qml.GellMann(wires=2, index=1)) is False - assert H1.compare(H3) is False - assert H2.compare(qml.GellMann(wires=2, index=1) @ qml.GellMann(wires=1, index=2)) is True - assert H2.compare(qml.GellMann(wires=2, index=2) @ qml.GellMann(wires=1, index=2)) is False - assert H2.compare(H4) is False + assert H1.compare(qml.GellMann(wires=2, index=2)) + assert H1.compare(qml.GellMann(wires=2, index=1)) + assert H1.compare(H3) + assert H2.compare(qml.GellMann(wires=2, index=1) @ qml.GellMann(wires=1, index=2)) + assert not H2.compare(qml.GellMann(wires=2, index=2) @ qml.GellMann(wires=1, index=2)) + assert not H2.compare(H4) def test_LinearCombination_equal_error(self): """Tests that the correct error is raised when compare() is called on invalid type""" H = qml.ops.LinearCombination([1], [Z(0)]) - with pytest.raises( - ValueError, - match=r"Can only compare a LinearCombination, and a LinearCombination/Observable/Tensor.", - ): - H.compare([[1, 0], [0, -1]]) + with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning): + with pytest.raises( + ValueError, + match=r"Can only compare a LinearCombination and an Operator.", + ): + _ = H.compare([[1, 0], [0, -1]]) @pytest.mark.parametrize(("H1", "H2", "res"), equal_LinearCombinations) def test_LinearCombination_equal(self, H1, H2, res): """Tests that equality can be checked between LinearCombinations""" - assert H1.compare(H2) == res + with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning): + assert H1.compare(H2) == res @pytest.mark.parametrize(("H1", "H2", "H"), add_LinearCombinations) def test_LinearCombination_add(self, H1, H2, H): """Tests that LinearCombinations are added correctly""" res = H1 + H2 assert isinstance(res, LinearCombination) - assert H.compare(res) + qml.assert_equal(H, qml.simplify(res)) @pytest.mark.parametrize("H", add_zero_LinearCombinations) def test_LinearCombination_add_zero(self, H): """Tests that LinearCombinations can be added to zero""" - assert H.compare(H + 0) - assert H.compare(0 + H) - assert H.compare(H + 0.0) - assert H.compare(0.0 + H) - assert H.compare(H + 0e1) - assert H.compare(0e1 + H) + assert H == (H + 0) + assert H == (0 + H) + assert H == (H + 0.0) + assert H == (0.0 + H) + assert H == (H + 0e1) + assert H == (0e1 + H) @pytest.mark.parametrize(("coeff", "H", "res"), mul_LinearCombinations) def test_LinearCombination_mul(self, coeff, H, res): """Tests that scalars and LinearCombinations are multiplied correctly""" - assert res.compare(coeff * H) - assert res.compare(H * coeff) + assert res == (coeff * H) + assert res == (H * coeff) def test_LinearCombination_mul_coeff_cast(self): """Test that the coefficients are correct when the type of the existing @@ -773,7 +787,7 @@ def test_LinearCombination_mul_coeff_cast(self): @pytest.mark.parametrize(("H1", "H2", "H"), sub_LinearCombinations) def test_LinearCombination_sub(self, H1, H2, H): """Tests that LinearCombinations are subtracted correctly""" - assert H.compare(H1 - H2) + qml.assert_equal(H, H1 - H2) def test_LinearCombination_tensor_matmul(self): """Tests that a LinearCombination can be multiplied by a tensor.""" @@ -781,14 +795,7 @@ def test_LinearCombination_tensor_matmul(self): t = Z(1) @ Z(2) out = H @ t - expected = qml.ops.LinearCombination( - [1, 1], - [ - X(0) @ Z(1) @ Z(2), - Y(0) @ Z(1) @ Z(2), - ], - ) - assert expected.compare(out) + qml.assert_equal(qml.prod(H, t), out) def test_LinearCombination_matmul_overlapping_wires_raises_error(self): """Test that an error is raised when attempting to multiply two @@ -806,17 +813,17 @@ def test_matmul_with_non_pauli_op(self): res = H @ op assert res.pauli_rep is None - assert res.compare(qml.ops.LinearCombination([0.5], [X(0) @ qml.Hadamard(0)])) + assert res == (qml.ops.LinearCombination([0.5], [X(0) @ qml.Hadamard(0)])) @pytest.mark.parametrize(("H1", "H2", "H"), matmul_LinearCombinations) def test_LinearCombination_matmul(self, H1, H2, H): """Tests that LinearCombinations are tensored correctly""" - assert H.compare(H1 @ H2) + assert H == (H1 @ H2) @pytest.mark.parametrize(("H1", "H2", "H"), rmatmul_LinearCombinations) def test_LinearCombination_rmatmul(self, H1, H2, H): """Tests that LinearCombinations are tensored correctly when using __rmatmul__""" - assert H.compare(H1 @ H2) + assert H == (H1 @ H2) def test_arithmetic_errors(self): """Tests that the arithmetic operations thrown the correct errors""" @@ -962,7 +969,7 @@ def test_simplify(self, coeffs): H1 = qml.ops.LinearCombination(coeffs, [X(0), Z(1)]) H2 = qml.ops.LinearCombination(coeffs, [X(0), qml.Identity(0) @ Z(1)]) H2 = H2.simplify() - assert H1.compare(H2) + assert H1 == (H2) assert qml.math.allclose(H1.data, H2.data) # TODO: increase coverage @@ -986,7 +993,7 @@ def test_LinearCombination_equal(self): obs2 = [Y(1), X(0)] H2 = qml.ops.LinearCombination(coeffs2, obs2) - assert H1.compare(H2) + assert H1 == (H2) def test_LinearCombination_add(self): """Tests that LinearCombinations are added correctly""" @@ -1000,7 +1007,7 @@ def test_LinearCombination_add(self): coeffs_expected = tf.Variable([1.0, -2.0]) H = qml.ops.LinearCombination(coeffs_expected, obs) - assert H.compare(H1 + H2) + assert H == (H1 + H2) def test_LinearCombination_sub(self): """Tests that LinearCombinations are subtracted correctly""" @@ -1014,7 +1021,7 @@ def test_LinearCombination_sub(self): coeffs_expected = tf.constant([0.5, -1.5]) H = qml.ops.LinearCombination(coeffs_expected, obs) - assert H.compare(H1 - H2) + assert H == (H1 - H2) def test_LinearCombination_matmul(self): """Tests that LinearCombinations are tensored correctly""" @@ -1036,7 +1043,7 @@ def test_LinearCombination_matmul(self): ] H = qml.ops.LinearCombination(coeffs_expected, obs_expected) - assert H.compare(H1 @ H2) + assert H == (H1 @ H2) @pytest.mark.torch @@ -1054,7 +1061,7 @@ def test_LinearCombination_equal(self): obs2 = [Y(1), X(0)] H2 = qml.ops.LinearCombination(coeffs2, obs2) - assert H1.compare(H2) + assert H1 == (H2) def test_LinearCombination_add(self): """Tests that LinearCombinations are added correctly""" @@ -1068,7 +1075,7 @@ def test_LinearCombination_add(self): coeffs_expected = torch.tensor([1.0, -2.0]) H = qml.ops.LinearCombination(coeffs_expected, obs) - assert H.compare(H1 + H2) + assert H == (H1 + H2) def test_LinearCombination_sub(self): """Tests that LinearCombinations are subtracted correctly""" @@ -1082,10 +1089,10 @@ def test_LinearCombination_sub(self): coeffs_expected = torch.tensor([0.5, -1.6]) H = qml.ops.LinearCombination(coeffs_expected, obs) - assert H.compare(H1 - H2) + assert H == (H1 - H2) H1 -= H2 - assert H.compare(H1) + assert H == (H1) def test_LinearCombination_matmul(self): """Tests that LinearCombinations are tensored correctly""" @@ -1107,7 +1114,7 @@ def test_LinearCombination_matmul(self): ] H = qml.ops.LinearCombination(coeffs_expected, obs_expected) - assert H.compare(H1 @ H2) + assert H == (H1 @ H2) @pytest.mark.autograd @@ -1125,7 +1132,7 @@ def test_LinearCombination_equal(self): obs2 = [Y(1), X(0)] H2 = qml.ops.LinearCombination(coeffs2, obs2) - assert H1.compare(H2) + assert H1 == (H2) def test_LinearCombination_add(self): """Tests that LinearCombinations are added correctly""" @@ -1139,7 +1146,7 @@ def test_LinearCombination_add(self): coeffs_expected = pnp.array([1.0, -2.0]) H = qml.ops.LinearCombination(coeffs_expected, obs) - assert H.compare(H1 + H2) + assert H == (H1 + H2) def test_LinearCombination_sub(self): """Tests that LinearCombinations are subtracted correctly""" @@ -1153,7 +1160,7 @@ def test_LinearCombination_sub(self): coeffs_expected = pnp.array([0.5, -1.5]) H = qml.ops.LinearCombination(coeffs_expected, obs) - assert H.compare(H1 - H2) + assert H == (H1 - H2) def test_LinearCombination_matmul(self): """Tests that LinearCombinations are tensored correctly""" @@ -1174,7 +1181,7 @@ def test_LinearCombination_matmul(self): ] H = qml.ops.LinearCombination(coeffs_expected, obs_expected) - assert H.compare(H1 @ H2) + assert H == (H1 @ H2) class TestLinearCombinationSparseMatrix: @@ -1342,7 +1349,7 @@ def test_LinearCombination_equal(self): obs2 = [Y(1), X(0)] H2 = qml.ops.LinearCombination(coeffs2, obs2) - assert H1.compare(H2) + assert H1 == (H2) def test_LinearCombination_add(self): """Tests that LinearCombinations are added correctly""" @@ -1356,7 +1363,7 @@ def test_LinearCombination_add(self): coeffs_expected = jnp.array([1.0, -2.0]) H = qml.ops.LinearCombination(coeffs_expected, obs) - assert H.compare(H1 + H2) + assert H == (H1 + H2) def test_LinearCombination_sub(self): """Tests that LinearCombinations are subtracted correctly""" @@ -1371,10 +1378,10 @@ def test_LinearCombination_sub(self): coeffs_expected = jnp.array([0.5, -1.6]) H = qml.ops.LinearCombination(coeffs_expected, obs) - assert H.compare(H1 - H2) + assert H == (H1 - H2) H1 -= H2 - assert H.compare(H1) + assert H == (H1) def test_LinearCombination_matmul(self): """Tests that LinearCombinations are tensored correctly""" @@ -1396,7 +1403,7 @@ def test_LinearCombination_matmul(self): ] H = qml.ops.LinearCombination(coeffs_expected, obs_expected) - assert H.compare(H1 @ H2) + assert H == (H1 @ H2) class TestGrouping: diff --git a/tests/ops/op_math/test_pow_op.py b/tests/ops/op_math/test_pow_op.py index 21ef622907b..6a0388797a1 100644 --- a/tests/ops/op_math/test_pow_op.py +++ b/tests/ops/op_math/test_pow_op.py @@ -116,7 +116,8 @@ def test_plain_operator(self, power_method): assert isinstance(op, Pow) assert isinstance(op, qml.operation.Operator) assert not isinstance(op, qml.operation.Operation) - assert not isinstance(op, qml.operation.Observable) + with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning): + assert not isinstance(op, qml.operation.Observable) assert not isinstance(op, PowOperation) # checking we can call `dir` without problems @@ -136,7 +137,8 @@ class CustomOp(qml.operation.Operation): assert isinstance(op, Pow) assert isinstance(op, qml.operation.Operator) assert isinstance(op, qml.operation.Operation) - assert not isinstance(op, qml.operation.Observable) + with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning): + assert not isinstance(op, qml.operation.Observable) assert isinstance(op, PowOperation) # check operation-specific properties made it into the mapping @@ -146,9 +148,11 @@ class CustomOp(qml.operation.Operation): def test_observable(self, power_method): """Test that when the base is an Observable, Pow will also inherit from Observable.""" - class CustomObs(qml.operation.Observable): - num_wires = 1 - num_params = 0 + with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning): + + class CustomObs(qml.operation.Observable): + num_wires = 1 + num_params = 0 base = CustomObs(wires=0) ob: Pow = power_method(base=base, z=-1.2) @@ -159,7 +163,8 @@ class CustomObs(qml.operation.Observable): assert not isinstance(ob, PowOperation) # Check some basic observable functionality - assert ob.compare(ob) + with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning): + assert ob.compare(ob) # check the dir assert "grad_recipe" not in dir(ob) diff --git a/tests/ops/op_math/test_prod.py b/tests/ops/op_math/test_prod.py index ac05642cec2..81f7ac17fd3 100644 --- a/tests/ops/op_math/test_prod.py +++ b/tests/ops/op_math/test_prod.py @@ -23,7 +23,7 @@ import pennylane as qml import pennylane.numpy as qnp from pennylane import math -from pennylane.operation import AnyWires, MatrixUndefinedError, Operator +from pennylane.operation import MatrixUndefinedError, Operator from pennylane.ops.op_math.prod import Prod, _swappable_ops, prod from pennylane.wires import Wires @@ -516,6 +516,11 @@ def test_prod_fails_with_non_callable_arg(self): prod(1) +def test_empty_repr(): + """Test that an empty prod still has a repr that indicates it's a prod.""" + assert repr(Prod()) == "Prod()" + + # pylint: disable=too-many-public-methods class TestMatrix: """Test matrix-related methods.""" @@ -534,8 +539,8 @@ def test_non_parametric_ops_two_terms( true_mat = mat1 @ mat2 prod_op = Prod( - op1(wires=0 if op1.num_wires is AnyWires else range(op1.num_wires)), - op2(wires=0 if op2.num_wires is AnyWires else range(op2.num_wires)), + op1(wires=0 if op1.num_wires is None else range(op1.num_wires)), + op2(wires=0 if op2.num_wires is None else range(op2.num_wires)), ) prod_mat = prod_op.matrix() diff --git a/tests/ops/op_math/test_sprod.py b/tests/ops/op_math/test_sprod.py index d07ae4dd826..4c651e43c1e 100644 --- a/tests/ops/op_math/test_sprod.py +++ b/tests/ops/op_math/test_sprod.py @@ -23,7 +23,7 @@ import pennylane as qml import pennylane.numpy as qnp from pennylane import math -from pennylane.operation import AnyWires, DecompositionUndefinedError, MatrixUndefinedError +from pennylane.operation import DecompositionUndefinedError, MatrixUndefinedError from pennylane.ops.op_math import Prod, SProd, Sum, s_prod from pennylane.wires import Wires @@ -398,7 +398,7 @@ def test_various_ops(self, scalar, op, mat): params = range(op.num_params) sprod_op = SProd( - scalar, op(*params, wires=0 if op.num_wires is AnyWires else range(op.num_wires)) + scalar, op(*params, wires=0 if op.num_wires is None else range(op.num_wires)) ) sprod_mat = sprod_op.matrix() diff --git a/tests/ops/op_math/test_sum.py b/tests/ops/op_math/test_sum.py index f463debf3f1..cf36500b354 100644 --- a/tests/ops/op_math/test_sum.py +++ b/tests/ops/op_math/test_sum.py @@ -24,7 +24,7 @@ import pennylane as qml import pennylane.numpy as qnp from pennylane import X, Y, Z, math -from pennylane.operation import AnyWires, MatrixUndefinedError, Operator +from pennylane.operation import MatrixUndefinedError, Operator from pennylane.ops.op_math import Prod, Sum from pennylane.wires import Wires @@ -302,6 +302,7 @@ def test_eigen_caching(self): assert np.allclose(eig_vecs, cached_vecs) SUM_REPR = ( + (qml.sum(), "Sum()"), (qml.sum(X(0), Y(1), Z(2)), "X(0) + Y(1) + Z(2)"), (X(0) + X(1) + X(2), "X(0) + X(1) + X(2)"), (0.5 * X(0) + 0.7 * X(1), "0.5 * X(0) + 0.7 * X(1)"), @@ -354,8 +355,8 @@ def test_non_parametric_ops_two_terms( true_mat = mat1 + mat2 sum_op = Sum( - op1(wires=0 if op1.num_wires is AnyWires else range(op1.num_wires)), - op2(wires=0 if op2.num_wires is AnyWires else range(op2.num_wires)), + op1(wires=0 if op1.num_wires is None else range(op1.num_wires)), + op2(wires=0 if op2.num_wires is None else range(op2.num_wires)), ) sum_mat = sum_op.matrix() diff --git a/tests/ops/qubit/test_attributes.py b/tests/ops/qubit/test_attributes.py index 24489258286..49fc2a5ce7b 100644 --- a/tests/ops/qubit/test_attributes.py +++ b/tests/ops/qubit/test_attributes.py @@ -21,7 +21,6 @@ from scipy.stats import unitary_group import pennylane as qml -from pennylane.operation import AnyWires from pennylane.ops.qubit.attributes import Attribute, has_unitary_generator # Dummy attribute @@ -189,7 +188,7 @@ def test_single_scalar_multi_wire_ops(self, name): cls = getattr(qml, name) # Provide up to 6 wires and take as many as the class requires - # This assumes that the class does *not* have `num_wires=qml.operation.AnyWires` + # This assumes that the class does *not* have `num_wires=qml.operation.None` wires = ["wire0", 5, 41, "aux_wire", -1, 9][: cls.num_wires] op = cls(par, wires=wires) @@ -485,9 +484,9 @@ def test_generator_unitarity(self, entry): attribute are unitary up to a factor of 2.""" op_class = getattr(qml, entry) phi = 1.23 - wires = [0, 1, 2] if op_class.num_wires is AnyWires else list(range(op_class.num_wires)) + wires = [0, 1, 2] if op_class.num_wires is None else list(range(op_class.num_wires)) if op_class is qml.PauliRot: - op = op_class(phi, pauli_word="XYZ", wires=wires) # PauliRot has num_wires == AnyWires + op = op_class(phi, pauli_word="XYZ", wires=wires) # PauliRot has num_wires == None elif op_class is qml.PCPhase: op = op_class(phi, dim=(2 ** len(wires) - 1), wires=wires) else: @@ -509,9 +508,9 @@ def test_no_missing_entries(self, entry): if not op_class.has_generator: pytest.skip("Operator does not have a generator") phi = 1.23 - wires = [0, 1, 2] if op_class.num_wires is AnyWires else list(range(op_class.num_wires)) + wires = [0, 1, 2] if op_class.num_wires is None else list(range(op_class.num_wires)) if op_class is qml.PauliRot: - op = op_class(phi, pauli_word="XYZ", wires=wires) # PauliRot has num_wires == AnyWires + op = op_class(phi, pauli_word="XYZ", wires=wires) # PauliRot has num_wires == None elif op_class is qml.PCPhase: op = op_class(phi, dim=(2 ** len(wires) - 1), wires=wires) else: diff --git a/tests/ops/qubit/test_non_parametric_ops.py b/tests/ops/qubit/test_non_parametric_ops.py index 7f9d929c3b5..772f217771b 100644 --- a/tests/ops/qubit/test_non_parametric_ops.py +++ b/tests/ops/qubit/test_non_parametric_ops.py @@ -20,6 +20,7 @@ import numpy as np import pytest +import scipy as sp from gate_data import ( CCZ, CH, @@ -45,7 +46,6 @@ from scipy.stats import unitary_group import pennylane as qml -from pennylane.operation import AnyWires from pennylane.wires import Wires # Non-parametrized operations and their matrix representation @@ -123,14 +123,14 @@ class TestOperations: @pytest.mark.parametrize("op_cls, _", NON_PARAMETRIZED_OPERATIONS) def test_op_copy(self, op_cls, _, tol): """Tests that copied nonparametrized ops function as expected""" - op = op_cls(wires=0 if op_cls.num_wires is AnyWires else range(op_cls.num_wires)) + op = op_cls(wires=0 if op_cls.num_wires is None else range(op_cls.num_wires)) copied_op = copy.copy(op) np.testing.assert_allclose(op.matrix(), copied_op.matrix(), atol=tol) @pytest.mark.parametrize("ops, mat", NON_PARAMETRIZED_OPERATIONS) def test_matrices(self, ops, mat, tol): """Test matrices of non-parametrized operations are correct""" - op = ops(wires=0 if ops.num_wires is AnyWires else range(ops.num_wires)) + op = ops(wires=0 if ops.num_wires is None else range(ops.num_wires)) res_static = op.compute_matrix() res_dynamic = op.matrix() assert np.allclose(res_static, mat, atol=tol, rtol=0) @@ -827,7 +827,6 @@ def test_repr(self): qml.PauliZ(0), qml.Hadamard("a"), qml.SWAP(wires=(0, 1)), - qml.ISWAP(wires=(0, 1)), qml.ECR(wires=(0, 1)), # Controlled operations qml.CNOT(wires=(0, 1)), @@ -919,16 +918,37 @@ def test_pauliz_general_power(self, n): assert op_pow[0].__class__ is qml.PhaseShift assert qml.math.allclose(op_pow[0].data[0], np.pi * (n % 2)) - @pytest.mark.parametrize("n", (0.5, 2.5, -1.5)) - def test_ISWAP_sqaure_root(self, n): - """Test that SISWAP is the square root of ISWAP.""" - op = qml.ISWAP(wires=(0, 1)) - - assert op.pow(n)[0].__class__ is qml.SISWAP + @pytest.mark.parametrize( + "n, expected", + [ + (0, []), + (4, []), + (-4, []), + (-3, [qml.ISWAP(wires=(0, 1))]), + (5, [qml.ISWAP(wires=(0, 1))]), + (0.5, [qml.SISWAP(wires=(0, 1))]), + (4.5, [qml.SISWAP(wires=(0, 1))]), + (2, [qml.Z(0), qml.Z(1)]), + (-2, [qml.Z(0), qml.Z(1)]), + (6, [qml.Z(0), qml.Z(1)]), + ], + ) + def test_ISWAP_powers(self, n, expected): + """Check that the special powers of ISWAP are correct.""" - sqrt_mat = qml.matrix(op.pow, wire_order=[0, 1])(n) - sqrt_mat_squared = qml.math.linalg.matrix_power(sqrt_mat, 2) - assert qml.math.allclose(sqrt_mat_squared, qml.matrix(op)) + op = qml.ISWAP(wires=(0, 1)) + op_mat = qml.matrix(op) + pow_ops = op.pow(n) + assert pow_ops == expected + if not pow_ops: + pow_ops.append(qml.I(wires=(0, 1))) + mat = qml.matrix(qml.prod(*pow_ops), wire_order=[0, 1]) + expected = ( + qml.math.linalg.matrix_power(op_mat, n) + if isinstance(n, int) + else sp.linalg.fractional_matrix_power(op_mat, n) + ) + assert qml.math.allclose(mat, expected) @pytest.mark.parametrize("offset", (0, 4, -4)) def test_S_pow(self, offset): @@ -972,17 +992,28 @@ def test_SX_pow(self, offset): with pytest.raises(qml.operation.PowUndefinedError): op.pow(2.43 + offset) - @pytest.mark.parametrize("offset", (0, 4, -4)) - def test_SISWAP_pow(self, offset): - """Test powers of the SISWAP operator""" - op = qml.SISWAP(wires=("b", "c")) - - assert len(op.pow(0 + offset)) == 0 - assert op.pow(1 + offset)[0].__class__ is qml.SISWAP - assert op.pow(2 + offset)[0].__class__ is qml.ISWAP + @pytest.mark.parametrize( + "n, expected", + [ + (0, []), + (8, []), + (9, [qml.SISWAP(wires=(0, 1))]), + (2, [qml.ISWAP(wires=(0, 1))]), + (4, [qml.Z(0), qml.Z(1)]), + ], + ) + def test_SISWAP_powers(self, n, expected): + """Check that the special powers of SISWAP are correct.""" - with pytest.raises(qml.operation.PowUndefinedError): - op.pow(2.34 + offset) + op = qml.SISWAP(wires=(0, 1)) + op_mat = qml.matrix(op) + pow_ops = op.pow(n) + assert pow_ops == expected + if not pow_ops: + pow_ops.append(qml.I(wires=(0, 1))) + mat = qml.matrix(qml.prod(*pow_ops), wire_order=[0, 1]) + expected = qml.math.linalg.matrix_power(op_mat, n) + assert qml.math.allclose(mat, expected) @pytest.mark.parametrize("op", (qml.WireCut(0), qml.Barrier(0))) @pytest.mark.parametrize("n", (2, 0.123, -2.3)) diff --git a/tests/ops/qubit/test_parametric_ops.py b/tests/ops/qubit/test_parametric_ops.py index 01959a5076c..7d6f0d59e53 100644 --- a/tests/ops/qubit/test_parametric_ops.py +++ b/tests/ops/qubit/test_parametric_ops.py @@ -245,7 +245,7 @@ class TestParameterFrequencies: @pytest.mark.parametrize("op", PARAMETRIZED_OPERATIONS) def test_parameter_frequencies_match_generator(self, op, tol): - if not qml.operation.has_gen(op): + if not op.has_generator: pytest.skip(f"Operation {op.name} does not have a generator defined to test against.") gen = op.generator() @@ -3660,7 +3660,7 @@ def test_simplify_to_identity(self, op): if op == qml.U2: pytest.skip("U2 gate does not simplify to Identity") - num_wires = op.num_wires if op.num_wires is not qml.operation.AnyWires else 2 + num_wires = op.num_wires if op.num_wires is not None else 2 if op == qml.PCPhase: unsimplified_op = op(*([0] * op.num_params), dim=2, wires=range(num_wires)) diff --git a/tests/ops/qubit/test_qchem_ops.py b/tests/ops/qubit/test_qchem_ops.py index a95dfe60a6f..e7bb284735b 100644 --- a/tests/ops/qubit/test_qchem_ops.py +++ b/tests/ops/qubit/test_qchem_ops.py @@ -49,7 +49,7 @@ class TestParameterFrequencies: @pytest.mark.parametrize("op", PARAMETRIZED_QCHEM_OPERATIONS) def test_parameter_frequencies_match_generator(self, op, tol): - if not qml.operation.has_gen(op): + if not op.has_generator: pytest.skip(f"Operation {op.name} does not have a generator defined to test against.") gen = op.generator() diff --git a/tests/ops/qutrit/test_qutrit_parametric_ops.py b/tests/ops/qutrit/test_qutrit_parametric_ops.py index 60c962ec257..12aefcc6c9d 100644 --- a/tests/ops/qutrit/test_qutrit_parametric_ops.py +++ b/tests/ops/qutrit/test_qutrit_parametric_ops.py @@ -101,7 +101,7 @@ class TestParameterFrequencies: @pytest.mark.parametrize("op", PARAMETRIZED_OPERATIONS) def test_parameter_frequencies_match_generator(self, op, tol): """Check that parameter frequencies of parametrized operations are defined correctly.""" - if not qml.operation.has_gen(op): + if not op.has_generator: pytest.skip(f"Operation {op.name} does not have a generator defined to test against.") gen = op.generator() diff --git a/tests/ops/test_cv_ops.py b/tests/ops/test_cv_ops.py index 12a2744fea3..474562d6aa7 100644 --- a/tests/ops/test_cv_ops.py +++ b/tests/ops/test_cv_ops.py @@ -20,7 +20,6 @@ import pytest from pennylane import numpy as np -from pennylane.operation import AnyWires from pennylane.ops import cv from pennylane.wires import Wires @@ -217,10 +216,10 @@ def test_heisenberg_transformation_nongaussian(self): (cv.SqueezedState(0.1, 0.2, wires=0), 2, 1, "F"), (cv.DisplacedSqueezedState(0.1, 0.2, 0.3, 0.4, wires=0), 4, 1, "F"), (cv.ThermalState(0.1, wires=0), 1, 1, "F"), - (cv.GaussianState(0.1, 0.2, wires=(0, 1, 2, 3, 4)), 2, AnyWires, "F"), + (cv.GaussianState(0.1, 0.2, wires=(0, 1, 2, 3, 4)), 2, None, "F"), (cv.FockState(1, wires=0), 1, 1, None), - (cv.FockStateVector([0, 0, 1, 0], wires=0), 1, AnyWires, "F"), - (cv.FockDensityMatrix(np.eye(2), wires=0), 1, AnyWires, "F"), + (cv.FockStateVector([0, 0, 1, 0], wires=0), 1, None, "F"), + (cv.FockDensityMatrix(np.eye(2), wires=0), 1, None, "F"), (cv.CatState(0.1, 0.2, 0.3, wires=0), 3, 1, "F"), ] diff --git a/tests/ops/test_meta.py b/tests/ops/test_meta.py index 2449a5ccb1a..2b0efcd2ce3 100644 --- a/tests/ops/test_meta.py +++ b/tests/ops/test_meta.py @@ -251,6 +251,12 @@ def test_snapshot_no_empty_wire_list_error(self): snapshot = qml.Snapshot() assert isinstance(snapshot, qml.Snapshot) + def test_shots_none_for_no_measurement(self): + """Test that the shots become None if no measurement is provided.""" + + op = qml.Snapshot() + assert op.hyperparameters["shots"] == qml.measurements.Shots(None) + @pytest.mark.parametrize( "mp", (qml.expval(qml.Z(0)), qml.measurements.StateMP(wires=(2, 1, 0))) ) @@ -262,3 +268,26 @@ def test_map_wires(self, mp): target_mp = mp.map_wires(wire_map) qml.assert_equal(target_mp, new_op.hyperparameters["measurement"]) assert new_op.tag == "my tag" + + # pylint: disable=unused-argument + @pytest.mark.jax + @pytest.mark.parametrize("measurement", (None, "state")) + def test_capture_measurement(self, measurement, enable_disable_plxpr): + """Test that a snapshot can be captured into plxpr.""" + + import jax + + def f(): + if measurement is None: + qml.Snapshot() + else: + qml.Snapshot(measurement=qml.state()) + + jaxpr = jax.make_jaxpr(f)() + + if measurement is None: + assert jaxpr.eqns[0].primitive == qml.Snapshot._primitive + else: + assert jaxpr.eqns[0].primitive == qml.measurements.StateMP._wires_primitive + assert jaxpr.eqns[1].primitive == qml.Snapshot._primitive + assert jaxpr.eqns[1].invars[0] == jaxpr.eqns[0].outvars[0] diff --git a/tests/pauli/grouping/test_pauli_group_observables.py b/tests/pauli/grouping/test_pauli_group_observables.py index a2256b8a0e0..538ad800bbc 100644 --- a/tests/pauli/grouping/test_pauli_group_observables.py +++ b/tests/pauli/grouping/test_pauli_group_observables.py @@ -369,8 +369,8 @@ def are_partitions_equal(partition_1: list, partition_2: list) -> bool: We check this way since the partitions might vary in the order of the elements Args: - partition_1 (list[Observable]): list of Pauli word ``Observable`` instances corresponding to a partition. - partition_2 (list[Observable]): list of Pauli word ``Observable`` instances corresponding to a partition. + partition_1 (list[Operator]): list of Pauli word ``Operator`` instances corresponding to a partition. + partition_2 (list[Operator]): list of Pauli word ``Operator`` instances corresponding to a partition. """ partition_3 = set( diff --git a/tests/pauli/test_pauli_utils.py b/tests/pauli/test_pauli_utils.py index 2574413d5df..9dcab29aa4b 100644 --- a/tests/pauli/test_pauli_utils.py +++ b/tests/pauli/test_pauli_utils.py @@ -588,7 +588,7 @@ def test_one_qubit_pauli_group_integer_wire_map(self): expected_pg_1 = [Identity(0), PauliZ(0), PauliX(0), PauliY(0)] pg_1 = list(pauli_group(1)) - assert all(expected.compare(obtained) for expected, obtained in zip(expected_pg_1, pg_1)) + assert all(expected == (obtained) for expected, obtained in zip(expected_pg_1, pg_1)) def test_one_qubit_pauli_group_valid_float_input(self): """Test that the single-qubit Pauli group is constructed correctly when a float @@ -596,7 +596,7 @@ def test_one_qubit_pauli_group_valid_float_input(self): expected_pg_1 = [Identity(0), PauliZ(0), PauliX(0), PauliY(0)] pg_1 = list(pauli_group(1.0)) - assert all(expected.compare(obtained) for expected, obtained in zip(expected_pg_1, pg_1)) + assert all(expected == (obtained) for expected, obtained in zip(expected_pg_1, pg_1)) def test_one_qubit_pauli_group_string_wire_map(self): """Test that the single-qubit Pauli group is constructed correctly with a wire @@ -610,7 +610,7 @@ def test_one_qubit_pauli_group_string_wire_map(self): PauliY("qubit"), ] pg_1_wires = list(pauli_group(1, wire_map=wire_map)) - assert all(exp.compare(ob) for exp, ob in zip(expected_pg_1_wires, pg_1_wires)) + assert all(exp == (ob) for exp, ob in zip(expected_pg_1_wires, pg_1_wires)) def test_two_qubit_pauli_group(self): """Test that the two-qubit Pauli group is constructed correctly.""" diff --git a/tests/pulse/test_parametrized_evolution.py b/tests/pulse/test_parametrized_evolution.py index 8d45f86c47c..0e6143bbbc4 100644 --- a/tests/pulse/test_parametrized_evolution.py +++ b/tests/pulse/test_parametrized_evolution.py @@ -22,7 +22,6 @@ import pennylane as qml from pennylane.devices import DefaultQubit -from pennylane.operation import AnyWires from pennylane.ops import QubitUnitary from pennylane.pulse import ParametrizedEvolution, ParametrizedHamiltonian from pennylane.tape import QuantumTape @@ -152,7 +151,7 @@ def test_init(self, params, coeffs): assert qml.math.allequal(ev.t, [0, 2]) assert ev.wires == H.wires - assert ev.num_wires == AnyWires + assert ev.num_wires is None assert ev.name == "ParametrizedEvolution" assert ev.id is None diff --git a/tests/python_compiler/test_quantum_dialect.py b/tests/python_compiler/test_quantum_dialect.py new file mode 100644 index 00000000000..f5d33ee9780 --- /dev/null +++ b/tests/python_compiler/test_quantum_dialect.py @@ -0,0 +1,104 @@ +# Copyright 2025 Xanadu Quantum Technologies Inc. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Unit test module for pennylane/compiler/python_compiler/quantum_dialect.py.""" + +import pytest + +# pylint: disable=wrong-import-position + +xdsl = pytest.importorskip("xdsl") + +pytestmark = pytest.mark.external + +from xdsl.dialects.test import TestOp +from xdsl.ir import AttributeCovT, OpResult + +from pennylane.compiler.python_compiler.quantum_dialect import QuantumDialect + +all_ops = list(QuantumDialect.operations) +all_attrs = list(QuantumDialect.attributes) + +expected_ops_names = { + "AdjointOp": "quantum.adjoint", + "AllocOp": "quantum.alloc", + "ComputationalBasisOp": "quantum.compbasis", + "CountsOp": "quantum.counts", + "CustomOp": "quantum.custom", + "DeallocOp": "quantum.dealloc", + "DeviceInitOp": "quantum.device_init", + "DeviceReleaseOp": "quantum.device_release", + "ExpvalOp": "quantum.expval", + "ExtractOp": "quantum.extract", + "FinalizeOp": "quantum.finalize", + "GlobalPhaseOp": "quantum.gphase", + "HamiltonianOp": "quantum.hamiltonian", + "HermitianOp": "quantum.hermitian", + "InitializeOp": "quantum.init", + "InsertOp": "quantum.insert", + "MeasureOp": "quantum.measure", + "MultiRZOp": "quantum.multirz", + "NamedObsOp": "quantum.namedobs", + "ProbsOp": "quantum.probs", + "QubitUnitaryOp": "quantum.unitary", + "SampleOp": "quantum.sample", + "SetBasisStateOp": "quantum.set_basis_state", + "SetStateOp": "quantum.set_state", + "StateOp": "quantum.state", + "TensorOp": "quantum.tensor", + "VarianceOp": "quantum.var", + "YieldOp": "quantum.yield", +} + +expected_attrs_names = { + "ObservableType": "quantum.obs", + "QubitType": "quantum.bit", + "QuregType": "quantum.reg", + "ResultType": "quantum.res", + "NamedObservableAttr": "quantum.named_observable", +} + + +# Test function taken from xdsl/utils/test_value.py +def create_ssa_value(t: AttributeCovT) -> OpResult[AttributeCovT]: + """Create a single SSA value with the given type for testing purposes.""" + op = TestOp(result_types=(t,)) + return op.results[0] + + +def test_quantum_dialect_name(): + """Test that the QuantumDialect name is correct.""" + assert QuantumDialect.name == "quantum" + + +@pytest.mark.parametrize("op", all_ops) +def test_all_operations_names(op): + """Test that all operations have the expected name.""" + op_class_name = op.__name__ + expected_name = expected_ops_names.get(op_class_name) + assert ( + expected_name is not None + ), f"Unexpected operation {op_class_name} found in QuantumDialect" + assert op.name == expected_name + + +@pytest.mark.parametrize("attr", all_attrs) +def test_all_attributes_names(attr): + """Test that all attributes have the expected name.""" + attr_class_name = attr.__name__ + expected_name = expected_attrs_names.get(attr_class_name) + assert ( + expected_name is not None + ), f"Unexpected attribute {attr_class_name} found in QuantumDialect" + assert attr.name == expected_name diff --git a/tests/qchem/openfermion_pyscf_tests/test_convert_openfermion.py b/tests/qchem/openfermion_pyscf_tests/test_convert_openfermion.py index fa3e1cf7bc0..761af0906f6 100644 --- a/tests/qchem/openfermion_pyscf_tests/test_convert_openfermion.py +++ b/tests/qchem/openfermion_pyscf_tests/test_convert_openfermion.py @@ -43,28 +43,28 @@ class TestFromOpenFermion: OPS = ( ( (openfermion.QubitOperator("X0", 1.2) + openfermion.QubitOperator("Z1", 2.4)), - (1.2 * qml.X(0) + 2.4 * qml.Z(1)), + (qml.ops.LinearCombination([1.2, 2.4], [qml.X(0), qml.Z(1)])), ), ( (openfermion.QubitOperator("X0 X2", 0.3) + openfermion.QubitOperator("Z1 Z0", 0.5)), - (0.3 * qml.X(0) @ qml.X(2) + 0.5 * qml.Z(1) @ qml.Z(0)), + (qml.ops.LinearCombination([0.3, 0.5], [qml.X(0) @ qml.X(2), qml.Z(1) @ qml.Z(0)])), ), - (openfermion.QubitOperator(), (0.0 * qml.I(0))), + (openfermion.QubitOperator(), qml.ops.LinearCombination([0.0], [qml.I(0)])), ( ( 0.1 * openfermion.QubitOperator("X0") + 0.5 * openfermion.QubitOperator("Y1") + 0.2 * openfermion.QubitOperator("Z2") ), - (0.1 * qml.X(0) + 0.2 * qml.Z(2) + 0.5 * qml.Y(1)), + qml.ops.LinearCombination([0.1, 0.5, 0.2], [qml.X(0), qml.Y(1), qml.Z(2)]), ), ( (openfermion.QubitOperator("Z0", 0.1) - openfermion.QubitOperator("Z1", 0.5)), - (qml.ops.Sum(0.1 * qml.Z(0), -0.5 * qml.Z(1))), + qml.ops.LinearCombination([0.1, -0.5], [qml.Z(0), qml.Z(1)]), ), ( (openfermion.QubitOperator("X0 X1", 1.0) + openfermion.QubitOperator("Y0 Y1", 0.5)), - (qml.ops.Sum(1.0 * qml.X(0) @ qml.X(1), 0.5 * qml.Y(0) @ qml.Y(1))), + qml.ops.LinearCombination([1.0, 0.5], [qml.X(0) @ qml.X(1), qml.Y(0) @ qml.Y(1)]), ), ) @@ -72,13 +72,13 @@ class TestFromOpenFermion: def test_convert_qubit(self, of_op, pl_op): """Test conversion from ``QubitOperator`` to PennyLane.""" converted_pl_op = qml.from_openfermion(of_op) - assert converted_pl_op.compare(pl_op) + qml.assert_equal(converted_pl_op, pl_op) OPS_WIRES = ( ( (openfermion.QubitOperator("X0", 1.2) + openfermion.QubitOperator("Z1", 2.4)), ({0: "a", 1: 2}), - (1.2 * qml.X("a") + 2.4 * qml.Z(2)), + qml.ops.LinearCombination([1.2, 2.4], [qml.X("a"), qml.Z(2)]), ), ) @@ -86,7 +86,7 @@ def test_convert_qubit(self, of_op, pl_op): def test_wires_qubit(self, of_op, wires, pl_op): """Test conversion from ``QubitOperator`` to PennyLane with wire map.""" converted_pl_op = qml.from_openfermion(of_op, wires=wires) - assert converted_pl_op.compare(pl_op) + qml.assert_equal(converted_pl_op, pl_op) OPS_FERMI = ( ((openfermion.FermionOperator("0^ 1")), ({0: "a", 1: 2})), diff --git a/tests/qchem/openfermion_pyscf_tests/test_molecular_hamiltonian.py b/tests/qchem/openfermion_pyscf_tests/test_molecular_hamiltonian.py index 9ce6655c7c3..5b121ee2e44 100644 --- a/tests/qchem/openfermion_pyscf_tests/test_molecular_hamiltonian.py +++ b/tests/qchem/openfermion_pyscf_tests/test_molecular_hamiltonian.py @@ -366,12 +366,12 @@ def test_differentiable_hamiltonian(symbols, geometry, mapping, h_ref_data): assert all(coeff.requires_grad is False for coeff in h_noargs_coeffs) assert np.allclose(np.sort(h_args_coeffs), np.sort(h_ref_coeffs)) - assert qml.Hamiltonian(np.ones(len(h_args_coeffs)), h_args_ops).compare( + assert qml.Hamiltonian(np.ones(len(h_args_coeffs)), h_args_ops) == ( qml.Hamiltonian(np.ones(len(h_ref_coeffs)), h_ref_ops) ) assert np.allclose(np.sort(h_noargs_coeffs), np.sort(h_ref_coeffs)) - assert qml.Hamiltonian(np.ones(len(h_noargs_coeffs)), h_noargs_ops).compare( + assert qml.Hamiltonian(np.ones(len(h_noargs_coeffs)), h_noargs_ops) == ( qml.Hamiltonian(np.ones(len(h_ref_coeffs)), h_ref_ops) ) @@ -594,12 +594,12 @@ def test_differentiable_hamiltonian_molecule_class(symbols, geometry, mapping, h assert all(coeff.requires_grad is False for coeff in h_noargs_coeffs) assert np.allclose(np.sort(h_args_coeffs), np.sort(h_ref_coeffs)) - assert qml.Hamiltonian(np.ones(len(h_args_coeffs)), h_args_ops).compare( + assert qml.Hamiltonian(np.ones(len(h_args_coeffs)), h_args_ops) == ( qml.Hamiltonian(np.ones(len(h_ref_coeffs)), h_ref_ops) ) assert np.allclose(np.sort(h_noargs_coeffs), np.sort(h_ref_coeffs)) - assert qml.Hamiltonian(np.ones(len(h_noargs_coeffs)), h_noargs_ops).compare( + assert qml.Hamiltonian(np.ones(len(h_noargs_coeffs)), h_noargs_ops) == ( qml.Hamiltonian(np.ones(len(h_ref_coeffs)), h_ref_ops) ) @@ -748,7 +748,7 @@ def test_mol_hamiltonian_with_read_structure(tmpdir): f_name = "h2.xyz" filename = tmpdir.join(f_name) - with open(filename, "w") as f: + with open(filename, "w", encoding="utf8") as f: f.write(file_content) symbols, coordinates = qchem.read_structure(str(filename), outpath=tmpdir) @@ -763,7 +763,7 @@ def test_mol_hamiltonian_with_read_structure_molecule_class(tmpdir): f_name = "h2.xyz" filename = tmpdir.join(f_name) - with open(filename, "w") as f: + with open(filename, "w", encoding="utf8") as f: f.write(file_content) symbols, coordinates = qchem.read_structure(str(filename), outpath=tmpdir) @@ -1234,7 +1234,7 @@ def test_mapped_hamiltonian_pyscf_openfermion( h_coeffs, h_ops = h.terms() assert np.allclose(np.sort(h_coeffs), np.sort(h_ref_coeffs)) - assert qml.Hamiltonian(np.ones(len(h_coeffs)), h_ops).compare( + assert qml.Hamiltonian(np.ones(len(h_coeffs)), h_ops) == ( qml.Hamiltonian(np.ones(len(h_ref_coeffs)), h_ref_ops) ) diff --git a/tests/qchem/test_dipole.py b/tests/qchem/test_dipole.py index 55a50a05d74..d73c0d96926 100644 --- a/tests/qchem/test_dipole.py +++ b/tests/qchem/test_dipole.py @@ -194,7 +194,7 @@ def test_dipole_moment(symbols, geometry, core, charge, active, coeffs, ops): dref_coeff, dref_ops = d_ref.terms() assert np.allclose(sorted(d_coeff), sorted(dref_coeff)) - assert qml.Hamiltonian(np.ones(len(d_coeff)), d_ops).compare( + assert qml.Hamiltonian(np.ones(len(d_coeff)), d_ops) == ( qml.Hamiltonian(np.ones(len(dref_coeff)), dref_ops) ) assert np.allclose( diff --git a/tests/qchem/test_hamiltonians.py b/tests/qchem/test_hamiltonians.py index 2b369d4c6fb..4521bc87738 100644 --- a/tests/qchem/test_hamiltonians.py +++ b/tests/qchem/test_hamiltonians.py @@ -268,7 +268,7 @@ def test_diff_hamiltonian(use_jax, symbols, geometry, h_ref_data): h_ref_data = qml.Hamiltonian(h_ref_data[0], ops) assert np.allclose(np.sort(h.terms()[0]), np.sort(h_ref_data.terms()[0])) - assert qml.Hamiltonian(np.ones(len(h.terms()[0])), h.terms()[1]).compare( + assert qml.Hamiltonian(np.ones(len(h.terms()[0])), h.terms()[1]) == ( qml.Hamiltonian(np.ones(len(h_ref_data.terms()[0])), h_ref_data.terms()[1]) ) diff --git a/tests/qchem/test_observable_hf.py b/tests/qchem/test_observable_hf.py index 2b0a3db7297..6194baa6a59 100644 --- a/tests/qchem/test_observable_hf.py +++ b/tests/qchem/test_observable_hf.py @@ -170,9 +170,8 @@ def test_qubit_observable(f_observable, q_observable): r"""Test that qubit_observable returns the correct operator.""" h_as_op = qchem.qubit_observable(f_observable) ops = list(map(qml.simplify, q_observable[1])) - h_ref = qml.Hamiltonian(q_observable[0], ops) - - assert h_ref.compare(h_as_op) + h_ref = qml.dot(q_observable[0], ops) + qml.assert_equal(h_ref, h_as_op) assert np.allclose( qml.matrix(h_as_op, wire_order=[0, 1, 2]), qml.matrix(h_ref, wire_order=[0, 1, 2]) ) @@ -193,10 +192,10 @@ def test_qubit_observable(f_observable, q_observable): ) def test_qubit_observable_cutoff(f_observable, cut_off): """Test that qubit_observable returns the correct operator when a cutoff is provided.""" - h_ref, h_ref_op = (qml.Hamiltonian([], []), qml.s_prod(0, qml.Identity(0))) + h_ref, h_ref_op = 0 * qml.I(0), qml.s_prod(0, qml.Identity(0)) h_as_op = qchem.qubit_observable(f_observable, cutoff=cut_off) - assert h_ref.compare(h_as_op) + qml.assert_equal(h_ref, h_as_op) assert np.allclose( qml.matrix(h_ref_op, wire_order=[0, 1, 2]), qml.matrix(h_as_op, wire_order=[0, 1, 2]) ) diff --git a/tests/qchem/test_particle_number.py b/tests/qchem/test_particle_number.py index bd1987ab205..e217e609374 100644 --- a/tests/qchem/test_particle_number.py +++ b/tests/qchem/test_particle_number.py @@ -56,8 +56,8 @@ def test_particle_number(orbitals, coeffs_ref, ops_ref): function `'spin_z'`. """ n = qchem.particle_number(orbitals) - n_ref = qml.Hamiltonian(coeffs_ref, ops_ref) - assert n_ref.compare(n) + n_ref = qml.dot(coeffs_ref, ops_ref) + qml.assert_equal(n_ref, n) assert isinstance(n, qml.ops.Sum) wire_order = n_ref.wires diff --git a/tests/qchem/test_spin.py b/tests/qchem/test_spin.py index 294254566d4..74ae001da8b 100644 --- a/tests/qchem/test_spin.py +++ b/tests/qchem/test_spin.py @@ -174,8 +174,8 @@ def test_spin2(electrons, orbitals, coeffs_ref, ops_ref): """ s2 = qchem.spin.spin2(electrons, orbitals) sops = list(map(simplify, ops_ref)) - s2_ref = qml.Hamiltonian(coeffs_ref, sops) - assert s2_ref.compare(s2) + s2_ref = qml.dot(coeffs_ref, sops) + qml.assert_equal(s2_ref, s2) assert isinstance(s2, qml.ops.Sum) wire_order = s2_ref.wires @@ -229,8 +229,8 @@ def test_spinz(orbitals, coeffs_ref, ops_ref): function `'spin_z'`. """ sz = qchem.spin.spinz(orbitals) - sz_ref = qml.Hamiltonian(coeffs_ref, ops_ref) - assert sz_ref.compare(sz) + sz_ref = qml.dot(coeffs_ref, ops_ref) + assert sz_ref == (sz) assert isinstance(sz, qml.ops.Sum) wire_order = sz_ref.wires diff --git a/tests/qchem/test_tapering.py b/tests/qchem/test_tapering.py index 512bd969f8f..2ff65a87be4 100644 --- a/tests/qchem/test_tapering.py +++ b/tests/qchem/test_tapering.py @@ -200,7 +200,7 @@ def test_generate_paulis(generators, num_qubits, result): r"""Test that generate_paulis returns the correct result.""" pauli_ops = qml.paulix_ops(generators, num_qubits) for p1, p2 in zip(pauli_ops, result): - assert p1.compare(p2) + qml.assert_equal(p1, p2) # test arithmetic op compatibility: generators_as_ops = [pauli_sentence(g).operation() for g in generators] diff --git a/tests/templates/test_state_preparations/test_mottonen_state_prep.py b/tests/templates/test_state_preparations/test_mottonen_state_prep.py index a63a2d20129..b75ef1a0d62 100644 --- a/tests/templates/test_state_preparations/test_mottonen_state_prep.py +++ b/tests/templates/test_state_preparations/test_mottonen_state_prep.py @@ -21,7 +21,7 @@ import pennylane as qml from pennylane import numpy as pnp -from pennylane.templates.state_preparations.mottonen import _get_alpha_y, gray_code +from pennylane.templates.state_preparations.mottonen import _get_alpha_y, compute_theta, gray_code def test_standard_validity(): @@ -35,21 +35,54 @@ def test_standard_validity(): qml.ops.functions.assert_valid(op) +def compute_theta_reference(alpha): + """Maps the angles alpha of the multi-controlled rotations decomposition of a + uniformly controlled rotation to the rotation angles used in the Gray code implementation. + + Args: + alpha (tensor_like): alpha parameters + + Returns: + (tensor_like): rotation angles theta + """ + ln = alpha.shape[-1] + + def _matrix_M_row(row): + """Returns one row of entries for the matrix that maps alpha to theta. + + See Eq. (3) in `Möttönen et al. (2004) `_. + + Args: + row (int): one-based row number + + Returns: + (float): transformation matrix row at given row index + """ + # (row >> 1) ^ row is the Gray code of row + COL = np.arange(ln) + b_and_g = COL & ((row >> 1) ^ row) + sum_of_ones = np.array([val.bit_count() for val in b_and_g]) + return (-1) ** sum_of_ones + + alpha = qml.math.transpose(alpha) + theta = qml.math.array([qml.math.dot(_matrix_M_row(i), alpha) for i in range(ln)]) + return qml.math.transpose(theta) / ln + + class TestHelpers: """Tests the helper functions for classical pre-processsing.""" # fmt: off - @pytest.mark.parametrize("rank,expected_gray_code", [ - (1, ['0', '1']), - (2, ['00', '01', '11', '10']), - (3, ['000', '001', '011', '010', '110', '111', '101', '100']), + @pytest.mark.parametrize("rank, expected_gray_code", [ + (1, [0, 1]), (2, [0, 1, 3, 2]), (3, [0, 1, 3, 2, 6, 7, 5, 4]) ]) # fmt: on def test_gray_code(self, rank, expected_gray_code): - """Tests that the function gray_code generates the proper - Gray code of given rank.""" + """Tests that the function gray_code generates the correct Gray code of given rank.""" - assert gray_code(rank) == expected_gray_code + code = gray_code(rank) + assert code.dtype == np.int64 + assert np.allclose(code, expected_gray_code) @pytest.mark.parametrize( "current_qubit, expected", @@ -66,6 +99,19 @@ def test_get_alpha_y(self, current_qubit, expected, tol): res = _get_alpha_y(state, 3, current_qubit) assert np.allclose(res, expected, atol=tol) + @pytest.mark.parametrize("batch_dim", [None, 1, 5, 10]) + @pytest.mark.parametrize("n", list(range(1, 11))) + def test_compute_theta(self, n, batch_dim): + """Test that the fast Walsh-Hadamard transform-based method reproduces the + matrix given in Eq. (3) in + `Möttönen et al. (2004) `_.""" + shape = (2**n,) if batch_dim is None else (batch_dim, 2**n) + alpha = np.random.random(shape) + expected_theta = compute_theta_reference(alpha) + theta = compute_theta(alpha) + assert theta.shape == shape == expected_theta.shape + assert np.allclose(expected_theta, theta) + # fmt: off fixed_states = ( diff --git a/tests/templates/test_subroutines/test_select_pauli_rot.py b/tests/templates/test_subroutines/test_select_pauli_rot.py index 97faaf89b3b..1fcb510df1f 100644 --- a/tests/templates/test_subroutines/test_select_pauli_rot.py +++ b/tests/templates/test_subroutines/test_select_pauli_rot.py @@ -130,15 +130,38 @@ def test_correctness(self, angles, rot_axis): assert np.isclose(1.0, output[0]) - def test_decomposition(self): + @pytest.mark.parametrize("n", [1, 2, 3, 4]) + @pytest.mark.parametrize("axis", "XYZ") + @pytest.mark.parametrize("batch_dim", [None, 1, 3]) + def test_decomposition(self, n, axis, batch_dim, seed): """Test that the correct gates are added in the decomposition""" + np.random.seed(seed) + shape = (2**n,) if batch_dim is None else (batch_dim, 2**n) + x = np.random.random(shape) decomposition = qml.SelectPauliRot.compute_decomposition( - np.array([1, 2, 3, 4, 5, 6, 7, 8]), control_wires=range(3), target_wire=3, rot_axis="Z" + x, control_wires=range(n), target_wire=n, rot_axis=axis ) - - for gate in decomposition: - assert gate.name in ["CNOT", "RZ"] + decomposition_2 = qml.SelectPauliRot( + x, control_wires=range(n), target_wire=n, rot_axis=axis + ).decomposition() + + for dec in [decomposition, decomposition_2]: + if axis == "Y": + assert dec[0].name == "Adjoint(S)" + assert dec[-1].name == "S" + dec = dec[1:-1] + if axis in "XY": + assert dec[0].name == "Hadamard" + assert dec[-1].name == "Hadamard" + dec = dec[1:-1] + # Remaining decomposition is the same for all axis types + assert len(dec) == 2 * 2**n + for gate in dec[::2]: + assert gate.name == "RZ" + assert gate.batch_size == batch_dim + for gate in dec[1::2]: + assert gate.name == "CNOT" @pytest.mark.jax def test_interface_jax(self): diff --git a/tests/test_debugging.py b/tests/test_debugging.py index 532c11860ae..4dbfbfd9616 100644 --- a/tests/test_debugging.py +++ b/tests/test_debugging.py @@ -41,7 +41,8 @@ def test_snapshot_output_tapes(self, shots): qml.Hadamard(wires=0), qml.Snapshot("very_important_state"), qml.CNOT(wires=[0, 1]), - qml.Snapshot(), + qml.Snapshot(measurement=qml.probs()), + qml.Snapshot(measurement=qml.sample(), shots=10), ] measurements = [qml.expval(qml.PauliX(0))] @@ -49,9 +50,10 @@ def test_snapshot_output_tapes(self, shots): num_snapshots = len(tuple(filter(lambda x: isinstance(x, qml.Snapshot), ops))) expected_tapes = [ - qml.tape.QuantumTape([], [qml.state()], shots=shots), - qml.tape.QuantumTape([qml.Hadamard(0)], [qml.state()], shots=shots), - qml.tape.QuantumTape([qml.Hadamard(0), qml.CNOT((0, 1))], [qml.state()], shots=shots), + qml.tape.QuantumTape([], [qml.state()], shots=None), + qml.tape.QuantumTape([qml.Hadamard(0)], [qml.state()], shots=None), + qml.tape.QuantumTape([qml.Hadamard(0), qml.CNOT((0, 1))], [qml.probs()], shots=shots), + qml.tape.QuantumTape([qml.Hadamard(0), qml.CNOT((0, 1))], [qml.sample()], shots=10), qml.tape.QuantumTape( [qml.Hadamard(0), qml.CNOT((0, 1))], [qml.expval(qml.X(0))], shots=shots ), @@ -179,12 +181,7 @@ def circuit(): return qml.expval(qml.PauliZ(0)) - with ( - pytest.warns(UserWarning, match="Requested state or density matrix with finite shots") - if isinstance(dev, qml.devices.default_qutrit.DefaultQutrit) - else nullcontext() - ): - qml.snapshots(circuit)(shots=200) + _ = qml.snapshots(circuit)(shots=200) @pytest.mark.parametrize("diff_method", [None, "parameter-shift"]) def test_all_state_measurement_snapshot_pure_qubit_dev(self, dev, diff_method): @@ -243,6 +240,46 @@ def circuit(): _compare_numpy_dicts(result, expected) + def test_override_shots(self, dev): + """Test that override shots allow snapshots to work with different numbers of measurements.""" + + @qml.qnode(dev) + def c(): + if dev.name != "default.qutrit": + qml.H(0) + qml.Snapshot("sample", qml.sample(wires=0), shots=5) + qml.Snapshot("counts", qml.counts(wires=0, all_outcomes=True), shots=20) + qml.Snapshot("probs", qml.probs(wires=0), shots=21) + return qml.state() + + out = qml.snapshots(c)() + + assert out["sample"].shape == (5,) + assert out["counts"]["0"] + out["counts"].get("1", 0) == 20 + if dev.name != "default.qutrit": + # very rare that it will be *exactly* [0.5, 0.5] if 20 shots + assert not qml.math.allclose(out["probs"], np.array([0.5, 0.5]), atol=1e-8) + + def test_override_analytic(self, dev): + """Test that finite shots can be written with analytic calculations.""" + + if dev.name == "default.qutrit": + pytest.skip("hard to write generic test that works with qutrits.") + + @qml.transform + def set_shots(tape, shots): + return (tape.copy(shots=shots),), lambda res: res[0] + + @qml.qnode(dev, diff_method=None) + def c(): + qml.H(0) + qml.Snapshot("probs", qml.probs(wires=0), shots=None) + return qml.sample(wires=0) + + out = qml.snapshots(set_shots(c, shots=10))() + assert qml.math.allclose(out["probs"], np.array([0.5, 0.5])) + assert out["execution_results"].shape == (10,) + class TestSnapshotSupportedQNode: """Test the Snapshot instruction for simulators.""" @@ -621,16 +658,9 @@ def test_lightning_qubit_fails_for_state_snapshots_with_adjoint_and_backprop(sel dev = qml.device("lightning.qubit", wires=2) - with ( - pytest.raises( - qml.DeviceError, - match=r"not accepted for analytic simulation on adjoint \+ lightning.qubit", - ) - if diff_method == "adjoint" - else pytest.raises( - qml.QuantumFunctionError, - match=f"does not support {diff_method} with requested circuit", - ) + with pytest.raises( + qml.QuantumFunctionError, + match=f"does not support {diff_method} with requested circuit", ): @qml.qnode(dev, diff_method=diff_method) diff --git a/tests/test_operation.py b/tests/test_operation.py index 91b3d7ef356..563bee211a2 100644 --- a/tests/test_operation.py +++ b/tests/test_operation.py @@ -41,6 +41,19 @@ I_broadcasted = I[pnp.newaxis] +def test_wires_enum_deprecation(): + """Test that WiresEnum, AllWires, and AnyWires are deprecated.""" + + with pytest.warns(qml.PennyLaneDeprecationWarning, match="is deprecated"): + _ = qml.operation.WiresEnum + + with pytest.warns(qml.PennyLaneDeprecationWarning, match="is deprecated"): + _ = qml.operation.AllWires + + with pytest.warns(qml.PennyLaneDeprecationWarning, match="is deprecated"): + _ = qml.operation.AnyWires + + class TestOperatorConstruction: """Test custom operators' construction.""" @@ -78,13 +91,13 @@ class DummyOp(qml.operation.Operator): DummyOp(0.5, wires=[1, 1]) def test_num_wires_default_any_wires(self): - """Test that num_wires is `AnyWires` by default.""" + """Test that num_wires is None by default.""" class DummyOp(qml.operation.Operator): r"""Dummy custom operator""" - assert DummyOp.num_wires == qml.operation.AnyWires - assert Operator.num_wires == qml.operation.AnyWires + assert DummyOp.num_wires is None + assert Operator.num_wires is None def test_incorrect_num_params(self): """Test that an exception is raised if called with wrong number of parameters""" @@ -938,11 +951,13 @@ class TestObservableConstruction: def test_construction_with_wires_pos_arg(self): """Test that the wires can be given as a positional argument""" - class DummyObserv(qml.operation.Observable): - r"""Dummy custom observable""" + with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning): - num_wires = 1 - grad_method = None + class DummyObserv(qml.operation.Observable): + r"""Dummy custom observable""" + + num_wires = 1 + grad_method = None ob = DummyObserv([1]) assert ob.wires == qml.wires.Wires(1) @@ -950,20 +965,26 @@ class DummyObserv(qml.operation.Observable): def test_observable_is_not_operation_but_operator(self): """Check that the Observable class inherits from an Operator, not from an Operation""" - assert issubclass(qml.operation.Observable, qml.operation.Operator) - assert not issubclass(qml.operation.Observable, qml.operation.Operation) + with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning): + assert issubclass(qml.operation.Observable, qml.operation.Operator) + + with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning): + assert not issubclass(qml.operation.Observable, qml.operation.Operation) def test_observable_is_operation_as_well(self): """Check that the Observable class inherits from an Operator class as well""" - class DummyObserv(qml.operation.Observable, qml.operation.Operation): - r"""Dummy custom observable""" + with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning): - num_wires = 1 - grad_method = None + class DummyObserv(qml.operation.Observable, qml.operation.Operation): + r"""Dummy custom observable""" + + num_wires = 1 + grad_method = None assert issubclass(DummyObserv, qml.operation.Operator) - assert issubclass(DummyObserv, qml.operation.Observable) + with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning): + assert issubclass(DummyObserv, qml.operation.Observable) assert issubclass(DummyObserv, qml.operation.Operation) def test_tensor_n_multiple_modes(self): @@ -1019,11 +1040,13 @@ def test_repr(self): def test_id(self): """Test that the id attribute of an observable can be set.""" - class DummyObserv(qml.operation.Observable): - r"""Dummy custom observable""" + with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning): - num_wires = 1 - grad_method = None + class DummyObserv(qml.operation.Observable): + r"""Dummy custom observable""" + + num_wires = 1 + grad_method = None op = DummyObserv(1.0, wires=0, id="test") assert op.id == "test" @@ -1031,8 +1054,10 @@ class DummyObserv(qml.operation.Observable): def test_raises_if_no_wire_is_given(self): """Test that an error is raised if no wire is passed at initialization.""" - class DummyObservable(qml.operation.Observable): - num_wires = 1 + with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning): + + class DummyObservable(qml.operation.Observable): + num_wires = 1 with pytest.raises(Exception, match="Must specify the wires *"): DummyObservable() @@ -1040,11 +1065,13 @@ class DummyObservable(qml.operation.Observable): def test_is_hermitian(self): """Test that the id attribute of an observable can be set.""" - class DummyObserv(qml.operation.Observable): - r"""Dummy custom observable""" + with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning): - num_wires = 1 - grad_method = None + class DummyObserv(qml.operation.Observable): + r"""Dummy custom observable""" + + num_wires = 1 + grad_method = None op = DummyObserv(wires=0) assert op.is_hermitian is True @@ -1597,66 +1624,92 @@ class TestCriteria: def test_docstring(self): expected = "Returns ``True`` if an operator has a generator defined." - assert qml.operation.has_gen.__doc__ == expected + assert expected in qml.operation.has_gen.__doc__ def test_has_gen(self): """Test has_gen criterion.""" - assert qml.operation.has_gen(self.rx) - assert not qml.operation.has_gen(self.cnot) - assert not qml.operation.has_gen(self.rot) - assert not qml.operation.has_gen(self.exp) + with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning): + assert qml.operation.has_gen(self.rx) + with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning): + assert not qml.operation.has_gen(self.cnot) + with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning): + assert not qml.operation.has_gen(self.rot) + with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning): + assert not qml.operation.has_gen(self.exp) def test_has_grad_method(self): """Test has_grad_method criterion.""" - assert qml.operation.has_grad_method(self.rx) - assert qml.operation.has_grad_method(self.rot) - assert not qml.operation.has_grad_method(self.cnot) + with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning): + assert qml.operation.has_grad_method(self.rx) + with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning): + assert qml.operation.has_grad_method(self.rot) + with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning): + assert not qml.operation.has_grad_method(self.cnot) def test_gen_is_multi_term_hamiltonian(self): """Test gen_is_multi_term_hamiltonian criterion.""" - assert qml.operation.gen_is_multi_term_hamiltonian(self.doubleExcitation) - assert not qml.operation.gen_is_multi_term_hamiltonian(self.cnot) - assert not qml.operation.gen_is_multi_term_hamiltonian(self.rot) - assert not qml.operation.gen_is_multi_term_hamiltonian(self.exp) + with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning): + assert qml.operation.gen_is_multi_term_hamiltonian(self.doubleExcitation) + with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning): + assert not qml.operation.gen_is_multi_term_hamiltonian(self.cnot) + with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning): + assert not qml.operation.gen_is_multi_term_hamiltonian(self.rot) + with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning): + assert not qml.operation.gen_is_multi_term_hamiltonian(self.exp) class SProdGen(Operator): def generator(self): return 2.0 * (qml.X(0) + qml.Y(0)) - assert qml.operation.gen_is_multi_term_hamiltonian(SProdGen(wires=0)) + with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning): + assert qml.operation.gen_is_multi_term_hamiltonian(SProdGen(wires=0)) class SumGen(Operator): def generator(self): return qml.X(0) + qml.Y(1) - assert qml.operation.gen_is_multi_term_hamiltonian(SumGen(wires=0)) + with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning): + assert qml.operation.gen_is_multi_term_hamiltonian(SumGen(wires=0)) def test_has_multipar(self): """Test has_multipar criterion.""" - assert not qml.operation.has_multipar(self.rx) - assert qml.operation.has_multipar(self.rot) - assert not qml.operation.has_multipar(self.cnot) + with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning): + assert not qml.operation.has_multipar(self.rx) + with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning): + assert qml.operation.has_multipar(self.rot) + with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning): + assert not qml.operation.has_multipar(self.cnot) def test_has_nopar(self): """Test has_nopar criterion.""" - assert not qml.operation.has_nopar(self.rx) - assert not qml.operation.has_nopar(self.rot) - assert qml.operation.has_nopar(self.cnot) + with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning): + assert not qml.operation.has_nopar(self.rx) + with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning): + assert not qml.operation.has_nopar(self.rot) + with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning): + assert qml.operation.has_nopar(self.cnot) def test_has_unitary_gen(self): """Test has_unitary_gen criterion.""" - assert qml.operation.has_unitary_gen(self.rx) - assert not qml.operation.has_unitary_gen(self.rot) - assert not qml.operation.has_unitary_gen(self.cnot) + with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning): + assert qml.operation.has_unitary_gen(self.rx) + with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning): + assert not qml.operation.has_unitary_gen(self.rot) + with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning): + assert not qml.operation.has_unitary_gen(self.cnot) def test_is_measurement(self): """Test is_measurement criterion.""" - assert not qml.operation.is_measurement(self.rx) - assert not qml.operation.is_measurement(self.rot) - assert not qml.operation.is_measurement(self.cnot) - assert qml.operation.is_measurement(self.exp) + with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning): + assert not qml.operation.is_measurement(self.rx) + with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning): + assert not qml.operation.is_measurement(self.rot) + with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning): + assert not qml.operation.is_measurement(self.cnot) + with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning): + assert qml.operation.is_measurement(self.exp) def test_is_trainable(self): """Test is_trainable criterion.""" @@ -1669,10 +1722,27 @@ def test_is_trainable(self): def test_composed(self): """Test has_gen criterion.""" both = qml.operation.has_gen & qml.operation.is_trainable - assert both(self.rx) - assert not both(self.cnot) - assert not both(self.rot) - assert not both(self.exp) + with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning): + assert both(self.rx) + with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning): + assert not both(self.cnot) + with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning): + assert not both(self.rot) + with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning): + assert not both(self.exp) + + def test_not_tape(self): + """Test the not_tape criterion.""" + + with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning): + assert qml.operation.not_tape(2) + with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning): + assert not qml.operation.not_tape(qml.tape.QuantumScript()) + + def test_defines_diagonalizing_gates(self): + """Test the defines_diagonalizing_gates criterion.""" + with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning): + assert qml.operation.defines_diagonalizing_gates(qml.X(0)) pairs_of_ops = [ @@ -1858,7 +1928,6 @@ def test_docstring_example_of_operator_class(tol): page in the developer guide.""" class FlipAndRotate(qml.operation.Operation): - num_wires = qml.operation.AnyWires grad_method = "A" # pylint: disable=too-many-arguments,too-many-positional-arguments diff --git a/tests/test_qaoa.py b/tests/test_qaoa.py index 5f31aff8d55..462696d3ea8 100644 --- a/tests/test_qaoa.py +++ b/tests/test_qaoa.py @@ -334,7 +334,7 @@ def test_x_mixer_output(self): [1, 1, 1, 1], [qml.PauliX(0), qml.PauliX(1), qml.PauliX(2), qml.PauliX(3)], ) - assert mixer_hamiltonian.compare(expected_hamiltonian) + assert mixer_hamiltonian == expected_hamiltonian def test_x_mixer_grouping(self): """Tests that the grouping information is set and correct""" @@ -362,7 +362,7 @@ def test_xy_mixer_type_error(self): def test_xy_mixer_output(self, graph, target_hamiltonian): """Tests that the output of the XY mixer is correct""" hamiltonian = qaoa.xy_mixer(graph) - assert hamiltonian.compare(target_hamiltonian) + assert hamiltonian == target_hamiltonian def test_bit_flip_mixer_errors(self): """Tests that the bit-flip mixer throws the correct errors""" @@ -384,7 +384,7 @@ def test_bit_flip_mixer_errors(self): def test_bit_flip_mixer_output(self, graph, n, target_hamiltonian): """Tests that the output of the bit-flip mixer is correct""" hamiltonian = qaoa.bit_flip_mixer(graph, n) - assert hamiltonian.compare(target_hamiltonian) + assert hamiltonian == target_hamiltonian GRAPHS = [ @@ -923,7 +923,7 @@ def test_bit_driver_output(self): H = qaoa.bit_driver(range(3), 1) hamiltonian = qml.Hamiltonian([1, 1, 1], [qml.PauliZ(0), qml.PauliZ(1), qml.PauliZ(2)]) - assert hamiltonian.compare(H) + assert hamiltonian == H def test_edge_driver_errors(self): """Tests that the edge driver Hamiltonian throws the correct errors""" @@ -946,7 +946,7 @@ def test_edge_driver_errors(self): def test_edge_driver_output(self, graph, reward, hamiltonian): """Tests that the edge driver Hamiltonian throws the correct errors""" H = qaoa.edge_driver(graph, reward) - assert hamiltonian.compare(H) + assert hamiltonian == H def test_max_weight_cycle_errors(self): """Tests that the max weight cycle Hamiltonian throws the correct errors""" @@ -976,8 +976,8 @@ def test_cost_graph_error(self): def test_maxcut_output(self, graph, cost_hamiltonian, mixer_hamiltonian): """Tests that the output of the MaxCut method is correct""" cost_h, mixer_h = qaoa.maxcut(graph) - assert cost_h.compare(cost_hamiltonian) - assert mixer_h.compare(mixer_hamiltonian) + assert cost_h == cost_hamiltonian + assert mixer_h == mixer_hamiltonian def test_maxcut_grouping(self): """Tests that the grouping information is set and correct""" @@ -999,8 +999,8 @@ def test_maxcut_grouping(self): def test_mis_output(self, graph, constrained, cost_hamiltonian, mixer_hamiltonian): """Tests that the output of the Max Indepenent Set method is correct""" cost_h, mixer_h = qaoa.max_independent_set(graph, constrained=constrained) - assert cost_h.compare(cost_hamiltonian) - assert mixer_h.compare(mixer_hamiltonian) + assert cost_h == cost_hamiltonian + assert mixer_h == mixer_hamiltonian def test_mis_grouping(self): """Tests that the grouping information is set and correct""" @@ -1022,8 +1022,8 @@ def test_mis_grouping(self): def test_mvc_output(self, graph, constrained, cost_hamiltonian, mixer_hamiltonian): """Tests that the output of the Min Vertex Cover method is correct""" cost_h, mixer_h = qaoa.min_vertex_cover(graph, constrained=constrained) - assert cost_h.compare(cost_hamiltonian) - assert mixer_h.compare(mixer_hamiltonian) + assert cost_h == cost_hamiltonian + assert mixer_h == mixer_hamiltonian def test_mvc_grouping(self): """Tests that the grouping information is set and correct""" @@ -1045,8 +1045,8 @@ def test_mvc_grouping(self): def test_max_clique_output(self, graph, constrained, cost_hamiltonian, mixer_hamiltonian): """Tests that the output of the Maximum Clique method is correct""" cost_h, mixer_h = qaoa.max_clique(graph, constrained=constrained) - assert cost_h.compare(cost_hamiltonian) - assert mixer_h.compare(mixer_hamiltonian) + assert cost_h == cost_hamiltonian + assert mixer_h == mixer_hamiltonian def test_max_clique_grouping(self): """Tests that the grouping information is set and correct""" @@ -1071,8 +1071,8 @@ def test_max_weight_cycle_output( ): """Tests that the output of the maximum weighted cycle method is correct""" cost_h, mixer_h, m = qaoa.max_weight_cycle(graph, constrained=constrained) - assert cost_h.compare(cost_hamiltonian) - assert mixer_h.compare(mixer_hamiltonian) + assert cost_h == cost_hamiltonian + assert mixer_h == mixer_hamiltonian assert mapping == m def test_max_weight_cycle_grouping(self): @@ -1818,7 +1818,7 @@ def test_inner_out_flow_constraint_hamiltonian(self, g): expected_coeffs = [2, 2, -2, -2] expected_hamiltonian = qml.Hamiltonian(expected_coeffs, expected_ops) - assert h.compare(expected_hamiltonian) + assert h == expected_hamiltonian @pytest.mark.parametrize("g", [nx.complete_graph(3), rx.generators.mesh_graph(3, [0, 1, 2])]) def test_inner_out_flow_constraint_hamiltonian_error(self, g): diff --git a/tests/test_qnode.py b/tests/test_qnode.py index 9fb0961d4ce..59831a7ce9c 100644 --- a/tests/test_qnode.py +++ b/tests/test_qnode.py @@ -577,7 +577,7 @@ def circuit(): ) def test_tensor_measurement(self, measurement): """Tests that measurements of tensor type are handled correctly""" - dev = qml.device("default.qubit", wires=2) + dev = qml.device("default.qubit", wires=3) @qml.qnode(dev) def circuit(): diff --git a/tests/transforms/test_add_noise.py b/tests/transforms/test_add_noise.py index fd069a8f62d..902eef7f3d2 100644 --- a/tests/transforms/test_add_noise.py +++ b/tests/transforms/test_add_noise.py @@ -282,7 +282,7 @@ def f2(w1, w2): def test_add_noise_with_non_qwc_obs_and_mid_meas(self): """Test that the add_noise transform catches and reports errors from the enclosed function.""" - dev = qml.device("default.qubit", wires=3) + dev = qml.device("default.qubit", wires=5) fcond = qml.noise.wires_in([0, 1]) diff --git a/tests/transforms/test_commutation_dag.py b/tests/transforms/test_commutation_dag.py index 6c59fe48a7b..ce91946e413 100644 --- a/tests/transforms/test_commutation_dag.py +++ b/tests/transforms/test_commutation_dag.py @@ -50,13 +50,13 @@ def circuit(): nodes = [a, b] edges = [(0, 1, {"commute": False})] - assert dag.get_node(0).op.compare(a) - assert dag.get_node(1).op.compare(b) + assert dag.get_node(0).op == a + assert dag.get_node(1).op == b assert dag.get_edge(0, 1) == {0: {"commute": False}} assert dag.get_edge(0, 2) is None assert dag.observables == [] for i, node in enumerate(dag.get_nodes()): - assert node[1].op.compare(nodes[i]) + assert node[1].op == nodes[i] for i, edge in enumerate(dag.get_edges()): assert edges[i] == edge @@ -75,13 +75,13 @@ def test_dag_transform_simple_dag_tape(self): nodes = [a, b] edges = [(0, 1, {"commute": False})] - assert dag.get_node(0).op.compare(a) - assert dag.get_node(1).op.compare(b) + assert dag.get_node(0).op == (a) + assert dag.get_node(1).op == (b) assert dag.get_edge(0, 1) == {0: {"commute": False}} assert dag.get_edge(0, 2) is None assert dag.observables == [] for i, node in enumerate(dag.get_nodes()): - assert node[1].op.compare(nodes[i]) + assert node[1].op == (nodes[i]) for i, edge in enumerate(dag.get_edges()): assert edges[i] == edge @@ -100,13 +100,13 @@ def circuit(): nodes = [a, b] edges = [(0, 1, {"commute": False})] - assert dag.get_node(0).op.compare(a) - assert dag.get_node(1).op.compare(b) + assert dag.get_node(0).op == (a) + assert dag.get_node(1).op == (b) assert dag.get_edge(0, 1) is None assert dag.get_edge(0, 2) is None assert dag.observables == [] for i, node in enumerate(dag.get_nodes()): - assert node[1].op.compare(nodes[i]) + assert node[1].op == (nodes[i]) for i, edge in enumerate(dag.get_edges()): assert edges[i] == edge @@ -129,14 +129,14 @@ def circuit(): nodes = [a, b] edges = [(0, 1, {"commute": False})] - assert dag.get_node(0).op.compare(a) - assert dag.get_node(1).op.compare(b) + assert dag.get_node(0).op == (a) + assert dag.get_node(1).op == (b) assert dag.get_edge(0, 1) == {0: {"commute": False}} assert dag.get_edge(0, 2) is None assert dag.observables[0].name == "PauliX" assert dag.observables[0].wires.tolist() == [0] for i, node in enumerate(dag.get_nodes()): - assert node[1].op.compare(nodes[i]) + assert node[1].op == (nodes[i]) for i, edge in enumerate(dag.get_edges()): assert edges[i] == edge diff --git a/tests/transforms/test_diagonalize_measurements.py b/tests/transforms/test_diagonalize_measurements.py index beba8b7af5f..95264e2a8d2 100644 --- a/tests/transforms/test_diagonalize_measurements.py +++ b/tests/transforms/test_diagonalize_measurements.py @@ -219,7 +219,7 @@ def test_diagonalizing_unknown_observable(self): """Test that an unknown observable is left undiagonalized""" # pylint: disable=too-few-public-methods - class MyObs(qml.operation.Observable): + class MyObs(qml.operation.Operator): @property def name(self): diff --git a/tests/transforms/test_qmc_transform.py b/tests/transforms/test_qmc_transform.py index 3558c631c3e..881b9e6a1a5 100644 --- a/tests/transforms/test_qmc_transform.py +++ b/tests/transforms/test_qmc_transform.py @@ -61,12 +61,7 @@ def r_unitary(gate, alpha, control_wires, target_wire): return code = qml.templates.state_preparations.mottonen.gray_code(gray_code_rank) - num_selections = len(code) - - control_indices = [ - int(np.log2(int(code[i], 2) ^ int(code[(i + 1) % num_selections], 2))) - for i in range(num_selections) - ] + control_indices = np.log2(code ^ np.roll(code, -1)).astype(int) for i, control_index in enumerate(control_indices): if qml.math.all(theta[..., i] != 0.0): diff --git a/tests/transforms/test_tape_expand.py b/tests/transforms/test_tape_expand.py index 04938b32bf0..f7b33c8f125 100644 --- a/tests/transforms/test_tape_expand.py +++ b/tests/transforms/test_tape_expand.py @@ -25,10 +25,15 @@ from pennylane.wires import Wires +def crit_0(op: qml.operation.Operator): + return not any(qml.math.requires_grad(d) for d in op.data) or ( + op.has_generator and any(qml.math.requires_grad(d) for d in op.data) + ) + + class TestCreateExpandFn: """Test creating expansion functions from stopping criteria.""" - crit_0 = (~qml.operation.is_trainable) | (qml.operation.has_gen & qml.operation.is_trainable) doc_0 = "Test docstring." with qml.queuing.AnnotatedQueue() as q: qml.RX(0.2, wires=0) @@ -41,14 +46,14 @@ def test_create_expand_fn(self): """Test creation of expand_fn.""" expand_fn = qml.transforms.create_expand_fn( depth=10, - stop_at=self.crit_0, + stop_at=crit_0, docstring=self.doc_0, ) assert expand_fn.__doc__ == "Test docstring." def test_create_expand_fn_expansion(self): """Test expansion with created expand_fn.""" - expand_fn = qml.transforms.create_expand_fn(depth=10, stop_at=self.crit_0) + expand_fn = qml.transforms.create_expand_fn(depth=10, stop_at=crit_0) new_tape = expand_fn(self.tape) assert new_tape.operations[0] == self.tape.operations[0] assert new_tape.operations[1] == self.tape.operations[1] @@ -58,7 +63,7 @@ def test_create_expand_fn_expansion(self): def test_create_expand_fn_dont_expand(self): """Test expansion is skipped with depth=0.""" - expand_fn = qml.transforms.create_expand_fn(depth=0, stop_at=self.crit_0) + expand_fn = qml.transforms.create_expand_fn(depth=0, stop_at=crit_0) new_tape = expand_fn(self.tape) assert new_tape.operations == self.tape.operations @@ -68,7 +73,7 @@ def test_device_and_stopping_expansion(self): that all operations are expanded to match the devices default gate set""" dev = DefaultQubitLegacy(wires=1) - expand_fn = qml.transforms.create_expand_fn(device=dev, depth=10, stop_at=self.crit_0) + expand_fn = qml.transforms.create_expand_fn(device=dev, depth=10, stop_at=crit_0) with qml.queuing.AnnotatedQueue() as q: qml.U1(0.2, wires=0) @@ -249,6 +254,7 @@ def test_decompose_all_nonunitary_generator(self): "SingleExcitationPlus", "DoubleExcitationMinus", "DoubleExcitationPlus", + "GlobalPhase", ] with qml.queuing.AnnotatedQueue() as q: diff --git a/tests/transforms/test_transpile.py b/tests/transforms/test_transpile.py index f4f6e28b80e..ccfde8cc096 100644 --- a/tests/transforms/test_transpile.py +++ b/tests/transforms/test_transpile.py @@ -166,7 +166,7 @@ def circuit(parameters): qml.gradients.param_shift(transpiled_qnode)(params) def test_more_than_2_qubits_raises_anywires(self): - """test that transpile raises an error for an operation with AnyWires that acts on more than 2 qubits""" + """test that transpile raises an error for an operation with num_wires=None that acts on more than 2 qubits""" dev = qml.device("default.qubit", wires=[0, 1, 2]) def circuit(param): @@ -245,7 +245,7 @@ def circuit(param): def test_transpile_ops_anywires_1_qubit(self): """test that transpile does not alter output for expectation value of an observable if the qfunc contains - 1-qubit operations with AnyWires defined for the operation""" + 1-qubit operations with num_wires=None defined for the operation""" dev = qml.device("default.qubit", wires=[0, 1, 2]) def circuit(param): @@ -301,7 +301,7 @@ def test_transpile_mcm(self): def test_transpile_ops_anywires_1_qubit_qnode(self): """test that transpile does not alter output for expectation value of an observable if the qfunc contains - 1-qubit operations with AnyWires defined for the operation""" + 1-qubit operations with num_wires=None defined for the operation""" dev = qml.device("default.qubit", wires=[0, 1, 2]) @qml.qnode(device=dev) diff --git a/tests/workflow/test_construct_batch.py b/tests/workflow/test_construct_batch.py index d855a6323d7..1cf535adc07 100644 --- a/tests/workflow/test_construct_batch.py +++ b/tests/workflow/test_construct_batch.py @@ -114,7 +114,7 @@ def circuit(): p_sliced = get_transform_program(circuit, slice(2, 7, 2)) assert len(p_sliced) == 3 assert p_sliced[0].transform == qml.compile.transform - assert p_sliced[1].transform == qml.devices.preprocess.validate_device_wires.transform + assert p_sliced[1].transform == qml.devices.preprocess.mid_circuit_measurements.transform assert p_sliced[2].transform == qml.devices.preprocess.decompose.transform def test_diff_method_device_gradient(self):