diff --git a/.github/workflows/interface-unit-tests.yml b/.github/workflows/interface-unit-tests.yml index 814772a5d6b..ad7356b0a07 100644 --- a/.github/workflows/interface-unit-tests.yml +++ b/.github/workflows/interface-unit-tests.yml @@ -54,11 +54,6 @@ on: required: false type: string default: '' - disable_new_opmath: - description: Whether to disable the new op_math or not when running the tests - required: false - type: string - default: "False" additional_python_packages: description: Additional Python packages to install separated by a space required: false @@ -246,7 +241,7 @@ jobs: ${{ needs.default-dependency-versions.outputs.pytorch-version }} ${{ inputs.additional_python_packages }} additional_pip_packages_post: ${{ needs.default-dependency-versions.outputs.pennylane-lightning-latest }} - pytest_additional_args: --disable-opmath=${{ inputs.disable_new_opmath }} -W ${{ inputs.python_warning_level }} ${{ inputs.python_warning_level != 'default' && '--continue-on-collection-errors' || '' }} + pytest_additional_args: -W ${{ inputs.python_warning_level }} ${{ inputs.python_warning_level != 'default' && '--continue-on-collection-errors' || '' }} pytest_coverage_flags: ${{ inputs.pytest_coverage_flags }} pytest_markers: torch and not qcut and not finite-diff and not param-shift requirements_file: ${{ github.event_name == 'schedule' && strategy.job-index == 0 && 'torch.txt' || '' }} @@ -282,7 +277,7 @@ jobs: additional_pip_packages: ${{ inputs.additional_python_packages }} additional_pip_packages_post: ${{ needs.default-dependency-versions.outputs.pennylane-lightning-latest }} pytest_coverage_flags: ${{ inputs.pytest_coverage_flags }} - pytest_additional_args: --disable-opmath=${{ inputs.disable_new_opmath }} -W ${{ inputs.python_warning_level }} ${{ inputs.python_warning_level != 'default' && '--continue-on-collection-errors' || '' }} + pytest_additional_args: -W ${{ inputs.python_warning_level }} ${{ inputs.python_warning_level != 'default' && '--continue-on-collection-errors' || '' }} pytest_markers: autograd and not qcut and not finite-diff and not param-shift @@ -320,7 +315,7 @@ jobs: additional_pip_packages_post: ${{ needs.default-dependency-versions.outputs.pennylane-lightning-latest }} pytest_coverage_flags: ${{ inputs.pytest_coverage_flags }} pytest_markers: tf and not qcut and not finite-diff and not param-shift - pytest_additional_args: --splits 3 --group ${{ matrix.group }} --disable-opmath=${{ inputs.disable_new_opmath }} -W ${{ inputs.python_warning_level }} ${{ inputs.python_warning_level != 'default' && '--continue-on-collection-errors' || '' }} + pytest_additional_args: --splits 3 --group ${{ matrix.group }} -W ${{ inputs.python_warning_level }} ${{ inputs.python_warning_level != 'default' && '--continue-on-collection-errors' || '' }} pytest_durations_file_path: '.github/durations/tf_tests_durations.json' requirements_file: ${{ github.event_name == 'schedule' && strategy.job-index == 0 && 'tf.txt' || '' }} @@ -358,7 +353,7 @@ jobs: additional_pip_packages_post: ${{ needs.default-dependency-versions.outputs.pennylane-lightning-latest }} pytest_coverage_flags: ${{ inputs.pytest_coverage_flags }} pytest_markers: jax and not qcut and not finite-diff and not param-shift - pytest_additional_args: --dist=loadscope --splits 4 --group ${{ matrix.group }} --disable-opmath=${{ inputs.disable_new_opmath }} -W ${{ inputs.python_warning_level }} ${{ inputs.python_warning_level != 'default' && '--continue-on-collection-errors' || '' }} + pytest_additional_args: --dist=loadscope --splits 4 --group ${{ matrix.group }} -W ${{ inputs.python_warning_level }} ${{ inputs.python_warning_level != 'default' && '--continue-on-collection-errors' || '' }} pytest_durations_file_path: '.github/durations/jax_tests_durations.json' requirements_file: ${{ github.event_name == 'schedule' && strategy.job-index == 0 && 'jax.txt' || '' }} @@ -395,7 +390,7 @@ jobs: additional_pip_packages_post: ${{ needs.default-dependency-versions.outputs.pennylane-lightning-latest }} pytest_coverage_flags: ${{ inputs.pytest_coverage_flags }} pytest_markers: core and not qcut and not finite-diff and not param-shift - pytest_additional_args: --splits 6 --group ${{ matrix.group }} --disable-opmath=${{ inputs.disable_new_opmath }} -W ${{ inputs.python_warning_level }} ${{ inputs.python_warning_level != 'default' && '--continue-on-collection-errors' || '' }} + pytest_additional_args: --splits 6 --group ${{ matrix.group }} -W ${{ inputs.python_warning_level }} ${{ inputs.python_warning_level != 'default' && '--continue-on-collection-errors' || '' }} pytest_durations_file_path: '.github/durations/core_tests_durations.json' requirements_file: ${{ github.event_name == 'schedule' && strategy.job-index == 0 && 'core.txt' || '' }} @@ -435,7 +430,7 @@ jobs: additional_pip_packages_post: ${{ needs.default-dependency-versions.outputs.pennylane-lightning-latest }} pytest_coverage_flags: ${{ inputs.pytest_coverage_flags }} pytest_markers: all_interfaces - pytest_additional_args: --disable-opmath=${{ inputs.disable_new_opmath }} -W ${{ inputs.python_warning_level }} ${{ inputs.python_warning_level != 'default' && '--continue-on-collection-errors' || '' }} + pytest_additional_args: -W ${{ inputs.python_warning_level }} ${{ inputs.python_warning_level != 'default' && '--continue-on-collection-errors' || '' }} requirements_file: ${{ github.event_name == 'schedule' && strategy.job-index == 0 && 'all_interfaces.txt' || '' }} @@ -468,7 +463,7 @@ jobs: python_version: ${{ matrix.python-version }} pytest_coverage_flags: ${{ inputs.pytest_coverage_flags }} pytest_markers: external - pytest_additional_args: --disable-opmath=${{ inputs.disable_new_opmath }} -W ${{ inputs.python_warning_level }} ${{ inputs.python_warning_level != 'default' && '--continue-on-collection-errors' || '' }} + pytest_additional_args: -W ${{ inputs.python_warning_level }} ${{ inputs.python_warning_level != 'default' && '--continue-on-collection-errors' || '' }} additional_pip_packages: | pyzx matplotlib stim quimb mitiq ply git+https://github.com/PennyLaneAI/pennylane-qiskit.git@master @@ -515,7 +510,7 @@ jobs: python_version: ${{ matrix.python-version }} pytest_coverage_flags: ${{ inputs.pytest_coverage_flags }} pytest_markers: qcut - pytest_additional_args: --disable-opmath=${{ inputs.disable_new_opmath }} -W ${{ inputs.python_warning_level }} ${{ inputs.python_warning_level != 'default' && '--continue-on-collection-errors' || '' }} + pytest_additional_args: -W ${{ inputs.python_warning_level }} ${{ inputs.python_warning_level != 'default' && '--continue-on-collection-errors' || '' }} additional_pip_packages: | kahypar==1.1.7 opt_einsum @@ -557,7 +552,7 @@ jobs: additional_pip_packages_post: ${{ needs.default-dependency-versions.outputs.pennylane-lightning-latest }} pytest_coverage_flags: ${{ inputs.pytest_coverage_flags }} pytest_markers: qchem - pytest_additional_args: --disable-opmath=${{ inputs.disable_new_opmath }} -W ${{ inputs.python_warning_level }} ${{ inputs.python_warning_level != 'default' && '--continue-on-collection-errors' || '' }} + pytest_additional_args: -W ${{ inputs.python_warning_level }} ${{ inputs.python_warning_level != 'default' && '--continue-on-collection-errors' || '' }} additional_pip_packages: | openfermionpyscf basis-set-exchange ${{ inputs.additional_python_packages }} @@ -592,7 +587,7 @@ jobs: branch: ${{ inputs.branch }} coverage_artifact_name: gradients-${{ matrix.config.suite }}-coverage python_version: ${{ matrix.python-version }} - pytest_additional_args: --disable-opmath=${{ inputs.disable_new_opmath }} -W ${{ inputs.python_warning_level }} ${{ inputs.python_warning_level != 'default' && '--continue-on-collection-errors' || '' }} + pytest_additional_args: -W ${{ inputs.python_warning_level }} ${{ inputs.python_warning_level != 'default' && '--continue-on-collection-errors' || '' }} additional_pip_packages: | ${{ needs.default-dependency-versions.outputs.jax-version }} ${{ needs.default-dependency-versions.outputs.tensorflow-version }} @@ -632,7 +627,7 @@ jobs: python_version: ${{ matrix.python-version }} additional_pip_packages_post: ${{ needs.default-dependency-versions.outputs.pennylane-lightning-latest }} pytest_coverage_flags: ${{ inputs.pytest_coverage_flags }} - pytest_additional_args: --disable-opmath=${{ inputs.disable_new_opmath }} -W ${{ inputs.python_warning_level }} ${{ inputs.python_warning_level != 'default' && '--continue-on-collection-errors' || '' }} + pytest_additional_args: -W ${{ inputs.python_warning_level }} ${{ inputs.python_warning_level != 'default' && '--continue-on-collection-errors' || '' }} pytest_markers: data additional_pip_packages: | h5py @@ -681,7 +676,7 @@ jobs: additional_pip_packages_post: ${{ needs.default-dependency-versions.outputs.pennylane-lightning-latest }} pytest_test_directory: pennylane/devices/tests pytest_coverage_flags: ${{ inputs.pytest_coverage_flags }} - pytest_additional_args: --device=${{ matrix.config.device }} --shots=${{ matrix.config.shots }} --disable-opmath=${{ inputs.disable_new_opmath }} -W ${{ inputs.python_warning_level }} ${{ inputs.python_warning_level != 'default' && '--continue-on-collection-errors' || '' }} + pytest_additional_args: --device=${{ matrix.config.device }} --shots=${{ matrix.config.shots }} -W ${{ inputs.python_warning_level }} ${{ inputs.python_warning_level != 'default' && '--continue-on-collection-errors' || '' }} upload-to-codecov: diff --git a/.github/workflows/legacy_op_math.yml b/.github/workflows/legacy_op_math.yml deleted file mode 100644 index b78fe7684d6..00000000000 --- a/.github/workflows/legacy_op_math.yml +++ /dev/null @@ -1,16 +0,0 @@ -name: Legacy opmath tests - -on: - schedule: - - cron: "0 2 * * *" - workflow_dispatch: - -jobs: - tests: - uses: ./.github/workflows/interface-unit-tests.yml - secrets: - codecov_token: ${{ secrets.CODECOV_TOKEN }} - with: - branch: 'master' - run_lightened_ci: false - disable_new_opmath: "True" diff --git a/doc/conf.py b/doc/conf.py index 99063fdf06d..193c7b91270 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -113,8 +113,8 @@ # built documents. import pennylane +pennylane.Hamiltonian = pennylane.ops.op_math.linear_combination.Hamiltonian -pennylane.Hamiltonian = pennylane.ops.Hamiltonian # The full version, including alpha/beta/rc tags. release = pennylane.__version__ diff --git a/doc/development/deprecations.rst b/doc/development/deprecations.rst index 34b053834d0..9fbcb25cb34 100644 --- a/doc/development/deprecations.rst +++ b/doc/development/deprecations.rst @@ -44,58 +44,47 @@ Pending deprecations - Deprecated in v0.39 - Will be removed in v0.40 -New operator arithmetic deprecations -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -In PennyLane v0.39, the legacy operator arithmetic system has been deprecated. Check out the :ref:`Updated operators ` page -for details on how to port your legacy code to the new system. The old system is still accessible via :func:`~.disable_new_opmath`, though -it is not recommended, as the old system is deprecated and will be removed in the v0.40 release. The following functionality will explicitly -raise a deprecation warning when used: - -* In PennyLane v0.39, legacy operator arithmetic has been deprecated. This includes :func:`~pennylane.operation.enable_new_opmath`, - :func:`~pennylane.operation.disable_new_opmath`, :class:`~pennylane.ops.Hamiltonian`, and :class:`~pennylane.operation.Tensor`. Note - that when new operator arithmetic is enabled, ``qml.Hamiltonian`` will continue to dispatch to :class:`~pennylane.ops.LinearCombination`; - this behaviour is not deprecated. - - - Deprecated in v0.39 - - Will be removed in v0.40 - -* :meth:`~pennylane.pauli.PauliSentence.hamiltonian` and :meth:`~pennylane.pauli.PauliWord.hamiltonian` are deprecated. Instead, please use - :meth:`~pennylane.pauli.PauliSentence.operation` and :meth:`~pennylane.pauli.PauliWord.operation` respectively. - - - Deprecated in v0.39 - - Will be removed in v0.40 - -* :func:`pennylane.pauli.simplify` is deprecated. Instead, please use :func:`pennylane.simplify` or :meth:`~pennylane.operation.Operator.simplify`. - - - Deprecated in v0.39 - - Will be removed in v0.40 - -* ``op.ops`` and ``op.coeffs`` will be deprecated in the future. Use +* ``op.ops`` and ``op.coeffs`` for ``Sum`` and ``Prod`` will be removed in the future. Use :meth:`~.Operator.terms` instead. - - Added and deprecated for ``Sum`` and ``Prod`` instances in v0.35 + - deprecated in v0.35 * Accessing terms of a tensor product (e.g., ``op = X(0) @ X(1)``) via ``op.obs`` is deprecated with new operator arithmetic. A user should use :class:`op.operands <~.CompositeOp>` instead. - Deprecated in v0.36 - -Other deprecations -~~~~~~~~~~~~~~~~~~ - -* PennyLane Lightning and Catalyst will no longer support ``manylinux2014`` (GLIBC 2.17) compatibile Linux operating systems, and will be migrated to ``manylinux_2_28`` (GLIBC 2.28). See `pypa/manylinux `_ for additional details. - - - Last supported version of ``manylinux2014`` with v0.36 - - Fully migrated to ``manylinux_2_28`` with v0.37 - * ``MultiControlledX`` is the only controlled operation that still supports specifying control values with a bit string. In the future, it will no longer accepts strings as control values. - Deprecated in v0.36 - Will be removed in v0.37 +Completed removal of legacy operator arithmetic +----------------------------------------------- + +In PennyLane v0.40, the legacy operator arithmetic system has been removed, and is fully replaced by the new +operator arithmetic functionality that was introduced in v0.36. Check out the :ref:`Updated operators ` page +for details on how to port your legacy code to the new system. The following functionality has been removed: + +* In PennyLane v0.40, legacy operator arithmetic has been removed. This includes :func:`~pennylane.operation.enable_new_opmath`, + :func:`~pennylane.operation.disable_new_opmath`, :class:`~pennylane.ops.Hamiltonian`, and :class:`~pennylane.operation.Tensor`. Note + that ``qml.Hamiltonian`` will continue to dispatch to :class:`~pennylane.ops.LinearCombination`. + + - Deprecated in v0.39 + - Removed in v0.40 + +* :meth:`~pennylane.pauli.PauliSentence.hamiltonian` and :meth:`~pennylane.pauli.PauliWord.hamiltonian` has been removed. Instead, please use + :meth:`~pennylane.pauli.PauliSentence.operation` and :meth:`~pennylane.pauli.PauliWord.operation` respectively. + + - Deprecated in v0.39 + - Removed in v0.40 + +* :func:`pennylane.pauli.simplify` has been removed. Instead, please use :func:`pennylane.simplify` or :meth:`~pennylane.operation.Operator.simplify`. + + - Deprecated in v0.39 + - Removed in v0.40 + Completed deprecation cycles ---------------------------- @@ -164,6 +153,11 @@ Completed deprecation cycles - Deprecated in v0.39 - Removed in v0.40 +* PennyLane Lightning and Catalyst will no longer support ``manylinux2014`` (GLIBC 2.17) compatibile Linux operating systems, and will be migrated to ``manylinux_2_28`` (GLIBC 2.28). See `pypa/manylinux `_ for additional details. + + - Last supported version of ``manylinux2014`` with v0.36 + - Fully migrated to ``manylinux_2_28`` with v0.37 + * The ``simplify`` argument in ``qml.Hamiltonian`` and ``qml.ops.LinearCombination`` has been removed. Instead, ``qml.simplify()`` can be called on the constructed operator. diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md index d7c504dfed4..3f1332c3261 100644 --- a/doc/releases/changelog-dev.md +++ b/doc/releases/changelog-dev.md @@ -66,6 +66,12 @@

Breaking changes 💔

+* Legacy operator arithmetic has been removed. This includes `qml.ops.Hamiltonian`, `qml.operation.Tensor`, + `qml.operation.enable_new_opmath`, `qml.operation.disable_new_opmath`, and `qml.operation.convert_to_legacy_H`. + Note that `qml.Hamiltonian` will continue to dispatch to `qml.ops.LinearCombination`. For more information, + check out the [updated operator troubleshooting page](https://docs.pennylane.ai/en/stable/news/new_opmath.html). + [(#6548)](https://github.com/PennyLaneAI/pennylane/pull/6548) + * The developer-facing `qml.utils` module has been removed. Specifically, the following 4 sets of functions have been either moved or removed[(#6588)](https://github.com/PennyLaneAI/pennylane/pull/6588): diff --git a/pennylane/__init__.py b/pennylane/__init__.py index 1e71e9d1691..334b860e492 100644 --- a/pennylane/__init__.py +++ b/pennylane/__init__.py @@ -74,6 +74,7 @@ ) from pennylane.ops import * from pennylane.ops import adjoint, ctrl, cond, exp, sum, pow, prod, s_prod +from pennylane.ops import LinearCombination as Hamiltonian from pennylane.templates import layer from pennylane.templates.embeddings import * from pennylane.templates.layers import * @@ -165,14 +166,7 @@ class PennyLaneDeprecationWarning(UserWarning): """Warning raised when a PennyLane feature is being deprecated.""" -del globals()["Hamiltonian"] - - def __getattr__(name): - if name == "Hamiltonian": - if pennylane.operation.active_new_opmath(): - return pennylane.ops.LinearCombination - return pennylane.ops.Hamiltonian if name == "plugin_devices": return pennylane.devices.device_constructor.plugin_devices diff --git a/pennylane/data/attributes/operator/operator.py b/pennylane/data/attributes/operator/operator.py index 1a048c0c280..c9685640df7 100644 --- a/pennylane/data/attributes/operator/operator.py +++ b/pennylane/data/attributes/operator/operator.py @@ -24,7 +24,7 @@ import pennylane as qml from pennylane.data.base.attribute import DatasetAttribute from pennylane.data.base.hdf5 import HDF5Group, h5py -from pennylane.operation import Operator, Tensor +from pennylane.operation import Operator from ._wires import wires_to_json @@ -44,8 +44,6 @@ class DatasetOperator(Generic[Op], DatasetAttribute[HDF5Group, Op, Op]): arguments. - Hyperparameters are not used or are automatically derived by ``__init__()``. - Almost all operators meet these conditions. This type also supports serializing the - ``Hamiltonian`` and ``Tensor`` operators. """ type_id = "operator" @@ -56,13 +54,9 @@ def supported_ops(cls) -> frozenset[Type[Operator]]: """Set of supported operators.""" return frozenset( ( - # pennylane/operation/Tensor - Tensor, # pennylane/ops/qubit/arithmetic_qml.py qml.QubitCarry, qml.QubitSum, - # pennylane/ops/qubit/hamiltonian.py - qml.ops.Hamiltonian, # pennylane/ops/op_math/linear_combination.py qml.ops.LinearCombination, # pennylane/ops/op_math - prod.py, s_prod.py, sum.py @@ -219,10 +213,7 @@ def _ops_to_hdf5( f"Serialization of operator type '{type(op).__name__}' is not supported." ) - if isinstance(op, Tensor): - self._ops_to_hdf5(bind, op_key, op.obs) - op_wire_labels.append("null") - elif isinstance(op, (qml.ops.Hamiltonian, qml.ops.LinearCombination)): + if isinstance(op, qml.ops.LinearCombination): coeffs, ops = op.terms() ham_grp = self._ops_to_hdf5(bind, op_key, ops) ham_grp["hamiltonian_coeffs"] = coeffs @@ -260,10 +251,7 @@ def _hdf5_to_ops(self, bind: HDF5Group) -> list[Operator]: op_key = f"op_{i}" op_cls = self._supported_ops_dict()[op_class_name] - if op_cls is Tensor: - prod_op = qml.ops.Prod if qml.operation.active_new_opmath() else Tensor - ops.append(prod_op(*self._hdf5_to_ops(bind[op_key]))) - elif op_cls in (qml.ops.Hamiltonian, qml.ops.LinearCombination): + if op_cls is qml.ops.LinearCombination: ops.append( qml.Hamiltonian( coeffs=list(bind[op_key]["hamiltonian_coeffs"]), diff --git a/pennylane/devices/_legacy_device.py b/pennylane/devices/_legacy_device.py index dd1b97dd4a5..93342f9d2e2 100644 --- a/pennylane/devices/_legacy_device.py +++ b/pennylane/devices/_legacy_device.py @@ -35,8 +35,8 @@ State, Variance, ) -from pennylane.operation import Observable, Operation, Operator, StatePrepBase, Tensor -from pennylane.ops import Hamiltonian, LinearCombination, Prod, SProd, Sum +from pennylane.operation import Observable, 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 from pennylane.wires import WireError, Wires @@ -465,22 +465,18 @@ def execute(self, queue, observables, parameters=None, **kwargs): for mp in observables: obs = mp.obs if isinstance(mp, MeasurementProcess) and mp.obs is not None else mp - if isinstance(obs, Tensor): - wires = [ob.wires for ob in obs.obs] - else: - wires = obs.wires if mp.return_type is Expectation: - results.append(self.expval(obs.name, wires, obs.parameters)) + results.append(self.expval(obs.name, obs.wires, obs.parameters)) elif mp.return_type is Variance: - results.append(self.var(obs.name, wires, obs.parameters)) + results.append(self.var(obs.name, obs.wires, obs.parameters)) elif mp.return_type is Sample: - results.append(np.array(self.sample(obs.name, wires, obs.parameters))) + results.append(np.array(self.sample(obs.name, obs.wires, obs.parameters))) elif mp.return_type is Probability: - results.append(list(self.probability(wires=wires).values())) + results.append(list(self.probability(wires=obs.wires).values())) elif mp.return_type is State: raise qml.QuantumFunctionError("Returning the state is not supported") @@ -680,7 +676,7 @@ def default_expand_fn(self, circuit, max_expansion=10): ) obs_on_same_wire = len(circuit.obs_sharing_wires) > 0 or comp_basis_sampled_multi_measure obs_on_same_wire &= not any( - isinstance(o, (Hamiltonian, LinearCombination)) for o in circuit.obs_sharing_wires + isinstance(o, LinearCombination) for o in circuit.obs_sharing_wires ) ops_not_supported = not all(self.stopping_condition(op) for op in circuit.operations) @@ -753,11 +749,11 @@ def null_postprocess(results): is_analytic_or_shadow = not finite_shots or has_shadow all_obs_usable = self._all_multi_term_obs_supported(circuit) exists_multi_term_obs = any( - isinstance(m.obs, (Hamiltonian, Sum, Prod, SProd)) for m in circuit.measurements + isinstance(m.obs, (Sum, Prod, SProd)) for m in circuit.measurements ) has_overlapping_wires = len(circuit.obs_sharing_wires) > 0 single_hamiltonian = len(circuit.measurements) == 1 and isinstance( - circuit.measurements[0].obs, (Hamiltonian, Sum) + circuit.measurements[0].obs, Sum ) single_hamiltonian_with_grouping_known = ( single_hamiltonian and circuit.measurements[0].obs.grouping_indices is not None @@ -779,10 +775,10 @@ def null_postprocess(results): elif single_hamiltonian_with_grouping_known: # Use qwc grouping if the circuit contains a single measurement of a - # Hamiltonian/Sum with grouping indices already calculated. + # Sum with grouping indices already calculated. circuits, processing_fn = qml.transforms.split_non_commuting(circuit, "qwc") - elif any(isinstance(m.obs, (Hamiltonian, LinearCombination)) for m in circuit.measurements): + elif any(isinstance(m.obs, LinearCombination) for m in circuit.measurements): # Otherwise, use wire-based grouping if the circuit contains a Hamiltonian # that is potentially very large. @@ -821,7 +817,6 @@ def _all_multi_term_obs_supported(self, circuit): return False if mp.obs.name in ( - "Hamiltonian", "Sum", "Prod", "SProd", @@ -1006,23 +1001,7 @@ def check_validity(self, queue, observables): if o is None: continue - if isinstance(o, Tensor): - # TODO: update when all capabilities keys changed to "supports_tensor_observables" - supports_tensor = self.capabilities().get( - "supports_tensor_observables", False - ) or self.capabilities().get("tensor_observables", False) - if not supports_tensor: - raise qml.DeviceError( - f"Tensor observables not supported on device {self.short_name}" - ) - - for i in o.obs: - if not self.supports_observable(i.name): - raise qml.DeviceError( - f"Observable {i.name} not supported on device {self.short_name}" - ) - - elif isinstance(o, qml.ops.Prod): + if isinstance(o, qml.ops.Prod): supports_prod = self.supports_observable(o.name) if not supports_prod: diff --git a/pennylane/devices/default_clifford.py b/pennylane/devices/default_clifford.py index 5d6ecdbd69a..a3fb9d941a2 100644 --- a/pennylane/devices/default_clifford.py +++ b/pennylane/devices/default_clifford.py @@ -73,7 +73,6 @@ "Hermitian", "Identity", "Projector", - "Hamiltonian", "LinearCombination", "Sum", "SProd", @@ -147,10 +146,9 @@ def _pl_op_to_stim(op): return stim_op, " ".join(stim_tg) -def _pl_obs_to_linear_comb(meas_op): +def _pl_obs_to_linear_comb(meas_obs): """Convert a PennyLane observable to a linear combination of Pauli strings""" - meas_obs = qml.operation.convert_to_opmath(meas_op) meas_rep = meas_obs.pauli_rep # Use manual decomposition for enabling Hermitian and partial Projector support @@ -160,11 +158,11 @@ def _pl_obs_to_linear_comb(meas_op): # A Pauli decomposition for the observable must exist if meas_rep is None: raise NotImplementedError( - f"default.clifford doesn't support expectation value calculation with {type(meas_op).__name__} at the moment." + f"default.clifford doesn't support expectation value calculation with {type(meas_obs).__name__} at the moment." ) coeffs, paulis = np.array(list(meas_rep.values())), [] - meas_op_wires = list(meas_op.wires) + meas_op_wires = list(meas_obs.wires) for pw in meas_rep: p_wire, p_word = pw.keys(), pw.values() if not p_word: @@ -792,8 +790,7 @@ def _measure_expectation(self, meas, tableau_simulator, **kwargs): def _measure_variance(self, meas, tableau_simulator, **_): """Measure the variance with respect to the state of simulator device.""" - meas_obs = qml.operation.convert_to_opmath(meas.obs) - meas_obs1 = meas_obs.simplify() + meas_obs1 = meas.obs.simplify() meas_obs2 = (meas_obs1**2).simplify() # use the naive formula for variance, i.e., Var(Q) = ⟨𝑄^2⟩−⟨𝑄⟩^2 @@ -1029,9 +1026,7 @@ def _sample_expectation(self, meas, stim_circuit, shots, seed): def _sample_variance(self, meas, stim_circuit, shots, seed): """Measure the variance with respect to samples from simulator device.""" # Get the observable for the expectation value measurement - meas_op = meas.obs - meas_obs = qml.operation.convert_to_opmath(meas_op) - meas_obs1 = meas_obs.simplify() + meas_obs1 = meas.obs.simplify() meas_obs2 = (meas_obs1**2).simplify() # use the naive formula for variance, i.e., Var(Q) = ⟨𝑄^2⟩−⟨𝑄⟩^2 diff --git a/pennylane/devices/default_qubit.py b/pennylane/devices/default_qubit.py index 13788e1e4c9..4d2f4d5a76e 100644 --- a/pennylane/devices/default_qubit.py +++ b/pennylane/devices/default_qubit.py @@ -91,12 +91,6 @@ def observable_accepts_sampling(obs: qml.operation.Operator) -> bool: if isinstance(obs, qml.ops.SymbolicOp): return observable_accepts_sampling(obs.base) - if isinstance(obs, qml.ops.Hamiltonian): - return all(observable_accepts_sampling(o) for o in obs.ops) - - if isinstance(obs, qml.operation.Tensor): - return all(observable_accepts_sampling(o) for o in obs.obs) - return obs.has_diagonalizing_gates @@ -109,12 +103,6 @@ def observable_accepts_analytic(obs: qml.operation.Operator, is_expval=False) -> if isinstance(obs, qml.ops.SymbolicOp): return observable_accepts_analytic(obs.base, is_expval) - if isinstance(obs, qml.ops.Hamiltonian): - return all(observable_accepts_analytic(o, is_expval) for o in obs.ops) - - if isinstance(obs, qml.operation.Tensor): - return all(observable_accepts_analytic(o, is_expval) for o in obs.obs) - if is_expval and isinstance(obs, (qml.ops.SparseHamiltonian, qml.ops.Hermitian)): return True diff --git a/pennylane/devices/default_qutrit_mixed.py b/pennylane/devices/default_qutrit_mixed.py index 01f341b8cc1..fbea3d0edee 100644 --- a/pennylane/devices/default_qutrit_mixed.py +++ b/pennylane/devices/default_qutrit_mixed.py @@ -55,11 +55,9 @@ def observable_stopping_condition(obs: qml.operation.Operator) -> bool: """Specifies whether an observable is accepted by DefaultQutritMixed.""" - if isinstance(obs, qml.operation.Tensor): - return all(observable_stopping_condition(observable) for observable in obs.obs) if obs.name in {"Prod", "Sum"}: return all(observable_stopping_condition(observable) for observable in obs.operands) - if obs.name in {"LinearCombination", "Hamiltonian"}: + if obs.name == "LinearCombination": return all(observable_stopping_condition(observable) for observable in obs.terms()[1]) if obs.name == "SProd": return observable_stopping_condition(obs.base) diff --git a/pennylane/devices/default_tensor.py b/pennylane/devices/default_tensor.py index 64046df1c2c..e6f349c2243 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, Tensor +from pennylane.operation import Observable, Operation from pennylane.ops import LinearCombination, Prod, SProd, Sum from pennylane.tape import QuantumScript, QuantumScriptOrBatch from pennylane.templates.subroutines.trotter import _recursive_expression @@ -125,7 +125,6 @@ "Identity", "Projector", "SparseHamiltonian", - "Hamiltonian", "LinearCombination", "Sum", "SProd", @@ -1028,12 +1027,6 @@ def expval_core(obs: Observable, device) -> float: return device._local_expectation(qml.matrix(obs), tuple(obs.wires)) -@expval_core.register -def expval_core_tensor(obs: Tensor, device) -> float: - """Computes the expval of a Tensor.""" - return expval_core(Prod(*obs._args), device) - - @expval_core.register def expval_core_prod(obs: Prod, device) -> float: """Computes the expval of a Prod.""" diff --git a/pennylane/devices/preprocess.py b/pennylane/devices/preprocess.py index beec009529f..0d75b88284f 100644 --- a/pennylane/devices/preprocess.py +++ b/pennylane/devices/preprocess.py @@ -26,7 +26,7 @@ import pennylane as qml from pennylane import Snapshot, transform from pennylane.measurements import SampleMeasurement, StateMeasurement -from pennylane.operation import StatePrepBase, Tensor +from pennylane.operation import StatePrepBase from pennylane.tape import QuantumScript, QuantumScriptBatch from pennylane.typing import PostprocessingFn from pennylane.wires import WireError @@ -458,17 +458,10 @@ def validate_observables( >>> validate_observables(tape, accepted_observable) qml.DeviceError: Observable Z(0) + Y(0) not supported on device - Note that if the observable is a :class:`~.Tensor`, the validation is run on each object in the - ``Tensor`` instead. - """ for m in tape.measurements: - if m.obs is not None: - if isinstance(m.obs, Tensor): - if any(not stopping_condition(o) for o in m.obs.obs): - raise qml.DeviceError(f"Observable {repr(m.obs)} not supported on {name}") - elif not stopping_condition(m.obs): - raise qml.DeviceError(f"Observable {repr(m.obs)} not supported on {name}") + if m.obs is not None and not stopping_condition(m.obs): + raise qml.DeviceError(f"Observable {repr(m.obs)} not supported on {name}") return (tape,), null_postprocessing diff --git a/pennylane/devices/qubit/measure.py b/pennylane/devices/qubit/measure.py index da4d51b7aec..2e4745af378 100644 --- a/pennylane/devices/qubit/measure.py +++ b/pennylane/devices/qubit/measure.py @@ -18,7 +18,6 @@ from scipy.sparse import csr_matrix -import pennylane as qml from pennylane import math from pennylane.measurements import ( ExpectationMP, @@ -26,7 +25,7 @@ MeasurementValue, StateMeasurement, ) -from pennylane.ops import Hamiltonian, LinearCombination, Sum +from pennylane.ops import LinearCombination, Sum from pennylane.pauli.conversion import is_pauli_sentence, pauli_sentence from pennylane.typing import TensorLike from pennylane.wires import Wires @@ -144,7 +143,7 @@ def full_dot_products( def sum_of_terms_method( measurementprocess: ExpectationMP, state: TensorLike, is_state_batched: bool = False ) -> TensorLike: - """Measure the expecation value of the state when the measured observable is a ``Hamiltonian`` or ``Sum`` + """Measure the expectation value of the state when the measured observable is a ``Hamiltonian`` or ``Sum`` and it must be backpropagation compatible. Args: @@ -155,17 +154,11 @@ def sum_of_terms_method( Returns: TensorLike: the result of the measurement """ - if isinstance(measurementprocess.obs, Sum): - # Recursively call measure on each term, so that the best measurement method can - # be used for each term - return sum( - measure(ExpectationMP(term), state, is_state_batched=is_state_batched) - for term in measurementprocess.obs - ) - # else hamiltonian + # Recursively call measure on each term, so that the best measurement method can + # be used for each term return sum( - c * measure(ExpectationMP(t), state, is_state_batched=is_state_batched) - for c, t in zip(*measurementprocess.obs.terms()) + measure(ExpectationMP(term), state, is_state_batched=is_state_batched) + for term in measurementprocess.obs ) @@ -195,7 +188,7 @@ def get_measurement_function( return full_dot_products backprop_mode = math.get_interface(state, *measurementprocess.obs.data) != "numpy" - if isinstance(measurementprocess.obs, (Hamiltonian, LinearCombination)): + if isinstance(measurementprocess.obs, LinearCombination): # need to work out thresholds for when it's faster to use "backprop mode" if backprop_mode: @@ -204,13 +197,6 @@ def get_measurement_function( if not all(obs.has_sparse_matrix for obs in measurementprocess.obs.terms()[1]): return sum_of_terms_method - # Hamiltonian.sparse_matrix raises a ValueError for this scenario. - if isinstance(measurementprocess.obs, Hamiltonian) and any( - any(len(o.wires) > 1 for o in qml.operation.Tensor(op).obs) - for op in measurementprocess.obs.ops - ): - return sum_of_terms_method - return csr_dot_products if isinstance(measurementprocess.obs, Sum): diff --git a/pennylane/devices/qubit/sampling.py b/pennylane/devices/qubit/sampling.py index 85c89de3701..fbdba2dae2f 100644 --- a/pennylane/devices/qubit/sampling.py +++ b/pennylane/devices/qubit/sampling.py @@ -25,7 +25,7 @@ ShadowExpvalMP, Shots, ) -from pennylane.ops import Hamiltonian, LinearCombination, Prod, SProd, Sum +from pennylane.ops import LinearCombination, Prod, SProd, Sum from pennylane.typing import TensorLike from .apply_operation import apply_operation @@ -158,7 +158,7 @@ def get_num_shots_and_executions(tape: qml.tape.QuantumScript) -> tuple[int, int num_shots = 0 for group in groups: if isinstance(group[0], ExpectationMP) and isinstance( - group[0].obs, (qml.ops.Hamiltonian, qml.ops.LinearCombination) + group[0].obs, qml.ops.LinearCombination ): H_executions = _get_num_executions_for_expval_H(group[0].obs) num_executions += H_executions @@ -238,9 +238,7 @@ def measure_with_samples( groups, indices = _group_measurements(mps) all_res = [] for group in groups: - if isinstance(group[0], ExpectationMP) and isinstance( - group[0].obs, (Hamiltonian, LinearCombination) - ): + if isinstance(group[0], ExpectationMP) and isinstance(group[0].obs, LinearCombination): measure_fn = _measure_hamiltonian_with_samples elif isinstance(group[0], ExpectationMP) and isinstance(group[0].obs, Sum): measure_fn = _measure_sum_with_samples diff --git a/pennylane/devices/qutrit_mixed/measure.py b/pennylane/devices/qutrit_mixed/measure.py index 3489cb3ee35..1feefa0ec3d 100644 --- a/pennylane/devices/qutrit_mixed/measure.py +++ b/pennylane/devices/qutrit_mixed/measure.py @@ -27,7 +27,7 @@ StateMP, VarianceMP, ) -from pennylane.ops import Hamiltonian, Sum +from pennylane.ops import Sum from pennylane.typing import TensorLike from pennylane.wires import Wires @@ -221,28 +221,16 @@ def calculate_expval_sum_of_terms( Returns: TensorLike: the expectation value of the sum of Hamiltonian observable wrt the state. """ - if isinstance(measurementprocess.obs, Sum): - # Recursively call measure on each term, so that the best measurement method can - # be used for each term - return sum( - measure( - ExpectationMP(term), - state, - is_state_batched=is_state_batched, - readout_errors=readout_errors, - ) - for term in measurementprocess.obs - ) - # else hamiltonian + # Recursively call measure on each term, so that the best measurement method can + # be used for each term return sum( - c - * measure( - ExpectationMP(t), + measure( + ExpectationMP(term), state, is_state_batched=is_state_batched, readout_errors=readout_errors, ) - for c, t in zip(*measurementprocess.obs.terms()) + for term in measurementprocess.obs ) @@ -262,7 +250,7 @@ def get_measurement_function( """ if isinstance(measurementprocess, StateMeasurement): if isinstance(measurementprocess, ExpectationMP): - if isinstance(measurementprocess.obs, (Hamiltonian, Sum)): + if isinstance(measurementprocess.obs, Sum): return calculate_expval_sum_of_terms if measurementprocess.obs.has_matrix: return calculate_expval diff --git a/pennylane/devices/qutrit_mixed/sampling.py b/pennylane/devices/qutrit_mixed/sampling.py index 0e5e661f085..f7ad19c97d1 100644 --- a/pennylane/devices/qutrit_mixed/sampling.py +++ b/pennylane/devices/qutrit_mixed/sampling.py @@ -194,14 +194,11 @@ def _measure_sum_with_samples( prng_key=None, readout_errors: list[Callable] = None, ): - """Compute expectation values of Sum or Hamiltonian Observables""" - # mp.obs returns is the list of observables for Sum, - # mp.obs.terms()[1] returns is the list of observables for Hamiltonian - obs_terms = mp.obs if isinstance(mp.obs, Sum) else mp.obs.terms()[1] + """Compute expectation values of Sum Observables""" def _sum_for_single_shot(s): results = [] - for term in obs_terms: + for term in mp.obs: results.append( measure_with_samples( ExpectationMP(term), @@ -214,10 +211,6 @@ def _sum_for_single_shot(s): ) ) - if isinstance(mp.obs, qml.ops.Hamiltonian): - # If Hamiltonian apply coefficients - return sum((c * res for c, res in zip(mp.obs.terms()[0], results))) - return sum(results) if shots.has_partitioned_shots: @@ -462,7 +455,7 @@ def measure_with_samples( TensorLike[Any]: Sample measurement results """ - if isinstance(mp, ExpectationMP) and isinstance(mp.obs, (qml.ops.Hamiltonian, Sum)): + if isinstance(mp, ExpectationMP) and isinstance(mp.obs, Sum): measure_fn = _measure_sum_with_samples else: # measure with the usual method (rotate into the measurement basis) diff --git a/pennylane/devices/tests/conftest.py b/pennylane/devices/tests/conftest.py index bcd48a3f164..217aca0938e 100755 --- a/pennylane/devices/tests/conftest.py +++ b/pennylane/devices/tests/conftest.py @@ -14,7 +14,6 @@ """Contains shared fixtures for the device tests.""" import argparse import os -from warnings import warn import numpy as np import pytest @@ -219,23 +218,6 @@ def pytest_addoption(parser): ) -# pylint: disable=eval-used -@pytest.fixture(scope="session", autouse=True) -def disable_opmath_if_requested(request): - """Check the value of the --disable-opmath option and turn off - if True before running the tests""" - disable_opmath = request.config.getoption("--disable-opmath") - # value from yaml file is a string, convert to boolean - if eval(disable_opmath): - warn( - "Disabling the new Operator arithmetic system for legacy support. " - "If you need help troubleshooting your code, please visit " - "https://docs.pennylane.ai/en/stable/news/new_opmath.html", - UserWarning, - ) - qml.operation.disable_new_opmath(warn=False) - - def pytest_generate_tests(metafunc): """Set up device_kwargs fixture from command line options. diff --git a/pennylane/devices/tests/test_measurements.py b/pennylane/devices/tests/test_measurements.py index 372ec5c8084..ef0bfdb7f94 100644 --- a/pennylane/devices/tests/test_measurements.py +++ b/pennylane/devices/tests/test_measurements.py @@ -51,7 +51,6 @@ qml.Projector(np.array([0, 1]), wires=[0]), ], "SparseHamiltonian": qml.SparseHamiltonian(csr_matrix(np.eye(8)), wires=[0, 1, 2]), - "Hamiltonian": qml.Hamiltonian([1, 1], [qml.Z(0), qml.X(0)]), "Prod": qml.prod(qml.X(0), qml.Z(1)), "SProd": qml.s_prod(0.1, qml.Z(0)), "Sum": qml.sum(qml.s_prod(0.1, qml.Z(0)), qml.prod(qml.X(0), qml.Z(1))), @@ -160,9 +159,7 @@ def circuit(): class TestHamiltonianSupport: """Separate test to ensure that the device can differentiate Hamiltonian observables.""" - @pytest.mark.parametrize("ham_constructor", [qml.ops.Hamiltonian, qml.ops.LinearCombination]) - @pytest.mark.filterwarnings("ignore::pennylane.PennyLaneDeprecationWarning") - def test_hamiltonian_diff(self, ham_constructor, device_kwargs, tol): + def test_hamiltonian_diff(self, device_kwargs, tol): """Tests a simple VQE gradient using parameter-shift rules.""" device_kwargs["wires"] = 1 @@ -175,7 +172,7 @@ def circuit(coeffs, param): qml.RX(param, wires=0) qml.RY(param, wires=0) return qml.expval( - ham_constructor( + qml.Hamiltonian( coeffs, [qml.X(0), qml.Z(0)], ) diff --git a/pennylane/gradients/hadamard_gradient.py b/pennylane/gradients/hadamard_gradient.py index beb9faf828f..5335bd0a060 100644 --- a/pennylane/gradients/hadamard_gradient.py +++ b/pennylane/gradients/hadamard_gradient.py @@ -371,17 +371,14 @@ def _expval_hadamard_grad(tape, argnum, aux_wire): measurements = [] # Add the Y measurement on the aux qubit for m in tape.measurements: - if isinstance(m.obs, qml.operation.Tensor): - obs_new = m.obs.obs.copy() - elif m.obs: + if m.obs: obs_new = [m.obs] else: m_wires = m.wires if len(m.wires) > 0 else tape.wires obs_new = [qml.Z(i) for i in m_wires] obs_new.append(qml.Y(aux_wire)) - obs_type = qml.prod if qml.operation.active_new_opmath() else qml.operation.Tensor - obs_new = obs_type(*obs_new) + obs_new = qml.prod(*obs_new) if isinstance(m, qml.measurements.ExpectationMP): measurements.append(qml.expval(op=obs_new)) diff --git a/pennylane/gradients/parameter_shift.py b/pennylane/gradients/parameter_shift.py index 63fe21f9eb8..5c25237db58 100644 --- a/pennylane/gradients/parameter_shift.py +++ b/pennylane/gradients/parameter_shift.py @@ -62,15 +62,6 @@ def _square_observable(obs): """Returns the square of an observable.""" - if isinstance(obs, qml.operation.Tensor): - # Observable is a tensor, we must consider its - # component observables independently. Note that - # we assume all component observables are on distinct wires. - components_squared = [ - NONINVOLUTORY_OBS[o.name](o) for o in obs.obs if o.name in NONINVOLUTORY_OBS - ] - return qml.operation.Tensor(*components_squared) - if isinstance(obs, qml.ops.Prod): components_squared = [ NONINVOLUTORY_OBS[o.name](o) for o in obs if o.name in NONINVOLUTORY_OBS @@ -380,7 +371,7 @@ def expval_param_shift( op, op_idx, _ = tape.get_operation(idx) - if op.name in ["Hamiltonian", "LinearCombination"]: + if op.name == "LinearCombination": # operation is a Hamiltonian if tape[op_idx].return_type is not qml.measurements.Expectation: raise ValueError( @@ -634,12 +625,7 @@ def _get_non_involuntory_indices(tape, var_indices): for i in var_indices: obs = tape.measurements[i].obs - if isinstance(obs, qml.operation.Tensor): - # Observable is a tensor product, we must investigate all constituent observables. - if any(o.name in NONINVOLUTORY_OBS for o in tape.measurements[i].obs.obs): - non_involutory_indices.append(i) - - elif isinstance(tape.measurements[i].obs, qml.ops.Prod): + if isinstance(tape.measurements[i].obs, qml.ops.Prod): if any(o.name in NONINVOLUTORY_OBS for o in tape.measurements[i].obs): non_involutory_indices.append(i) @@ -695,7 +681,7 @@ def var_param_shift(tape, argnum, shifts=None, gradient_recipes=None, f0=None, b for i in var_indices: obs = new_measurements[i].obs new_measurements[i] = qml.expval(op=obs) - if obs.name in ["Hamiltonian", "LinearCombination", "Sum"]: + if obs.name in ["LinearCombination", "Sum"]: first_obs_idx = len(tape.operations) for t_idx in reversed(range(len(tape.trainable_params))): op, op_idx, _ = tape.get_operation(t_idx) diff --git a/pennylane/measurements/probs.py b/pennylane/measurements/probs.py index 64e9e86bffb..77c9f61e4df 100644 --- a/pennylane/measurements/probs.py +++ b/pennylane/measurements/probs.py @@ -116,7 +116,7 @@ def circuit(): return ProbabilityMP(obs=op) - if isinstance(op, (qml.ops.Hamiltonian, qml.ops.LinearCombination)): + if isinstance(op, qml.ops.LinearCombination): raise qml.QuantumFunctionError("Hamiltonians are not supported for rotating probabilities.") if op is not None and not qml.math.is_abstract(op) and not op.has_diagonalizing_gates: diff --git a/pennylane/operation.py b/pennylane/operation.py index 1cc41052f6f..10452589156 100644 --- a/pennylane/operation.py +++ b/pennylane/operation.py @@ -120,12 +120,11 @@ ~CVObservable ~CVOperation ~Channel - ~Tensor ~StatePrepBase .. currentmodule:: pennylane.operation -.. inheritance-diagram:: Operator Operation Observable Channel CV CVObservable CVOperation Tensor StatePrepBase +.. inheritance-diagram:: Operator Operation Observable Channel CV CVObservable CVOperation StatePrepBase :parts: 1 Errors @@ -173,27 +172,6 @@ ~is_trainable ~not_tape -Enabling New Arithmetic Operators -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -PennyLane is in the process of replacing :class:`~pennylane.Hamiltonian` and :class:`~.Tensor` -with newer, more general arithmetic operators. These consist of :class:`~pennylane.ops.op_math.Prod`, -:class:`~pennylane.ops.op_math.Sum` and :class:`~pennylane.ops.op_math.SProd`. By default, using dunder -methods (eg. ``+``, ``-``, ``@``, ``*``) to combine operators with scalars or other operators will -create the aforementioned newer operators. To toggle the dunders to return the older arithmetic operators, -the ``operation`` module provides the following helper functions: - -.. currentmodule:: pennylane.operation - -.. autosummary:: - :toctree: api - - ~enable_new_opmath - ~disable_new_opmath - ~active_new_opmath - ~convert_to_opmath - ~convert_to_legacy_H - Other ~~~~~ @@ -244,17 +222,13 @@ # pylint:disable=access-member-before-definition,global-statement import abc import copy -import functools -import itertools import warnings from collections.abc import Hashable, Iterable -from contextlib import contextmanager from enum import IntEnum from typing import Any, Callable, Literal, Optional, Union import numpy as np -from numpy.linalg import multi_dot -from scipy.sparse import coo_matrix, csr_matrix, eye, kron +from scipy.sparse import csr_matrix import pennylane as qml from pennylane.capture import ABCCaptureMeta, create_operator_primitive @@ -270,7 +244,6 @@ # ============================================================================= SUPPORTED_INTERFACES = {"numpy", "scipy", "autograd", "torch", "tensorflow", "jax"} -__use_new_opmath = True _UNSET_BATCH_SIZE = -1 # indicates that the (lazy) batch size has not yet been accessed/computed @@ -1154,7 +1127,6 @@ def __init__( ( qml.Barrier, qml.Snapshot, - qml.ops.Hamiltonian, qml.ops.LinearCombination, qml.GlobalPhase, qml.Identity, @@ -1956,8 +1928,9 @@ def _queue_category(self) -> Literal["_ops", "_measurements", None]: * `"_measurements"` * None - Non-pauli observables, like Tensor, Hermitian, and Hamiltonian, should not be processed into any queue. - The Pauli observables double as Operations, and should therefore be processed into `_ops` if unowned. + 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 @@ -1966,50 +1939,13 @@ def is_hermitian(self) -> bool: """All observables must be hermitian""" return True - def __matmul__(self, other: Operator) -> Operator: - if active_new_opmath(): - return super().__matmul__(other=other) - - if isinstance(other, (Tensor, qml.ops.Hamiltonian, qml.ops.LinearCombination)): - return other.__rmatmul__(self) - - if isinstance(other, Observable): - return Tensor(self, other) - - return super().__matmul__(other=other) - - def _obs_data(self) -> set[tuple[str, Wires, tuple[int, ...]]]: - r"""Extracts the data from a Observable or Tensor and serializes it in an order-independent fashion. - - This allows for comparison between observables that are equivalent, but are expressed - in different orders. For example, `qml.X(0) @ qml.Z(1)` and - `qml.Z(1) @ qml.X(0)` are equivalent observables with different orderings. - - **Example** - - >>> tensor = qml.X(0) @ qml.Z(1) - >>> print(tensor._obs_data()) - {("PauliZ", Wires([1]), ()), ("PauliX", Wires([0]), ())} - """ - obs = Tensor(self).non_identity_obs - tensor = set() - - for ob in obs: - parameters = tuple(param.tobytes() for param in ob.parameters) - if isinstance(ob, qml.GellMann): - parameters += (ob.hyperparameters["index"],) - tensor.add((ob.name, ob.wires, parameters)) - - return tensor - def compare( self, - other: Union["Tensor", "Observable", "qml.ops.Hamiltonian", "qml.ops.LinearCombination"], + other: Union["Observable", "qml.ops.LinearCombination"], ) -> bool: - r"""Compares with another :class:`~.Hamiltonian`, :class:`~Tensor`, or :class:`~Observable`, - to determine if they are equivalent. + r"""Compares with another :class:`~Observable`, to determine if they are equivalent. - Observables/Hamiltonians are equivalent if they represent the same operator + Observables are equivalent if they represent the same operator (their matrix representations are equal), and they are defined on the same wires. .. Warning:: @@ -2017,8 +1953,8 @@ def compare( 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 of Hamiltonians and Tensors - be calculated, which would drastically increase runtime. + To do so would require the matrix form to be calculated, which would + drastically increase runtime. Returns: (bool): True if equivalent. @@ -2034,595 +1970,7 @@ def compare( >>> ob1.compare(ob2) False """ - if isinstance(other, (qml.ops.Hamiltonian, qml.ops.LinearCombination)): - return other.compare(self) - if isinstance(other, (Tensor, Observable)): - return other._obs_data() == self._obs_data() - - raise ValueError( - "Can only compare an Observable/Tensor, and a Hamiltonian/Observable/Tensor." - ) - - def __add__(self, other: Operator) -> Operator: - r"""The addition operation between Observables/Tensors/qml.Hamiltonian objects.""" - if active_new_opmath(): - return super().__add__(other=other) - - if isinstance(other, (qml.ops.Hamiltonian, qml.ops.LinearCombination)): - return other + self - if isinstance(other, (Observable, Tensor)): - return qml.simplify(qml.Hamiltonian([1, 1], [self, other])) - - return super().__add__(other=other) - - __radd__ = __add__ - - def __mul__(self, a): - r"""The scalar multiplication operation between a scalar and an Observable/Tensor.""" - if active_new_opmath(): - return super().__mul__(other=a) - - if isinstance(a, (int, float)): - return qml.simplify(qml.Hamiltonian([a], [self])) - - return super().__mul__(other=a) - - __rmul__ = __mul__ - - def __sub__(self, other: Operator) -> Operator: - r"""The subtraction operation between Observables/Tensors/qml.Hamiltonian objects.""" - if active_new_opmath(): - return super().__sub__(other=other) - - if isinstance(other, (Observable, Tensor, qml.ops.Hamiltonian, qml.ops.LinearCombination)): - return self + (-1 * other) - - return super().__sub__(other=other) - - -class Tensor(Observable): - """Container class representing tensor products of observables. - - To create a tensor, simply initiate it like so: - - >>> T = Tensor(qml.X(0), qml.Hermitian(A, [1, 2])) - - You can also create a tensor from other Tensors: - - >>> T = Tensor(T, qml.Z(4)) - - The ``@`` symbol can be used as a tensor product operation: - - >>> T = qml.X(0) @ qml.Hadamard(2) - - .. note: - - This class is marked for deletion or overhaul. - """ - - # pylint: disable=abstract-method - tensor = True - has_matrix = True - - def _flatten(self) -> FlatPytree: - return tuple(self.obs), tuple() - - @classmethod - def _unflatten(cls, data, _): - return cls(*data) - - @classmethod - def _primitive_bind_call(cls, *args, **kwargs): - return cls._primitive.bind(*args) - - def __init__(self, *args): # pylint: disable=super-init-not-called - self._eigvals_cache = None - self.obs: list[Observable] = [] - self._args = args - self._batch_size = None - self._pauli_rep = None - self.queue(init=True) - - warnings.warn( - "qml.operation.Tensor uses the old approach to operator arithmetic, which will become " - "unavailable in version 0.40 of PennyLane. If you are experiencing issues, visit " - "https://docs.pennylane.ai/en/stable/news/new_opmath.html or contact the PennyLane " - "team on the discussion forum: https://discuss.pennylane.ai/.", - qml.PennyLaneDeprecationWarning, - ) - - wires = [op.wires for op in self.obs] - if len(wires) != len(set(wires)): - warnings.warn( - "Tensor object acts on overlapping wires; in some PennyLane functions this will " - "lead to undefined behaviour", - UserWarning, - ) - - # Queue before updating pauli_rep because self.queue updates self.obs - if all(prs := [o.pauli_rep for o in self.obs]): - self._pauli_rep = functools.reduce(lambda a, b: a @ b, prs) - else: - self._pauli_rep = None - - def label( - self, - decimals: Optional[int] = None, - base_label: Optional[str] = None, - cache: Optional[dict] = None, - ) -> str: - r"""How the operator is represented in diagrams and drawings. - - Args: - decimals=None (Int): If ``None``, no parameters are included. Else, - how to round the parameters. - base_label=None (Iterable[str]): overwrite the non-parameter component of the label. - Must be same length as ``obs`` attribute. - cache=None (dict): dictionary that carries information between label calls - in the same drawing - - Returns: - str: label to use in drawings - - >>> T = qml.X(0) @ qml.Hadamard(2) - >>> T.label() - 'X@H' - >>> T.label(base_label=["X0", "H2"]) - 'X0@H2' - - """ - if base_label is not None: - if len(base_label) != len(self.obs): - raise ValueError( - "Tensor label requires ``base_label`` keyword to be same length " - "as tensor components." - ) - return "@".join( - ob.label(decimals=decimals, base_label=lbl) for ob, lbl in zip(self.obs, base_label) - ) - - return "@".join(ob.label(decimals=decimals) for ob in self.obs) - - def queue(self, context=QueuingManager, init=False): # pylint: disable=arguments-differ - constituents = self._args if init else self.obs - for o in constituents: - if init: - if isinstance(o, Tensor): - self.obs.extend(o.obs) - elif isinstance(o, Observable): - self.obs.append(o) - else: - raise ValueError("Can only perform tensor products between observables.") - - context.remove(o) - - context.append(self) - return self - - def __copy__(self): - cls = self.__class__ - copied_op = cls.__new__(cls) # pylint: disable=no-value-for-parameter - copied_op.obs = self.obs.copy() - copied_op._eigvals_cache = self._eigvals_cache - copied_op._batch_size = self._batch_size - copied_op._pauli_rep = self._pauli_rep - return copied_op - - def __repr__(self) -> str: - """Constructor-call-like representation.""" - return " @ ".join([repr(o) for o in self.obs]) - - @property - def name(self) -> list[str]: - """All constituent observable names making up the tensor product. - - Returns: - list[str]: list containing all observable names - """ - return [o.name for o in self.obs] - - @property - def num_wires(self) -> int: - """Number of wires the tensor product acts on. - - Returns: - int: number of wires - """ - return len(self.wires) - - @property - def wires(self) -> Wires: - """All wires in the system the tensor product acts on. - - Returns: - Wires: wires addressed by the observables in the tensor product - """ - return Wires.all_wires([o.wires for o in self.obs]) - - @property - def data(self): - """Raw parameters of all constituent observables in the tensor product. - - Returns: - tuple[Any]: flattened list containing all dependent parameters - """ - return tuple(d for op in self.obs for d in op.data) - - @data.setter - def data(self, new_data): - """Setter used to set the parameters of all constituent observables in the tensor product. - - The ``new_data`` argument should contain a list of elements, where each element corresponds - to a list containing the parameters of each observable (in order). If an observable doesn't - have any parameter, an empty list must be used. - - **Example:** - - >>> op = qml.X(0) @ qml.Hermitian(np.eye(2), wires=1) - >>> op.data - [array([[1., 0.], - [0., 1.]])] - >>> op.data = [[], [np.eye(2) * 5]] - >>> op.data - [array([[5., 0.], - [0., 5.]])] - """ - if isinstance(new_data, tuple): - start = 0 - for op in self.obs: - op.data = new_data[start : start + len(op.data)] - start += len(op.data) - else: - for new_entry, op in zip(new_data, self.obs): - op.data = tuple(new_entry) - - @property - def num_params(self) -> int: - """Raw parameters of all constituent observables in the tensor product. - - Returns: - list[Any]: flattened list containing all dependent parameters - """ - return len(self.data) - - @property - def parameters(self): - """Evaluated parameter values of all constituent observables in the tensor product. - - Returns: - list[list[Any]]: nested list containing the parameters per observable - in the tensor product - """ - return [o.parameters for o in self.obs] - - @property - def non_identity_obs(self): - """Returns the non-identity observables contained in the tensor product. - - Returns: - list[:class:`~.Observable`]: list containing the non-identity observables - in the tensor product - """ - return [obs for obs in self.obs if not isinstance(obs, qml.Identity)] - - @property - def arithmetic_depth(self) -> int: - return 1 + max(o.arithmetic_depth for o in self.obs) - - def __matmul__(self, other: Operator) -> Operator: - if isinstance(other, (qml.ops.Hamiltonian, qml.ops.LinearCombination)): - return other.__rmatmul__(self) - - if isinstance(other, Observable): - return Tensor(self, other) - - if isinstance(other, Operator): - return qml.prod(*self.obs, other) - - return NotImplemented - - def __rmatmul__(self, other): - if isinstance(other, Observable): - return Tensor(other, self) - - return NotImplemented - - __imatmul__ = __matmul__ - - def eigvals(self): - """Return the eigenvalues of the specified tensor product observable. - - This method uses pre-stored eigenvalues for standard observables where - possible. - - Returns: - array[float]: array containing the eigenvalues of the tensor product - observable - """ - if self._eigvals_cache is not None: - return self._eigvals_cache - - standard_observables = {"PauliX", "PauliY", "PauliZ", "Hadamard"} - - # observable should be Z^{\otimes n} - self._eigvals_cache = qml.pauli.pauli_eigs(len(self.wires)) - - # check if there are any non-standard observables (such as Identity) - if set(self.name) - standard_observables: - # Tensor product of observables contains a mixture - # of standard and non-standard observables - self._eigvals_cache = np.array([1]) - for k, g in itertools.groupby(self.obs, lambda x: x.name in standard_observables): - if k: - # Subgroup g contains only standard observables. - self._eigvals_cache = qml.math.kron( - self._eigvals_cache, qml.pauli.pauli_eigs(len(list(g))) - ) - else: - # Subgroup g contains only non-standard observables. - for ns_ob in g: - # loop through all non-standard observables - self._eigvals_cache = qml.math.kron(self._eigvals_cache, ns_ob.eigvals()) - - return self._eigvals_cache - - # pylint: disable=arguments-renamed, invalid-overridden-method - @property - def has_diagonalizing_gates(self): - r"""Bool: Whether or not the Tensor returns defined diagonalizing gates.""" - return all(o.has_diagonalizing_gates for o in self.obs) - - def diagonalizing_gates(self): - """Return the gate set that diagonalizes a circuit according to the - specified tensor observable. - - This method uses pre-stored eigenvalues for standard observables where - possible and stores the corresponding eigenvectors from the eigendecomposition. - - Returns: - list: list containing the gates diagonalizing the tensor observable - """ - diag_gates = [] - for o in self.obs: - diag_gates.extend(o.diagonalizing_gates()) - - return diag_gates - - def matrix(self, wire_order=None): - r"""Matrix representation of the Tensor operator - in the computational basis. - - .. note:: - - The wire_order argument is added for compatibility, but currently not implemented. - The Tensor class is planned to be removed soon. - - Args: - wire_order (Iterable): global wire order, must contain all wire labels in the operator's wires - - Returns: - array: matrix representation - - **Example** - - >>> O = qml.Z(0) @ qml.Z(2) - >>> O.matrix() - array([[ 1, 0, 0, 0], - [ 0, -1, 0, 0], - [ 0, 0, -1, 0], - [ 0, 0, 0, 1]]) - - To get the full :math:`2^3\times 2^3` Hermitian matrix - acting on the 3-qubit system, the identity on wire 1 - must be explicitly included: - - >>> O = qml.Z(0) @ qml.Identity(1) @ qml.Z(2) - >>> O.matrix() - array([[ 1., 0., 0., 0., 0., 0., 0., 0.], - [ 0., -1., 0., -0., 0., -0., 0., -0.], - [ 0., 0., 1., 0., 0., 0., 0., 0.], - [ 0., -0., 0., -1., 0., -0., 0., -0.], - [ 0., 0., 0., 0., -1., -0., -0., -0.], - [ 0., -0., 0., -0., -0., 1., -0., 0.], - [ 0., 0., 0., 0., -0., -0., -1., -0.], - [ 0., -0., 0., -0., -0., 0., -0., 1.]]) - """ - - if wire_order is not None: - raise NotImplementedError("The wire_order argument is currently not implemented.") - - # Check for partially (but not fully) overlapping wires in the observables - partial_overlap = self.check_wires_partial_overlap() - - # group the observables based on what wires they act on - U_list = [] - for _, g in itertools.groupby(self.obs, lambda x: x.wires.labels): - # extract the matrices of each diagonalizing gate - mats = [i.matrix() for i in g] - - if len(mats) > 1: - # multiply all unitaries together before appending - mats = [multi_dot(mats)] - - # append diagonalizing unitary for specific wire to U_list - U_list.append(mats[0]) - - mat_size = np.prod([qml.math.shape(mat)[0] for mat in U_list]) - wire_size = 2 ** len(self.wires) - if mat_size != wire_size: - if partial_overlap: - warnings.warn( - "The matrix for Tensors of Tensors/Observables with partially " - "overlapping wires might yield unexpected results. In particular " - "the matrix size might be larger than intended." - ) - else: - warnings.warn( - f"The size of the returned matrix ({mat_size}) will not be compatible " - f"with the subspace of the wires of the Tensor ({wire_size}). " - "This likely is due to wires being used in multiple tensor product " - "factors of the Tensor." - ) - - # Return the Hermitian matrix representing the observable - # over the defined wires. - return functools.reduce(qml.math.kron, U_list) - - def check_wires_partial_overlap(self): - r"""Tests whether any two observables in the Tensor have partially - overlapping wires and raise a warning if they do. - - .. note:: - - Fully overlapping wires, i.e., observables with - same (sets of) wires are not reported, as the ``matrix`` method is - well-defined and implemented for this scenario. - """ - for o1, o2 in itertools.combinations(self.obs, r=2): - shared = qml.wires.Wires.shared_wires([o1.wires, o2.wires]) - if shared and (shared != o1.wires or shared != o2.wires): - return 1 - return 0 - - @property - def has_sparse_matrix(self): - return all(op.has_matrix for op in self.obs) - - def sparse_matrix( - self, wire_order=None, wires=None, format="csr" - ): # pylint:disable=arguments-renamed, arguments-differ - r"""Computes, by default, a `scipy.sparse.csr_matrix` representation of this Tensor. - - This is useful for larger qubit numbers, where the dense matrix becomes very large, while - consisting mostly of zero entries. - - Args: - wire_order (Iterable): Wire labels that indicate the order of wires according to which the matrix - is constructed. If not provided, ``self.wires`` is used. - wires (Iterable): Same as ``wire_order`` to ensure compatibility with all the classes. Must only - provide one: either ``wire_order`` or ``wires``. - format: the output format for the sparse representation. All scipy sparse formats are accepted. - - Raises: - ValueError: if both ``wire_order`` and ``wires`` are provided at the same time. - - Returns: - :class:`scipy.sparse._csr.csr_matrix`: sparse matrix representation - - **Example** - - Consider the following tensor: - - >>> t = qml.X(0) @ qml.Z(1) - - Without passing wires, the sparse representation is given by: - - >>> print(t.sparse_matrix()) - (0, 2) 1 - (1, 3) -1 - (2, 0) 1 - (3, 1) -1 - - If we define a custom wire ordering, the matrix representation changes - accordingly: - - >>> print(t.sparse_matrix(wire_order=[1, 0])) - (0, 1) 1 - (1, 0) 1 - (2, 3) -1 - (3, 2) -1 - - We can also enforce implicit identities by passing wire labels that - are not present in the constituent operations: - - >>> res = t.sparse_matrix(wire_order=[0, 1, 2]) - >>> print(res.shape) - (8, 8) - """ - if wires is not None and wire_order is not None: - raise ValueError( - "Wire order has been specified twice. Provide only one of either " - "``wire_order`` or ``wires``, but not both." - ) - - wires = wires or wire_order - wires = self.wires if wires is None else Wires(wires) - list_of_sparse_ops = [eye(2, format="coo")] * len(wires) - - for o in self.obs: - if len(o.wires) > 1: - # todo: deal with multi-qubit operations that do not act on consecutive qubits - raise ValueError( - f"Can only compute sparse representation for tensors whose operations " - f"act on consecutive wires; got {o}." - ) - # store the single-qubit ops according to the order of their wires - idx = wires.index(o.wires) - list_of_sparse_ops[idx] = coo_matrix(o.matrix()) - - return functools.reduce(lambda i, j: kron(i, j, format=format), list_of_sparse_ops) - - def prune(self): - """Returns a pruned tensor product of observables by removing :class:`~.Identity` instances from - the observables building up the :class:`~.Tensor`. - - If the tensor product only contains one observable, then this observable instance is - returned. - - Note that, as a result, this method can return observables that are not a :class:`~.Tensor` - instance. - - **Example:** - - Pruning that returns a :class:`~.Tensor`: - - >>> O = qml.Z(0) @ qml.Identity(1) @ qml.Z(2) - >>> O.prune() - >> [(o.name, o.wires) for o in O.prune().obs] - [('PauliZ', [0]), ('PauliZ', [2])] - - Pruning that returns a single observable: - - >>> O = qml.Z(0) @ qml.Identity(1) - >>> O_pruned = O.prune() - >>> (O_pruned.name, O_pruned.wires) - ('PauliZ', [0]) - - Returns: - ~.Observable: the pruned tensor product of observables - """ - if qml.QueuingManager.recording(): - qml.QueuingManager.remove(self) - - if len(self.non_identity_obs) == 0: - # Return a single Identity as the tensor only contains Identities - return qml.Identity(self.wires[0]) if self.wires else qml.Identity() - return ( - self.non_identity_obs[0] - if len(self.non_identity_obs) == 1 - else Tensor(*self.non_identity_obs) - ) - - def map_wires(self, wire_map: dict): - """Returns a copy of the current tensor with its wires changed according to the given - wire map. - - Args: - wire_map (dict): dictionary containing the old wires as keys and the new wires as values - - Returns: - .Tensor: new tensor - """ - cls = self.__class__ - new_op = cls.__new__(cls) # pylint: disable=no-value-for-parameter - new_op.obs = [obs.map_wires(wire_map) for obs in self.obs] - new_op._eigvals_cache = self._eigvals_cache - new_op._batch_size = self._batch_size - new_op._pauli_rep = ( - self._pauli_rep.map_wires(wire_map) if self.pauli_rep is not None else None - ) - return new_op + return qml.equal(self, other) # ============================================================================= @@ -3040,272 +2388,7 @@ def gen_is_multi_term_hamiltonian(obj): except (AttributeError, OperatorPropertyUndefined, GeneratorUndefinedError): return False - return isinstance(o, (qml.ops.Hamiltonian, qml.ops.LinearCombination)) and len(o.coeffs) > 1 - - -def enable_new_opmath(warn=True): - """ - Change dunder methods to return arithmetic operators instead of Hamiltonians and Tensors - - .. warning:: - - Using legacy operator arithmetic is deprecated, and will be removed in PennyLane v0.40. - For further details, see :doc:`Updated Operators `. - - Args: - warn (bool): Whether or not to emit a warning for re-enabling new opmath. Default is ``True``. - - **Example** - - >>> qml.operation.active_new_opmath() - False - >>> type(qml.X(0) @ qml.Z(1)) - - >>> qml.operation.enable_new_opmath() - >>> type(qml.X(0) @ qml.Z(1)) - - """ - if warn: - warnings.warn( - "Toggling the new approach to operator arithmetic is deprecated. From version 0.40 of " - "PennyLane, only the new approach to operator arithmetic will be available. If you are " - "experiencing issues, visit https://docs.pennylane.ai/en/stable/news/new_opmath.html " - "or contact the PennyLane team on the discussion forum: https://discuss.pennylane.ai/.", - qml.PennyLaneDeprecationWarning, - ) - global __use_new_opmath - __use_new_opmath = True - - -def disable_new_opmath(warn=True): - """ - Change dunder methods to return Hamiltonians and Tensors instead of arithmetic operators - - .. warning:: - - Using legacy operator arithmetic is deprecated, and will be removed in PennyLane v0.40. - For further details, see :doc:`Updated Operators `. - - Args: - warn (bool): Whether or not to emit a warning for disabling new opmath. Default is ``True``. - - **Example** - - >>> qml.operation.active_new_opmath() - True - >>> type(qml.X(0) @ qml.Z(1)) - - >>> qml.operation.disable_new_opmath() - >>> type(qml.X(0) @ qml.Z(1)) - - """ - if warn: - warnings.warn( - "Disabling the new approach to operator arithmetic is deprecated. From version 0.40 of " - "PennyLane, only the new approach to operator arithmetic will be available. If you are " - "experiencing issues, visit https://docs.pennylane.ai/en/stable/news/new_opmath.html " - "or contact the PennyLane team on the discussion forum: https://discuss.pennylane.ai/.", - qml.PennyLaneDeprecationWarning, - ) - global __use_new_opmath - __use_new_opmath = False - - -def active_new_opmath(): - """ - Function that checks if the new arithmetic operator dunders are active - - .. warning:: - - Using legacy operator arithmetic is deprecated, and will be removed in PennyLane v0.40. - For further details, see :doc:`Updated Operators `. - - Returns: - bool: Returns ``True`` if the new arithmetic operator dunders are active - - **Example** - - >>> qml.operation.active_new_opmath() - False - >>> qml.operation.enable_new_opmath() - >>> qml.operation.active_new_opmath() - True - """ - return __use_new_opmath - - -def convert_to_opmath(op): - """ - Converts :class:`~pennylane.Hamiltonian` and :class:`.Tensor` instances - into arithmetic operators. Objects of any other type are returned directly. - - Arithmetic operators include :class:`~pennylane.ops.op_math.Prod`, - :class:`~pennylane.ops.op_math.Sum` and :class:`~pennylane.ops.op_math.SProd`. - - Args: - op (Operator): The operator instance to convert - - Returns: - Operator: An operator using the new arithmetic operations, if relevant - """ - if isinstance(op, (qml.ops.Hamiltonian, qml.ops.LinearCombination)): - if qml.QueuingManager.recording(): - qml.QueuingManager.remove(op) - c, ops = op.terms() - ops = tuple(convert_to_opmath(o) for o in ops) - return qml.dot(c, ops) - if isinstance(op, Tensor): - if qml.QueuingManager.recording(): - qml.QueuingManager.remove(op) - return qml.prod(*op.obs) - return op - - -@contextmanager -def disable_new_opmath_cm(warn=True): - r"""Allows to use the old operator arithmetic within a - temporary context using the `with` statement.""" - if warn: - warnings.warn( - "Disabling the new approach to operator arithmetic is deprecated. From version 0.40 of " - "PennyLane, only the new approach to operator arithmetic will be available. If you are " - "experiencing issues, visit https://docs.pennylane.ai/en/stable/news/new_opmath.html " - "or contact the PennyLane team on the discussion forum: https://discuss.pennylane.ai/.", - qml.PennyLaneDeprecationWarning, - ) - - was_active = qml.operation.active_new_opmath() - try: - if was_active: - disable_new_opmath(warn=False) # Only warn once - yield - except Exception as e: - raise e - finally: - if was_active: - enable_new_opmath(warn=False) # Only warn once - else: - disable_new_opmath(warn=False) # Only warn once - - -@contextmanager -def enable_new_opmath_cm(warn=True): - r"""Allows to use the new operator arithmetic within a - temporary context using the `with` statement.""" - if warn: - warnings.warn( - "Toggling the new approach to operator arithmetic is deprecated. From version 0.40 of " - "PennyLane, only the new approach to operator arithmetic will be available. If you are " - "experiencing issues, visit https://docs.pennylane.ai/en/stable/news/new_opmath.html " - "or contact the PennyLane team on the discussion forum: https://discuss.pennylane.ai/.", - qml.PennyLaneDeprecationWarning, - ) - - was_active = qml.operation.active_new_opmath() - if not was_active: - enable_new_opmath(warn=False) # Only warn once - yield - if was_active: - enable_new_opmath(warn=False) # Only warn once - else: - disable_new_opmath(warn=False) # Only warn once - - -# pylint: disable=too-many-branches -def convert_to_H(op): - """ - Converts arithmetic operators into a :class:`~pennylane.ops.Hamiltonian` or - :class:`~pennylane.ops.LinearCombination` instance, depending on whether - new_opmath is enabled. Objects of any other type are returned directly. - - Arithmetic operators include :class:`~pennylane.ops.op_math.Prod`, - :class:`~pennylane.ops.op_math.Sum` and :class:`~pennylane.ops.op_math.SProd`. - - Args: - op (Operator): The operator instance to convert. - - Returns: - Operator: The operator as a :class:`~pennylane.ops.LinearCombination` instance - if `active_new_opmath()`, otherwise a :class:`~pennylane.ops.Hamiltonian` - """ - if not isinstance(op, (qml.ops.op_math.Prod, qml.ops.op_math.SProd, qml.ops.op_math.Sum)): - return op - - coeffs = [] - ops = [] - - op = qml.simplify(op) - product = qml.ops.op_math.Prod if active_new_opmath() else Tensor - - if isinstance(op, Observable): - coeffs.append(1.0) - ops.append(op) - - elif isinstance(op, qml.ops.SProd): - coeffs.append(op.scalar) - if isinstance(op.base, Observable): - ops.append(op.base) - elif isinstance(op.base, qml.ops.op_math.Prod): - ops.append(product(*op.base)) - else: - raise ValueError("The base of scalar product must be an observable or a product.") - - elif isinstance(op, qml.ops.Prod): - coeffs.append(1.0) - ops.append(product(*op)) - - elif isinstance(op, qml.ops.Sum): - for factor in op: - if isinstance(factor, (qml.ops.SProd)): - coeffs.append(factor.scalar) - if isinstance(factor.base, Observable): - ops.append(factor.base) - elif isinstance(factor.base, qml.ops.op_math.Prod): - ops.append(product(*factor.base)) - else: - raise ValueError( - "The base of scalar product must be an observable or a product." - ) - elif isinstance(factor, (qml.ops.Prod)): - coeffs.append(1.0) - ops.append(product(*factor)) - elif isinstance(factor, Observable): - coeffs.append(1.0) - ops.append(factor) - else: - raise ValueError( - "Could not convert to Hamiltonian. Some or all observables are not valid." - ) - - else: - raise ValueError("Could not convert to Hamiltonian. Some or all observables are not valid.") - - return qml.Hamiltonian(coeffs, ops) - - -def convert_to_legacy_H(op): - """ - Converts arithmetic operators into a legacy :class:`~pennylane.Hamiltonian` instance. - Objects of any other type are returned directly. - - Arithmetic operators include :class:`~pennylane.ops.op_math.Prod`, - :class:`~pennylane.ops.op_math.Sum` and :class:`~pennylane.ops.op_math.SProd`. - - .. warning:: - - Using legacy operator arithmetic is deprecated, and will be removed in PennyLane v0.40. - For further details, see :doc:`Updated Operators `. - - Args: - op (Operator): The operator instance to convert. - - Returns: - Operator: The operator as a :class:`~pennylane.Hamiltonian` instance - """ - with disable_new_opmath_cm(warn=False): - # Suppress warning because constructing Hamiltonian will raise a warning anyway - res = convert_to_H(op) - return res + return isinstance(o, qml.ops.LinearCombination) and len(o.coeffs) > 1 def __getattr__(name): diff --git a/pennylane/ops/functions/bind_new_parameters.py b/pennylane/ops/functions/bind_new_parameters.py index af29d1576af..f6f57ff5952 100644 --- a/pennylane/ops/functions/bind_new_parameters.py +++ b/pennylane/ops/functions/bind_new_parameters.py @@ -22,7 +22,7 @@ from typing import Union import pennylane as qml -from pennylane.operation import Operator, Tensor +from pennylane.operation import Operator from pennylane.typing import TensorLike from ..identity import Identity @@ -233,26 +233,6 @@ def bind_new_parameters_pow(op: Pow, params: Sequence[TensorLike]): return Pow(bind_new_parameters(op.base, params), op.scalar) -@bind_new_parameters.register -def bind_new_parameters_hamiltonian(op: qml.ops.Hamiltonian, params: Sequence[TensorLike]): - new_H = qml.ops.Hamiltonian(params, op.ops) - if op.grouping_indices is not None: - new_H.grouping_indices = op.grouping_indices - return new_H - - -@bind_new_parameters.register -def bind_new_parameters_tensor(op: Tensor, params: Sequence[TensorLike]): - new_obs = [] - - for obs in op.obs: - sub_params = params[: obs.num_params] - params = params[obs.num_params :] - new_obs.append(bind_new_parameters(obs, sub_params)) - - return Tensor(*new_obs) - - @bind_new_parameters.register def bind_new_parameters_conditional(op: qml.ops.Conditional, params: Sequence[TensorLike]): then_op = bind_new_parameters(op.base, params) diff --git a/pennylane/ops/functions/dot.py b/pennylane/ops/functions/dot.py index dd5b85cb547..d54adf5d335 100644 --- a/pennylane/ops/functions/dot.py +++ b/pennylane/ops/functions/dot.py @@ -21,7 +21,7 @@ from typing import Union import pennylane as qml -from pennylane.operation import Operator, convert_to_opmath +from pennylane.operation import Operator from pennylane.pauli import PauliSentence, PauliWord from pennylane.pulse import ParametrizedHamiltonian @@ -165,9 +165,6 @@ def dot( # Convert possible PauliWord and PauliSentence instances to operation ops = [op.operation() if isinstance(op, (PauliWord, PauliSentence)) else op for op in ops] - # When casting a Hamiltonian to a Sum, we also cast its inner Tensors to Prods - ops = (convert_to_opmath(op) for op in ops) - operands = [op if coeff == 1 else qml.s_prod(coeff, op) for coeff, op in zip(coeffs, ops)] return ( operands[0] diff --git a/pennylane/ops/functions/eigvals.py b/pennylane/ops/functions/eigvals.py index 423ea7e51f7..5f71d62dc72 100644 --- a/pennylane/ops/functions/eigvals.py +++ b/pennylane/ops/functions/eigvals.py @@ -115,16 +115,6 @@ def circuit(theta): raise TransformError("Input is not an Operator, tape, QNode, or quantum function") return _eigvals_tranform(op, k=k, which=which) - if isinstance(op, qml.ops.Hamiltonian): - - warnings.warn( - "For Hamiltonians, the eigenvalues will be computed numerically. " - "This may be computationally intensive for a large number of wires. " - "Consider using a sparse representation of the Hamiltonian with qml.SparseHamiltonian.", - UserWarning, - ) - return qml.math.linalg.eigvalsh(qml.matrix(op)) - if isinstance(op, qml.SparseHamiltonian): sparse_matrix = op.sparse_matrix() if k < sparse_matrix.shape[0] - 1: diff --git a/pennylane/ops/functions/equal.py b/pennylane/ops/functions/equal.py index 93598fe52cb..546b5aae0eb 100644 --- a/pennylane/ops/functions/equal.py +++ b/pennylane/ops/functions/equal.py @@ -20,25 +20,14 @@ from typing import Union import pennylane as qml -from pennylane import Hermitian from pennylane.measurements import MeasurementProcess from pennylane.measurements.classical_shadow import ShadowExpvalMP from pennylane.measurements.counts import CountsMP from pennylane.measurements.mid_measure import MeasurementValue, MidMeasureMP from pennylane.measurements.mutual_info import MutualInfoMP from pennylane.measurements.vn_entropy import VnEntropyMP -from pennylane.operation import Observable, Operator, Tensor -from pennylane.ops import ( - Adjoint, - CompositeOp, - Conditional, - Controlled, - Exp, - Hamiltonian, - LinearCombination, - Pow, - SProd, -) +from pennylane.operation import Observable, Operator +from pennylane.ops import Adjoint, CompositeOp, Conditional, Controlled, Exp, Pow, SProd from pennylane.pulse.parametrized_evolution import ParametrizedEvolution from pennylane.tape import QuantumScript from pennylane.templates.subroutines import ControlledSequence, PrepSelPrep @@ -61,17 +50,16 @@ def equal( .. Warning:: The ``qml.equal`` function is based on a comparison of the types and attributes of the - measurements or operators, not their mathematical representations. While mathematical - comparisons between some classes, such as ``Tensor`` and ``Hamiltonian``, are supported, - mathematically equivalent operators defined via different classes may return False when - compared via ``qml.equal``. To be more thorough would require the matrix forms to be - calculated, which may drastically increase runtime. + measurements or operators, not their mathematical representations. Mathematically equivalent + operators defined via different classes may return False when compared via ``qml.equal``. + To be more thorough would require the matrix forms to be calculated, which may drastically + increase runtime. .. Warning:: - The interfaces and trainability of data within some observables including ``Tensor``, - ``Hamiltonian``, ``Prod``, ``Sum`` are sometimes ignored, regardless of what the user - specifies for ``check_interface`` and ``check_trainability``. + The interfaces and trainability of data within some observables including ``Prod`` and + ``Sum`` are sometimes ignored, regardless of what the user specifies for ``check_interface`` + and ``check_trainability``. Args: op1 (.Operator or .MeasurementProcess or .QuantumTape): First object to compare @@ -93,15 +81,15 @@ def equal( >>> qml.equal(op1, op1), qml.equal(op1, op2) (True, False) - >>> T1 = qml.X(0) @ qml.Y(1) - >>> T2 = qml.Y(1) @ qml.X(0) - >>> T3 = qml.X(1) @ qml.Y(0) - >>> qml.equal(T1, T2), qml.equal(T1, T3) + >>> prod1 = qml.X(0) @ qml.Y(1) + >>> prod2 = qml.Y(1) @ qml.X(0) + >>> prod3 = qml.X(1) @ qml.Y(0) + >>> qml.equal(prod1, prod2), qml.equal(prod1, prod3) (True, False) - >>> T = qml.X(0) @ qml.Y(1) - >>> H = qml.Hamiltonian([1], [qml.X(0) @ qml.Y(1)]) - >>> qml.equal(T, H) + >>> prod = qml.X(0) @ qml.Y(1) + >>> ham = qml.Hamiltonian([1], [qml.X(0) @ qml.Y(1)]) + >>> qml.equal(prod, ham) True >>> H1 = qml.Hamiltonian([0.5, 0.5], [qml.Z(0) @ qml.Y(1), qml.Y(1) @ qml.Z(0) @ qml.Identity("a")]) @@ -152,9 +140,6 @@ def equal( """ - if isinstance(op2, (Hamiltonian, Tensor)): - op1, op2 = op2, op1 - dispatch_result = _equal( op1, op2, @@ -577,39 +562,6 @@ def _equal_sprod(op1: SProd, op2: SProd, **kwargs): return True -@_equal_dispatch.register -# pylint: disable=unused-argument -def _equal_tensor(op1: Tensor, op2: Observable, **kwargs): - """Determine whether a Tensor object is equal to a Hamiltonian/Tensor""" - - if not isinstance(op2, Observable): - return f"{op2} is not of type Observable" - - if isinstance(op2, (Hamiltonian, LinearCombination, Hermitian)): - return ( - op2.compare(op1) or f"'{op1}' and '{op2}' are not the same for an unspecified reason." - ) - - if isinstance(op2, Tensor): - return ( - op1._obs_data() == op2._obs_data() # pylint: disable=protected-access - or f"{op1} and {op2} have different _obs_data outputs" - ) - - return f"{op1} is of type {type(op1)} and {op2} is of type {type(op2)}" - - -@_equal_dispatch.register -# pylint: disable=unused-argument -def _equal_hamiltonian(op1: Hamiltonian, op2: Observable, **kwargs): - """Determine whether a Hamiltonian object is equal to a Hamiltonian/Tensor objects""" - - if not isinstance(op2, Observable): - return f"{op2} is not of type Observable" - - return op1.compare(op2) or f"'{op1}' and '{op2}' are not the same for an unspecified reason" - - @_equal_dispatch.register def _equal_parametrized_evolution(op1: ParametrizedEvolution, op2: ParametrizedEvolution, **kwargs): # check times match diff --git a/pennylane/ops/functions/generator.py b/pennylane/ops/functions/generator.py index a004c757f63..d9115808bc4 100644 --- a/pennylane/ops/functions/generator.py +++ b/pennylane/ops/functions/generator.py @@ -21,34 +21,30 @@ import numpy as np import pennylane as qml -from pennylane.operation import convert_to_H -from pennylane.ops import Hamiltonian, LinearCombination, Prod, SProd, Sum +from pennylane.ops import LinearCombination, Prod, SProd, Sum # pylint: disable=too-many-branches def _generator_hamiltonian(gen, op): - """Return the generator as type :class:`~.Hamiltonian`.""" - wires = op.wires + """Return the generator as type :class:`~ops.LinearCombination`.""" - if isinstance(gen, (Hamiltonian, LinearCombination)): - H = gen + if isinstance(gen, LinearCombination): + return gen - elif isinstance(gen, (qml.Hermitian, qml.SparseHamiltonian)): + if isinstance(gen, (qml.Hermitian, qml.SparseHamiltonian)): if isinstance(gen, qml.Hermitian): mat = gen.parameters[0] elif isinstance(gen, qml.SparseHamiltonian): mat = gen.parameters[0].toarray() - H = qml.pauli_decompose(mat, wire_order=wires, hide_identity=True) + return qml.pauli_decompose(mat, wire_order=op.wires, hide_identity=True) - elif isinstance(gen, qml.operation.Observable): - H = qml.Hamiltonian([1.0], [gen]) + if isinstance(gen, (SProd, Prod, Sum)): + coeffs, ops = gen.terms() + return qml.Hamiltonian(coeffs, ops) - elif isinstance(gen, (SProd, Prod, Sum)): - H = convert_to_H(gen) - - return H + return qml.Hamiltonian([1.0], [gen]) # pylint: disable=no-member @@ -64,12 +60,15 @@ def _generator_prefactor(gen): prefactor = 1.0 - if isinstance(gen, Prod): - gen = qml.simplify(gen) + gen = qml.simplify(gen) if isinstance(gen, Prod) else gen - if isinstance(gen, (Hamiltonian, LinearCombination)): + if isinstance(gen, LinearCombination): gen = qml.dot(gen.coeffs, gen.ops) # convert to Sum + if isinstance(gen, Prod): + coeffs, ops = gen.terms() + return ops[0], coeffs[0] + if isinstance(gen, Sum): ops = [o.base if isinstance(o, SProd) else o for o in gen] coeffs = [o.scalar if isinstance(o, SProd) else 1 for o in gen] @@ -134,8 +133,7 @@ def generator(op: qml.operation.Operator, format="prefactor"): * ``"observable"``: Return the generator as a single observable as directly defined by ``op``. Returned generators may be any type of observable, including - :class:`~.Hermitian`, :class:`~.Tensor`, - :class:`~.SparseHamiltonian`, or :class:`~.Hamiltonian`. + :class:`~.Hermitian`, :class:`~.SparseHamiltonian`, or :class:`~.Hamiltonian`. * ``"hamiltonian"``: Similar to ``"observable"``, however the returned observable will always be converted into :class:`~.Hamiltonian` regardless of how ``op`` @@ -169,12 +167,8 @@ def generator(op: qml.operation.Operator, format="prefactor"): >>> op = qml.RX(0.2, wires=0) >>> qml.generator(op, format="prefactor") # output will always be (obs, prefactor) (X(0), -0.5) - >>> qml.generator(op, format="hamiltonian") # output will always be a Hamiltonian/LinearCombination + >>> qml.generator(op, format="hamiltonian") # output will be a LinearCombination -0.5 * X(0) - >>> with qml.operation.disable_new_opmath_cm(): - ... gen = qml.generator(op, format="hamiltonian")) # legacy Hamiltonian class - ... print(gen, type(gen)) - (-0.5) [X0] >>> qml.generator(qml.PhaseShift(0.1, wires=0), format="observable") # ouput will be a simplified obs where possible Projector([1], wires=[0]) >>> qml.generator(op, format="arithmetic") # output is an instance of `SProd` diff --git a/pennylane/ops/functions/is_commuting.py b/pennylane/ops/functions/is_commuting.py index c835236de7d..4f01ccf72aa 100644 --- a/pennylane/ops/functions/is_commuting.py +++ b/pennylane/ops/functions/is_commuting.py @@ -152,16 +152,16 @@ def commutes_inner(op_name1, op_name2): def _check_opmath_operations(operation1, operation2): - """Check that `Tensor`, `SProd`, `Prod`, and `Sum` instances only contain Pauli words.""" + """Check that `SProd`, `Prod`, and `Sum` instances only contain Pauli words.""" for op in [operation1, operation2]: if op.pauli_rep is not None: continue - if isinstance(op, (qml.operation.Tensor, SProd, Prod, Sum)): + if isinstance(op, (SProd, Prod, Sum)): raise qml.QuantumFunctionError( - f"Operation {op} currently not supported. Tensor, Prod, Sprod, and Sum instances must have a valid Pauli representation." + f"Operation {op} currently not supported. Prod, Sprod, and Sum instances must have a valid Pauli representation." ) diff --git a/pennylane/ops/functions/matrix.py b/pennylane/ops/functions/matrix.py index 1fad95575d1..c2b5e259f1a 100644 --- a/pennylane/ops/functions/matrix.py +++ b/pennylane/ops/functions/matrix.py @@ -219,14 +219,6 @@ def circuit(): raise TransformError("Input is not an Operator, tape, QNode, or quantum function") return _matrix_transform(op, wire_order=wire_order) - - if isinstance(op, qml.operation.Tensor) and wire_order is not None: - op = 1.0 * op # convert to a Hamiltonian - - if isinstance(op, qml.ops.Hamiltonian): - - return op.sparse_matrix(wire_order=wire_order).toarray() - try: return op.matrix(wire_order=wire_order) except: # pylint: disable=bare-except diff --git a/pennylane/ops/op_math/adjoint.py b/pennylane/ops/op_math/adjoint.py index fd2d6e12dd5..92e9fd071d5 100644 --- a/pennylane/ops/op_math/adjoint.py +++ b/pennylane/ops/op_math/adjoint.py @@ -364,11 +364,7 @@ def label(self, decimals=None, base_label=None, cache=None): ) def matrix(self, wire_order=None): - if isinstance(self.base, qml.ops.Hamiltonian): - base_matrix = qml.matrix(self.base, wire_order=wire_order) - else: - base_matrix = self.base.matrix(wire_order=wire_order) - + base_matrix = self.base.matrix(wire_order=wire_order) return moveaxis(conj(base_matrix), -2, -1) # pylint: disable=arguments-renamed, invalid-overridden-method diff --git a/pennylane/ops/op_math/composite.py b/pennylane/ops/op_math/composite.py index 132fa3cd516..19fee35a5b7 100644 --- a/pennylane/ops/op_math/composite.py +++ b/pennylane/ops/op_math/composite.py @@ -177,7 +177,7 @@ def is_hermitian(self): @property @handle_recursion_error def has_matrix(self): - return all(op.has_matrix or isinstance(op, qml.ops.Hamiltonian) for op in self) + return all(op.has_matrix for op in self) @handle_recursion_error def eigvals(self): diff --git a/pennylane/ops/op_math/exp.py b/pennylane/ops/op_math/exp.py index 106f387b1e2..52ac51c1700 100644 --- a/pennylane/ops/op_math/exp.py +++ b/pennylane/ops/op_math/exp.py @@ -29,11 +29,9 @@ Operation, Operator, OperatorPropertyUndefined, - Tensor, ) from pennylane.wires import Wires -from ..qubit.hamiltonian import Hamiltonian from .linear_combination import LinearCombination from .sprod import SProd from .sum import Sum @@ -215,15 +213,13 @@ def _queue_category(self): @property def has_decomposition(self): # TODO: Support nested sums in method - if isinstance(self.base, Tensor) and len(self.base.wires) != len(self.base.obs): - return False base = self.base coeff = self.coeff if isinstance(base, SProd): coeff *= base.scalar base = base.base is_pauli_rot = qml.pauli.is_pauli_word(self.base) and math.real(self.coeff) == 0 - is_hamiltonian = isinstance(base, (Hamiltonian, LinearCombination)) + is_hamiltonian = isinstance(base, LinearCombination) is_sum_of_pauli_words = isinstance(base, Sum) and all( qml.pauli.is_pauli_word(o) for o in base ) @@ -260,18 +256,9 @@ def _recursive_decomposition(self, base: Operator, coeff: complex): Returns: List[Operator]: decomposition """ - if isinstance(base, Tensor) and len(base.wires) != len(base.obs): - raise DecompositionUndefinedError( - "Unable to determine if the exponential has a decomposition " - "when the base operator is a Tensor object with overlapping wires. " - f"Received base {base}." - ) - # Change base to `Sum`/`Prod` - if isinstance(base, (Hamiltonian, LinearCombination)): + if isinstance(base, LinearCombination): base = qml.dot(base.coeffs, base.ops) - elif isinstance(base, Tensor): - base = qml.prod(*base.obs) if isinstance(base, SProd): return self._recursive_decomposition(base.base, base.scalar * coeff) diff --git a/pennylane/ops/op_math/linear_combination.py b/pennylane/ops/op_math/linear_combination.py index 348a95cfab0..9e897aa0739 100644 --- a/pennylane/ops/op_math/linear_combination.py +++ b/pennylane/ops/op_math/linear_combination.py @@ -18,12 +18,11 @@ import numbers # pylint: disable=too-many-arguments, protected-access, too-many-instance-attributes -import warnings from copy import copy from typing import Union import pennylane as qml -from pennylane.operation import Observable, Operator, Tensor, convert_to_opmath +from pennylane.operation import Observable, Operator from .sum import Sum @@ -137,7 +136,7 @@ def __init__( self._coeffs = coeffs - self._ops = [convert_to_opmath(op) for op in observables] + self._ops = list(observables) self._hyperparameters = {"ops": self._ops} @@ -345,21 +344,6 @@ def compare(self, other): pr2.simplify() return pr1 == pr2 - if isinstance(other, (qml.ops.Hamiltonian, Tensor)): - warnings.warn( - f"Attempting to compare a legacy operator class instance {other} of type {type(other)} with {self} of type {type(self)}." - f"You are likely disabling/enabling new opmath in the same script or explicitly create legacy operator classes Tensor and ops.Hamiltonian." - f"Please visit https://docs.pennylane.ai/en/stable/news/new_opmath.html for more information and help troubleshooting.", - UserWarning, - ) - op1 = self.simplify() - op2 = other.simplify() - - op2 = qml.operation.convert_to_opmath(op2) - op2 = qml.ops.LinearCombination(*op2.terms()) - - return qml.equal(op1, op2) - op1 = self.simplify() op2 = other.simplify() return qml.equal(op1, op2) @@ -410,7 +394,7 @@ def __add__(self, H: Union[numbers.Number, Operator]) -> Operator: if isinstance(H, numbers.Number) and H == 0: return self - if isinstance(H, (LinearCombination, qml.ops.Hamiltonian)): + if isinstance(H, LinearCombination): coeffs = qml.math.concatenate([self_coeffs, H.coeffs], axis=0) ops.extend(H.ops) if (pr1 := self.pauli_rep) is not None and (pr2 := H.pauli_rep) is not None: @@ -443,8 +427,8 @@ 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/Tensor/Observable.""" - if isinstance(H, (LinearCombination, qml.ops.Hamiltonian, Tensor, 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 @@ -542,3 +526,158 @@ def _(*args, n_obs, **kwargs): coeffs = args[:n_obs] observables = args[n_obs:] return type.__call__(LinearCombination, coeffs, observables, **kwargs) + + +# this just exists for the docs build for now, since we're waiting until the next PR to fix the docs +# pylint: disable=too-few-public-methods +class Hamiltonian: + r"""Returns an operator representing a Hamiltonian. + + The Hamiltonian is represented as a linear combination of other operators, e.g., + :math:`\sum_{k=0}^{N-1} c_k O_k`, where the :math:`c_k` are trainable parameters. + + .. note:: + + ``qml.Hamiltonian`` dispatches to :class:`~pennylane.ops.op_math.LinearCombination`. + + Args: + coeffs (tensor_like): coefficients of the Hamiltonian expression + observables (Iterable[Observable]): 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. + Can be ``'qwc'`` (qubit-wise commuting), ``'commuting'``, or ``'anticommuting'``. + method (str): The graph colouring heuristic to use in solving minimum clique cover for grouping, which + can be ``'lf'`` (Largest First) or ``'rlf'`` (Recursive Largest First). Ignored if ``grouping_type=None``. + id (str): name to be assigned to this Hamiltonian instance + + **Example:** + + ``qml.Hamiltonian`` takes in a list of coefficients and a list of operators. + + >>> coeffs = [0.2, -0.543] + >>> obs = [qml.X(0) @ qml.Z(1), qml.Z(0) @ qml.Hadamard(2)] + >>> H = qml.Hamiltonian(coeffs, obs) + >>> print(H) + 0.2 * (X(0) @ Z(1)) + -0.543 * (Z(0) @ Hadamard(wires=[2])) + + The coefficients can be a trainable tensor, for example: + + >>> coeffs = tf.Variable([0.2, -0.543], dtype=tf.double) + >>> obs = [qml.X(0) @ qml.Z(1), qml.Z(0) @ qml.Hadamard(2)] + >>> H = qml.Hamiltonian(coeffs, obs) + >>> print(H) + 0.2 * (X(0) @ Z(1)) + -0.543 * (Z(0) @ Hadamard(wires=[2])) + + A ``qml.Hamiltonian`` stores information on which commuting observables should be measured + together in a circuit: + + >>> obs = [qml.X(0), qml.X(1), qml.Z(0)] + >>> coeffs = np.array([1., 2., 3.]) + >>> H = qml.Hamiltonian(coeffs, obs, grouping_type='qwc') + >>> H.grouping_indices + ((0, 1), (2,)) + + This attribute can be used to compute groups of coefficients and observables: + + >>> grouped_coeffs = [coeffs[list(indices)] for indices in H.grouping_indices] + >>> grouped_obs = [[H.ops[i] for i in indices] for indices in H.grouping_indices] + >>> grouped_coeffs + [array([1., 2.]), array([3.])] + >>> grouped_obs + [[X(0), X(1)], [Z(0)]] + + Devices that evaluate a ``qml.Hamiltonian`` expectation by splitting it into its local + observables can use this information to reduce the number of circuits evaluated. + + Note that one can compute the ``grouping_indices`` for an already initialized ``qml.Hamiltonian`` + by using the :func:`compute_grouping ` method. + + .. details:: + :title: Old Hamiltonian behaviour + + The following code examples show the behaviour of ``qml.Hamiltonian`` using old operator + arithmetic. See :doc:`Updated Operators ` for more details. The old + behaviour can be reactivated by calling the deprecated + + >>> qml.operation.disable_new_opmath() + + Alternatively, ``qml.ops.Hamiltonian`` provides a permanent access point for Hamiltonian + behaviour before ``v0.36``. + + >>> coeffs = [0.2, -0.543] + >>> obs = [qml.X(0) @ qml.Z(1), qml.Z(0) @ qml.Hadamard(2)] + >>> H = qml.Hamiltonian(coeffs, obs) + >>> print(H) + (-0.543) [Z0 H2] + + (0.2) [X0 Z1] + + The coefficients can be a trainable tensor, for example: + + >>> coeffs = tf.Variable([0.2, -0.543], dtype=tf.double) + >>> obs = [qml.X(0) @ qml.Z(1), qml.Z(0) @ qml.Hadamard(2)] + >>> H = qml.Hamiltonian(coeffs, obs) + >>> print(H) + (-0.543) [Z0 H2] + + (0.2) [X0 Z1] + + The user can also provide custom observables: + + >>> obs_matrix = np.array([[0.5, 1.0j, 0.0, -3j], + [-1.0j, -1.1, 0.0, -0.1], + [0.0, 0.0, -0.9, 12.0], + [3j, -0.1, 12.0, 0.0]]) + >>> obs = qml.Hermitian(obs_matrix, wires=[0, 1]) + >>> H = qml.Hamiltonian((0.8, ), (obs, )) + >>> print(H) + (0.8) [Hermitian0,1] + + Alternatively, the :func:`~.molecular_hamiltonian` function from the + :doc:`/introduction/chemistry` module can be used to generate a molecular + Hamiltonian. + + In many cases, Hamiltonians can be constructed using Pythonic arithmetic operations. + For example: + + >>> qml.Hamiltonian([1.], [qml.X(0)]) + 2 * qml.Z(0) @ qml.Z(1) + + is equivalent to the following Hamiltonian: + + >>> qml.Hamiltonian([1, 2], [qml.X(0), qml.Z(0) @ qml.Z(1)]) + + While scalar multiplication requires native python floats or integer types, + addition, subtraction, and tensor multiplication of Hamiltonians with Hamiltonians or + other observables is possible with tensor-valued coefficients, i.e., + + >>> H1 = qml.Hamiltonian(torch.tensor([1.]), [qml.X(0)]) + >>> H2 = qml.Hamiltonian(torch.tensor([2., 3.]), [qml.Y(0), qml.X(1)]) + >>> obs3 = [qml.X(0), qml.Y(0), qml.X(1)] + >>> H3 = qml.Hamiltonian(torch.tensor([1., 2., 3.]), obs3) + >>> H3.compare(H1 + H2) + True + + A Hamiltonian can store information on which commuting observables should be measured together in + a circuit: + + >>> obs = [qml.X(0), qml.X(1), qml.Z(0)] + >>> coeffs = np.array([1., 2., 3.]) + >>> H = qml.Hamiltonian(coeffs, obs, grouping_type='qwc') + >>> H.grouping_indices + [[0, 1], [2]] + + This attribute can be used to compute groups of coefficients and observables: + + >>> grouped_coeffs = [coeffs[indices] for indices in H.grouping_indices] + >>> grouped_obs = [[H.ops[i] for i in indices] for indices in H.grouping_indices] + >>> grouped_coeffs + [tensor([1., 2.], requires_grad=True), tensor([3.], requires_grad=True)] + >>> grouped_obs + [[qml.X(0), qml.X(1)], [qml.Z(0)]] + + Devices that evaluate a Hamiltonian expectation by splitting it into its local observables can + use this information to reduce the number of circuits evaluated. + + Note that one can compute the ``grouping_indices`` for an already initialized Hamiltonian by + using the :func:`compute_grouping ` method. + + """ diff --git a/pennylane/ops/op_math/prod.py b/pennylane/ops/op_math/prod.py index 7190142f2d3..97da3e157ae 100644 --- a/pennylane/ops/op_math/prod.py +++ b/pennylane/ops/op_math/prod.py @@ -26,7 +26,7 @@ import pennylane as qml from pennylane import math -from pennylane.operation import Operator, convert_to_opmath +from pennylane.operation import Operator from pennylane.ops.op_math.pow import Pow from pennylane.ops.op_math.sprod import SProd from pennylane.ops.op_math.sum import Sum @@ -98,7 +98,6 @@ def prod(*ops, id=None, lazy=True): >>> prod_op CNOT(wires=[0, 1]) @ RX(1.1, wires=[0]) """ - ops = tuple(convert_to_opmath(op) for op in ops) if len(ops) == 1: if isinstance(ops[0], qml.operation.Operator): return ops[0] @@ -282,13 +281,7 @@ def matrix(self, wire_order=None): mats: list[TensorLike] = [] batched: list[bool] = [] # batched[i] tells if mats[i] is batched or not for ops in self.overlapping_ops: - gen = ( - ( - (qml.matrix(op) if isinstance(op, qml.ops.Hamiltonian) else op.matrix()), - op.wires, - ) - for op in ops - ) + gen = ((op.matrix(), op.wires) for op in ops) reduced_mat, _ = math.reduce_matrices(gen, reduce_func=math.matmul) diff --git a/pennylane/ops/op_math/sprod.py b/pennylane/ops/op_math/sprod.py index d73b29fce54..bd8e6a3a3e5 100644 --- a/pennylane/ops/op_math/sprod.py +++ b/pennylane/ops/op_math/sprod.py @@ -20,7 +20,7 @@ import pennylane as qml import pennylane.math as qnp -from pennylane.operation import Operator, TermsUndefinedError, convert_to_opmath +from pennylane.operation import Operator, TermsUndefinedError from pennylane.ops.op_math.pow import Pow from pennylane.ops.op_math.sum import Sum from pennylane.queuing import QueuingManager @@ -73,7 +73,6 @@ def s_prod(scalar, operator, lazy=True, id=None): array([[ 0., 2.], [ 2., 0.]]) """ - operator = convert_to_opmath(operator) if lazy or not isinstance(operator, SProd): return SProd(scalar, operator, id=id) @@ -282,7 +281,7 @@ def has_sparse_matrix(self): @handle_recursion_error def has_matrix(self): """Bool: Whether or not the Operator returns a defined matrix.""" - return isinstance(self.base, qml.ops.Hamiltonian) or self.base.has_matrix + return self.base.has_matrix @staticmethod @handle_recursion_error diff --git a/pennylane/ops/op_math/sum.py b/pennylane/ops/op_math/sum.py index 58590480f40..5401b230ac3 100644 --- a/pennylane/ops/op_math/sum.py +++ b/pennylane/ops/op_math/sum.py @@ -25,7 +25,7 @@ import pennylane as qml from pennylane import math -from pennylane.operation import Operator, convert_to_opmath +from pennylane.operation import Operator from pennylane.queuing import QueuingManager from .composite import CompositeOp, handle_recursion_error @@ -103,7 +103,6 @@ def sum(*summands, grouping_type=None, method="rlf", id=None, lazy=True): ``method`` can be ``"rlf"`` or ``"lf"``. To see more details about how these affect grouping, see :ref:`Pauli Graph Colouring` and :func:`~pennylane.pauli.group_observables`. """ - summands = tuple(convert_to_opmath(op) for op in summands) if lazy: return Sum(*summands, grouping_type=grouping_type, method=method, id=id) @@ -337,10 +336,7 @@ def matrix(self, wire_order=None): """ if self.pauli_rep: return self.pauli_rep.to_mat(wire_order=wire_order or self.wires) - gen = ( - (qml.matrix(op) if isinstance(op, qml.ops.Hamiltonian) else op.matrix(), op.wires) - for op in self - ) + gen = ((op.matrix(), op.wires) for op in self) reduced_mat, sum_wires = math.reduce_matrices(gen, reduce_func=math.add) diff --git a/pennylane/ops/op_math/symbolicop.py b/pennylane/ops/op_math/symbolicop.py index 304684d06e5..5d8ea2a0bfa 100644 --- a/pennylane/ops/op_math/symbolicop.py +++ b/pennylane/ops/op_math/symbolicop.py @@ -210,7 +210,7 @@ def data(self, new_data): @property @handle_recursion_error def has_matrix(self): - return self.base.has_matrix or isinstance(self.base, qml.ops.Hamiltonian) + return self.base.has_matrix @property @handle_recursion_error @@ -259,10 +259,7 @@ def matrix(self, wire_order=None): tensor_like: matrix representation """ # compute base matrix - if isinstance(self.base, qml.ops.Hamiltonian): - base_matrix = qml.matrix(self.base) - else: - base_matrix = self.base.matrix() + base_matrix = self.base.matrix() scalar_interface = qml.math.get_interface(self.scalar) scalar = self.scalar diff --git a/pennylane/ops/qubit/__init__.py b/pennylane/ops/qubit/__init__.py index 9c0172c7384..2c2b932ad0f 100644 --- a/pennylane/ops/qubit/__init__.py +++ b/pennylane/ops/qubit/__init__.py @@ -32,7 +32,6 @@ from ..identity import GlobalPhase, Identity from ..meta import Barrier, Snapshot, WireCut from .arithmetic_ops import * -from .hamiltonian import Hamiltonian from .matrix_ops import * from .non_parametric_ops import * from .observables import * @@ -116,7 +115,6 @@ "Hermitian", "Projector", "SparseHamiltonian", - "Hamiltonian", } diff --git a/pennylane/ops/qubit/attributes.py b/pennylane/ops/qubit/attributes.py index 401c81b155e..934afdd8d09 100644 --- a/pennylane/ops/qubit/attributes.py +++ b/pennylane/ops/qubit/attributes.py @@ -17,7 +17,7 @@ """ from inspect import isclass -from pennylane.operation import Operator, Tensor +from pennylane.operation import Operator class Attribute(set): @@ -74,12 +74,6 @@ def __contains__(self, obj): if isinstance(obj, str): return super().__contains__(obj) - # Hotfix: return False for all tensors. - # Can be removed or updated when tensor class is - # improved. - if isinstance(obj, Tensor): - return False - if isinstance(obj, Operator): return super().__contains__(obj.name) diff --git a/pennylane/ops/qubit/hamiltonian.py b/pennylane/ops/qubit/hamiltonian.py deleted file mode 100644 index 9c37be86146..00000000000 --- a/pennylane/ops/qubit/hamiltonian.py +++ /dev/null @@ -1,916 +0,0 @@ -# Copyright 2018-2021 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 contains the discrete-variable quantum operations that perform -arithmetic operations on their input states. -""" -import functools - -# pylint: disable=too-many-arguments,too-many-instance-attributes -import itertools -import numbers -from collections.abc import Iterable -from copy import copy -from typing import Hashable, Literal, Optional, Union -from warnings import warn - -import numpy as np -import scipy - -import pennylane as qml -from pennylane.operation import FlatPytree, Observable, Tensor -from pennylane.typing import TensorLike -from pennylane.wires import Wires, WiresLike - -OBS_MAP = {"PauliX": "X", "PauliY": "Y", "PauliZ": "Z", "Hadamard": "H", "Identity": "I"} - - -def _compute_grouping_indices( - observables: list[Observable], - grouping_type: Literal["qwc", "commuting", "anticommuting"] = "qwc", - method: Literal["lf", "rlf"] = "lf", -): - # todo: directly compute the - # indices, instead of extracting groups of observables first - observable_groups = qml.pauli.group_observables( - observables, coefficients=None, grouping_type=grouping_type, method=method - ) - - observables = copy(observables) - - indices = [] - available_indices = list(range(len(observables))) - for partition in observable_groups: # pylint:disable=too-many-nested-blocks - indices_this_group = [] - for pauli_word in partition: - # find index of this pauli word in remaining original observables, - for ind, observable in enumerate(observables): - if qml.pauli.are_identical_pauli_words(pauli_word, observable): - indices_this_group.append(available_indices[ind]) - # delete this observable and its index, so it cannot be found again - observables.pop(ind) - available_indices.pop(ind) - break - indices.append(tuple(indices_this_group)) - - return tuple(indices) - - -class Hamiltonian(Observable): - r"""Operator representing a Hamiltonian. - - The Hamiltonian is represented as a linear combination of other operators, e.g., - :math:`\sum_{k=0}^{N-1} c_k O_k`, where the :math:`c_k` are trainable parameters. - - .. warning:: - - As of ``v0.39``, ``qml.ops.Hamiltonian`` is deprecated. When using the new operator arithmetic, - ``qml.Hamiltonian`` will dispatch to :class:`~pennylane.ops.op_math.LinearCombination`. See - :doc:`Updated Operators ` for more details. - - Args: - coeffs (tensor_like): coefficients of the Hamiltonian expression - observables (Iterable[Observable]): 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. - Can be ``'qwc'`` (qubit-wise commuting), ``'commuting'``, or ``'anticommuting'``. - method (str): The graph colouring heuristic to use in solving minimum clique cover for grouping, which - can be ``'lf'`` (Largest First) or ``'rlf'`` (Recursive Largest First). Ignored if ``grouping_type=None``. - id (str): name to be assigned to this Hamiltonian instance - - **Example:** - - .. note:: - As of ``v0.36``, ``qml.Hamiltonian`` dispatches to :class:`~.pennylane.ops.op_math.LinearCombination` - by default, so the following examples assume this behaviour. - - ``qml.Hamiltonian`` takes in a list of coefficients and a list of operators. - - >>> coeffs = [0.2, -0.543] - >>> obs = [qml.X(0) @ qml.Z(1), qml.Z(0) @ qml.Hadamard(2)] - >>> H = qml.Hamiltonian(coeffs, obs) - >>> print(H) - 0.2 * (X(0) @ Z(1)) + -0.543 * (Z(0) @ H(2)) - - The coefficients can be a trainable tensor, for example: - - >>> coeffs = tf.Variable([0.2, -0.543], dtype=tf.double) - >>> obs = [qml.X(0) @ qml.Z(1), qml.Z(0) @ qml.Hadamard(2)] - >>> H = qml.Hamiltonian(coeffs, obs) - >>> print(H) - 0.2 * (X(0) @ Z(1)) + -0.543 * (Z(0) @ H(2)) - - A ``qml.Hamiltonian`` stores information on which commuting observables should be measured - together in a circuit: - - >>> obs = [qml.X(0), qml.X(1), qml.Z(0)] - >>> coeffs = np.array([1., 2., 3.]) - >>> H = qml.Hamiltonian(coeffs, obs, grouping_type='qwc') - >>> H.grouping_indices - ((0, 1), (2,)) - - This attribute can be used to compute groups of coefficients and observables: - - >>> grouped_coeffs = [coeffs[list(indices)] for indices in H.grouping_indices] - >>> grouped_obs = [[H.ops[i] for i in indices] for indices in H.grouping_indices] - >>> grouped_coeffs - [array([1., 2.]), array([3.])] - >>> grouped_obs - [[X(0), X(1)], [Z(0)]] - - Devices that evaluate a ``qml.Hamiltonian`` expectation by splitting it into its local - observables can use this information to reduce the number of circuits evaluated. - - Note that one can compute the ``grouping_indices`` for an already initialized ``qml.Hamiltonian`` - by using the :func:`compute_grouping ` method. - - .. details:: - :title: Old Hamiltonian behaviour - - The following code examples show the behaviour of ``qml.Hamiltonian`` using old operator - arithmetic. See :doc:`Updated Operators ` for more details. The old - behaviour can be reactivated by calling the deprecated - - >>> qml.operation.disable_new_opmath() - - Alternatively, ``qml.ops.Hamiltonian`` provides a permanent access point for Hamiltonian - behaviour before ``v0.36``. - - >>> coeffs = [0.2, -0.543] - >>> obs = [qml.X(0) @ qml.Z(1), qml.Z(0) @ qml.Hadamard(2)] - >>> H = qml.Hamiltonian(coeffs, obs) - >>> print(H) - (-0.543) [Z0 H2] - + (0.2) [X0 Z1] - - The coefficients can be a trainable tensor, for example: - - >>> coeffs = tf.Variable([0.2, -0.543], dtype=tf.double) - >>> obs = [qml.X(0) @ qml.Z(1), qml.Z(0) @ qml.Hadamard(2)] - >>> H = qml.Hamiltonian(coeffs, obs) - >>> print(H) - (-0.543) [Z0 H2] - + (0.2) [X0 Z1] - - The user can also provide custom observables: - - >>> obs_matrix = np.array([[0.5, 1.0j, 0.0, -3j], - [-1.0j, -1.1, 0.0, -0.1], - [0.0, 0.0, -0.9, 12.0], - [3j, -0.1, 12.0, 0.0]]) - >>> obs = qml.Hermitian(obs_matrix, wires=[0, 1]) - >>> H = qml.Hamiltonian((0.8, ), (obs, )) - >>> print(H) - (0.8) [Hermitian0,1] - - Alternatively, the :func:`~.molecular_hamiltonian` function from the - :doc:`/introduction/chemistry` module can be used to generate a molecular - Hamiltonian. - - In many cases, Hamiltonians can be constructed using Pythonic arithmetic operations. - For example: - - >>> qml.Hamiltonian([1.], [qml.X(0)]) + 2 * qml.Z(0) @ qml.Z(1) - - is equivalent to the following Hamiltonian: - - >>> qml.Hamiltonian([1, 2], [qml.X(0), qml.Z(0) @ qml.Z(1)]) - - While scalar multiplication requires native python floats or integer types, - addition, subtraction, and tensor multiplication of Hamiltonians with Hamiltonians or - other observables is possible with tensor-valued coefficients, i.e., - - >>> H1 = qml.Hamiltonian(torch.tensor([1.]), [qml.X(0)]) - >>> H2 = qml.Hamiltonian(torch.tensor([2., 3.]), [qml.Y(0), qml.X(1)]) - >>> obs3 = [qml.X(0), qml.Y(0), qml.X(1)] - >>> H3 = qml.Hamiltonian(torch.tensor([1., 2., 3.]), obs3) - >>> H3.compare(H1 + H2) - True - - A Hamiltonian can store information on which commuting observables should be measured together in - a circuit: - - >>> obs = [qml.X(0), qml.X(1), qml.Z(0)] - >>> coeffs = np.array([1., 2., 3.]) - >>> H = qml.Hamiltonian(coeffs, obs, grouping_type='qwc') - >>> H.grouping_indices - [[0, 1], [2]] - - This attribute can be used to compute groups of coefficients and observables: - - >>> grouped_coeffs = [coeffs[indices] for indices in H.grouping_indices] - >>> grouped_obs = [[H.ops[i] for i in indices] for indices in H.grouping_indices] - >>> grouped_coeffs - [tensor([1., 2.], requires_grad=True), tensor([3.], requires_grad=True)] - >>> grouped_obs - [[qml.X(0), qml.X(1)], [qml.Z(0)]] - - Devices that evaluate a Hamiltonian expectation by splitting it into its local observables can - use this information to reduce the number of circuits evaluated. - - Note that one can compute the ``grouping_indices`` for an already initialized Hamiltonian by - 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 - - def _flatten(self) -> FlatPytree: - # note that we are unable to restore grouping type or method without creating new properties - return (self.data, self._ops), (self.grouping_indices,) - - @classmethod - def _unflatten( - cls, data: tuple[tuple[float, ...], list[Observable]], metadata: tuple[list[list[int]]] - ): - return cls(data[0], data[1], _grouping_indices=metadata[0]) - - # pylint: disable=arguments-differ - @classmethod - def _primitive_bind_call(cls, coeffs, observables, **kwargs): - return cls._primitive.bind(*coeffs, *observables, **kwargs, n_obs=len(observables)) - - def __init__( - self, - coeffs: TensorLike, - observables: Iterable[Observable], - grouping_type: Literal[None, "qwc", "commuting", "anticommuting"] = None, - _grouping_indices: Optional[list[list[int]]] = None, - method: Literal["lf", "rlf"] = "rlf", - id: str = None, - ): - warn( - "qml.ops.Hamiltonian uses the old approach to operator arithmetic, which will become " - "unavailable in version 0.40 of PennyLane. If you are experiencing issues, visit " - "https://docs.pennylane.ai/en/stable/news/new_opmath.html or contact the PennyLane " - "team on the discussion forum: https://discuss.pennylane.ai/.", - qml.PennyLaneDeprecationWarning, - ) - - if qml.math.shape(coeffs)[0] != len(observables): - raise ValueError( - "Could not create valid Hamiltonian; " - "number of coefficients and operators does not match." - ) - - for obs in observables: - if not isinstance(obs, Observable): - raise ValueError( - "Could not create circuits. Some or all observables are not valid." - ) - - self._coeffs = coeffs - self._ops = list(observables) - - # TODO: avoid having multiple ways to store ops and coeffs, - # ideally only use parameters for coeffs, and hyperparameters for ops - self._hyperparameters = {"ops": self._ops} - - self._wires = qml.wires.Wires.all_wires([op.wires for op in self.ops], sort=True) - - # attribute to store indices used to form groups of - # commuting observables, since recomputation is costly - self._grouping_indices = _grouping_indices - - if grouping_type is not None: - with qml.QueuingManager.stop_recording(): - self._grouping_indices = _compute_grouping_indices( - self.ops, grouping_type=grouping_type, method=method - ) - - coeffs_flat = [self._coeffs[i] for i in range(qml.math.shape(self._coeffs)[0])] - - # create the operator using each coefficient as a separate parameter; - # this causes H.data to be a list of tensor scalars, - # while H.coeffs is the original tensor - - super().__init__(*coeffs_flat, wires=self._wires, id=id) - self._pauli_rep = "unset" - - def __len__(self) -> int: - """The number of terms in the Hamiltonian.""" - return len(self.ops) - - @property - def pauli_rep(self) -> Optional["qml.pauli.PauliSentence"]: - if self._pauli_rep != "unset": - return self._pauli_rep - - if any(op.pauli_rep is None for op in self.ops): - self._pauli_rep = None - return self._pauli_rep - - ps = qml.pauli.PauliSentence() - for coeff, term in zip(*self.terms()): - ps += term.pauli_rep * coeff - - self._pauli_rep = ps - return self._pauli_rep - - def _check_batching(self): - """Override for Hamiltonian, batching is not yet supported.""" - - def label( - self, - decimals: Optional[int] = None, - base_label: Optional[str] = None, - cache: Optional[dict] = None, - ): - decimals = None if (len(self.parameters) > 3) else decimals - return super().label(decimals=decimals, base_label=base_label or "𝓗", cache=cache) - - @property - def coeffs(self) -> TensorLike: - """Return the coefficients defining the Hamiltonian. - - Returns: - Sequence[float]): coefficients in the Hamiltonian expression - """ - return self._coeffs - - @property - def ops(self) -> list[Observable]: - """Return the operators defining the Hamiltonian. - - Returns: - list[Observable]): observables in the Hamiltonian expression - """ - return self._ops - - def terms(self) -> tuple[list[TensorLike], list[Observable]]: - r"""Representation of the operator as a linear combination of other operators. - - .. math:: O = \sum_i c_i O_i - - .. seealso:: :meth:`~.Hamiltonian.terms` - - Returns: - tuple[Iterable[tensor_like or float], list[.Operator]]: coefficients and operations - - **Example** - >>> coeffs = [1., 2.] - >>> ops = [qml.X(0), qml.Z(0)] - >>> H = qml.Hamiltonian(coeffs, ops) - - >>> H.terms() - [1., 2.], [qml.X(0), qml.Z(0)] - - The coefficients are differentiable and can be stored as tensors: - >>> import tensorflow as tf - >>> H = qml.Hamiltonian([tf.Variable(1.), tf.Variable(2.)], [qml.X(0), qml.Z(0)]) - >>> t = H.terms() - - >>> t[0] - [, ] - """ - return self.parameters, self.ops - - @property - def wires(self) -> Wires: - r"""The sorted union of wires from all operators. - - Returns: - (Wires): Combined wires present in all terms, sorted. - """ - return self._wires - - @property - def name(self) -> str: - return "Hamiltonian" - - @property - def grouping_indices(self) -> Optional[list[list[int]]]: - """Return the grouping indices attribute. - - Returns: - list[list[int]]: indices needed to form groups of commuting observables - """ - return self._grouping_indices - - @grouping_indices.setter - def grouping_indices(self, value: Iterable[Iterable[int]]): - """Set the grouping indices, if known without explicit computation, or if - computation was done externally. The groups are not verified. - - **Example** - - Examples of valid groupings for the Hamiltonian - - >>> H = qml.Hamiltonian([qml.X('a'), qml.X('b'), qml.Y('b')]) - - are - - >>> H.grouping_indices = [[0, 1], [2]] - - or - - >>> H.grouping_indices = [[0, 2], [1]] - - since both ``qml.X('a'), qml.X('b')`` and ``qml.X('a'), qml.Y('b')`` commute. - - - Args: - value (list[list[int]]): List of lists of indexes of the observables in ``self.ops``. Each sublist - represents a group of commuting observables. - """ - - if ( - not isinstance(value, Iterable) - or any(not isinstance(sublist, Iterable) for sublist in value) - or any(i not in range(len(self.ops)) for i in [i for sl in value for i in sl]) - ): - raise ValueError( - f"The grouped index value needs to be a tuple of tuples of integers between 0 and the " - f"number of observables in the Hamiltonian; got {value}" - ) - # make sure all tuples so can be hashable - self._grouping_indices = tuple(tuple(sublist) for sublist in value) - - def compute_grouping( - self, - grouping_type: Literal["qwc", "commuting", "anticommuting"] = "qwc", - method: Literal["lf", "rlf"] = "lf", - ): - """ - Compute groups of indices corresponding to commuting observables of this - Hamiltonian, and store it in the ``grouping_indices`` attribute. - - Args: - grouping_type (str): The type of binary relation between Pauli words used to compute the grouping. - Can be ``'qwc'``, ``'commuting'``, or ``'anticommuting'``. - method (str): The graph colouring heuristic to use in solving minimum clique cover for grouping, which - can be ``'lf'`` (Largest First) or ``'rlf'`` (Recursive Largest First). - """ - - with qml.QueuingManager.stop_recording(): - self._grouping_indices = _compute_grouping_indices( - self.ops, grouping_type=grouping_type, method=method - ) - - def sparse_matrix(self, wire_order: Optional[WiresLike] = None) -> scipy.sparse.csr_matrix: - r"""Computes the sparse matrix representation of a Hamiltonian in the computational basis. - - Args: - wire_order (Iterable): global wire order, must contain all wire labels from the operator's wires. - If not provided, the default order of the wires (self.wires) of the Hamiltonian is used. - - Returns: - csr_matrix: a sparse matrix in scipy Compressed Sparse Row (CSR) format with dimension - :math:`(2^n, 2^n)`, where :math:`n` is the number of wires - - **Example:** - - >>> coeffs = [1, -0.45] - >>> obs = [qml.Z(0) @ qml.Z(1), qml.Y(0) @ qml.Z(1)] - >>> H = qml.Hamiltonian(coeffs, obs) - >>> H_sparse = H.sparse_matrix() - >>> H_sparse - <4x4 sparse matrix of type '' - with 8 stored elements in Compressed Sparse Row format> - - The resulting sparse matrix can be either used directly or transformed into a numpy array: - - >>> H_sparse.toarray() - array([[ 1.+0.j , 0.+0.j , 0.+0.45j, 0.+0.j ], - [ 0.+0.j , -1.+0.j , 0.+0.j , 0.-0.45j], - [ 0.-0.45j, 0.+0.j , -1.+0.j , 0.+0.j ], - [ 0.+0.j , 0.+0.45j, 0.+0.j , 1.+0.j ]]) - """ - if wire_order is None: - wires = self.wires - else: - wires = wire_order - n = len(wires) - matrix = scipy.sparse.csr_matrix((2**n, 2**n), dtype="complex128") - - coeffs = qml.math.toarray(self.data) - - temp_mats = [] - for coeff, op in zip(coeffs, self.ops): - obs = [] - for o in qml.operation.Tensor(op).obs: - if len(o.wires) > 1: - # todo: deal with operations created from multi-qubit operations such as Hermitian - raise ValueError( - f"Can only sparsify Hamiltonians whose constituent observables consist of " - f"(tensor products of) single-qubit operators; got {op}." - ) - obs.append(o.matrix()) - - # Array to store the single-wire observables which will be Kronecker producted together - mat = [] - # i_count tracks the number of consecutive single-wire identity matrices encountered - # in order to avoid unnecessary Kronecker products, since I_n x I_m = I_{n+m} - i_count = 0 - for wire_lab in wires: - if wire_lab in op.wires: - if i_count > 0: - mat.append(scipy.sparse.eye(2**i_count, format="coo")) - i_count = 0 - idx = op.wires.index(wire_lab) - # obs is an array storing the single-wire observables which - # make up the full Hamiltonian term - sp_obs = scipy.sparse.coo_matrix(obs[idx]) - mat.append(sp_obs) - else: - i_count += 1 - - if i_count > 0: - mat.append(scipy.sparse.eye(2**i_count, format="coo")) - - red_mat = ( - functools.reduce(lambda i, j: scipy.sparse.kron(i, j, format="coo"), mat) * coeff - ) - - temp_mats.append(red_mat.tocsr()) - # Value of 100 arrived at empirically to balance time savings vs memory use. At this point - # the `temp_mats` are summed into the final result and the temporary storage array is - # cleared. - if (len(temp_mats) % 100) == 0: - matrix += sum(temp_mats) - temp_mats = [] - - matrix += sum(temp_mats) - return matrix - - def simplify(self) -> "Hamiltonian": - r"""Simplifies the Hamiltonian by combining like-terms. - - **Example** - - >>> ops = [qml.Y(2), qml.X(0) @ qml.Identity(1), qml.X(0)] - >>> H = qml.Hamiltonian([1, 1, -2], ops) - >>> H.simplify() - >>> print(H) - (-1) [X0] - + (1) [Y2] - - .. warning:: - - Calling this method will reset ``grouping_indices`` to None, since - the observables it refers to are updated. - """ - - # Todo: make simplify return a new operation, so - # it does not mutate this one - - new_coeffs = [] - new_ops = [] - - for i in range(len(self.ops)): # pylint: disable=consider-using-enumerate - op = self.ops[i] - c = self.coeffs[i] - op = op if isinstance(op, Tensor) else Tensor(op) - - ind = next((j for j, o in enumerate(new_ops) if op.compare(o)), None) - if ind is not None: - new_coeffs[ind] += c - if np.isclose(qml.math.toarray(new_coeffs[ind]), np.array(0.0)): - del new_coeffs[ind] - del new_ops[ind] - else: - new_ops.append(op.prune()) - new_coeffs.append(c) - - # hotfix: We `self.data`, since `self.parameters` returns a copy of the data and is now returned in - # self.terms(). To be improved soon. - self.data = tuple(new_coeffs) - # hotfix: We overwrite the hyperparameter entry, which is now returned in self.terms(). - # To be improved soon. - self.hyperparameters["ops"] = new_ops - - self._coeffs = qml.math.stack(new_coeffs) if new_coeffs else [] - self._ops = new_ops - self._wires = qml.wires.Wires.all_wires([op.wires for op in self.ops], sort=True) - # reset grouping, since the indices refer to the old observables and coefficients - self._grouping_indices = None - return self - - def __str__(self) -> str: - def wires_print(ob: Observable): - """Function that formats the wires.""" - return ",".join(map(str, ob.wires.tolist())) - - list_of_coeffs = self.data # list of scalar tensors - paired_coeff_obs = list(zip(list_of_coeffs, self.ops)) - paired_coeff_obs.sort(key=lambda pair: (len(pair[1].wires), qml.math.real(pair[0]))) - - terms_ls = [] - - for coeff, obs in paired_coeff_obs: - if isinstance(obs, Tensor): - obs_strs = [f"{OBS_MAP.get(ob.name, ob.name)}{wires_print(ob)}" for ob in obs.obs] - ob_str = " ".join(obs_strs) - elif isinstance(obs, Observable): - ob_str = f"{OBS_MAP.get(obs.name, obs.name)}{wires_print(obs)}" - - term_str = f"({coeff}) [{ob_str}]" - - terms_ls.append(term_str) - - return " " + "\n+ ".join(terms_ls) - - def __repr__(self) -> str: - # Constructor-call-like representation - return f"" - - def _ipython_display_(self): # pragma: no-cover - """Displays __str__ in ipython instead of __repr__ - See https://ipython.readthedocs.io/en/stable/config/integrating.html - """ - if len(self.ops) < 15: - print(str(self)) - else: # pragma: no-cover - print(repr(self)) - - def _obs_data(self) -> set[tuple[TensorLike, frozenset[tuple[str, Wires, list[str]]]]]: - r"""Extracts the data from a Hamiltonian and serializes it in an order-independent fashion. - - This allows for comparison between Hamiltonians that are equivalent, but are defined with terms and tensors - expressed in different orders. For example, `qml.X(0) @ qml.Z(1)` and - `qml.Z(1) @ qml.X(0)` are equivalent observables with different orderings. - - .. Note:: - - In order to store the data from each term of the Hamiltonian in an order-independent serialization, - we make use of sets. Note that all data contained within each term must be immutable, hence the use of - strings and frozensets. - - **Example** - - >>> H = qml.Hamiltonian([1, 1], [qml.X(0) @ qml.X(1), qml.Z(0)]) - >>> print(H._obs_data()) - {(1, frozenset({('PauliX', Wires([1]), ()), ('PauliX', Wires([0]), ())})), - (1, frozenset({('PauliZ', Wires([0]), ())}))} - """ - data = set() - - coeffs_arr = qml.math.toarray(self.coeffs) - for co, op in zip(coeffs_arr, self.ops): - obs = op.non_identity_obs if isinstance(op, Tensor) else [op] - tensor = [] - for ob in obs: - parameters = tuple( - str(param) for param in ob.parameters - ) # Converts params into immutable type - if isinstance(ob, qml.GellMann): - parameters += (ob.hyperparameters["index"],) - tensor.append((ob.name, ob.wires, parameters)) - data.add((co, frozenset(tensor))) - - return data - - def compare(self, other: Observable) -> bool: - r"""Determines whether the operator is equivalent to another. - - Currently only supported for :class:`~Hamiltonian`, :class:`~.Observable`, or :class:`~.Tensor`. - Hamiltonians/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, or as a - linear combination of Hermitians. - To do so would require the matrix form of Hamiltonians and Tensors - be calculated, which would drastically increase runtime. - - Returns: - (bool): True if equivalent. - - **Examples** - - >>> H = qml.Hamiltonian( - ... [0.5, 0.5], - ... [qml.Z(0) @ qml.Y(1), qml.Y(1) @ qml.Z(0) @ qml.Identity("a")] - ... ) - >>> obs = qml.Z(0) @ qml.Y(1) - >>> print(H.compare(obs)) - True - - >>> H1 = qml.Hamiltonian([1, 1], [qml.X(0), qml.Z(1)]) - >>> H2 = qml.Hamiltonian([1, 1], [qml.Z(0), qml.X(1)]) - >>> H1.compare(H2) - False - - >>> ob1 = qml.Hamiltonian([1], [qml.X(0)]) - >>> ob2 = qml.Hermitian(np.array([[0, 1], [1, 0]]), 0) - >>> ob1.compare(ob2) - False - """ - - if isinstance(other, qml.operation.Operator): - if (pr1 := self.pauli_rep) is not None and (pr2 := other.pauli_rep) is not None: - pr1.simplify() - pr2.simplify() - return pr1 == pr2 - - if isinstance(other, Hamiltonian): - self.simplify() - other.simplify() - return self._obs_data() == other._obs_data() # pylint: disable=protected-access - - if isinstance(other, (Tensor, Observable)): - self.simplify() - return self._obs_data() == { - (1, frozenset(other._obs_data())) # pylint: disable=protected-access - } - - raise ValueError("Can only compare a Hamiltonian, and a Hamiltonian/Observable/Tensor.") - - def __matmul__(self, H: Observable) -> Observable: - r"""The tensor product operation between a Hamiltonian and a Hamiltonian/Tensor/Observable.""" - coeffs1 = copy(self.coeffs) - ops1 = self.ops.copy() - - qml.QueuingManager.remove(H) - qml.QueuingManager.remove(self) - - if isinstance(H, Hamiltonian): - shared_wires = Wires.shared_wires([self.wires, H.wires]) - if len(shared_wires) > 0: - raise ValueError( - "Hamiltonians can only be multiplied together if they act on " - "different sets of wires" - ) - - coeffs2 = H.coeffs - ops2 = H.ops - - coeffs = qml.math.kron(coeffs1, coeffs2) - ops_list = itertools.product(ops1, ops2) - terms = [qml.operation.Tensor(t[0], t[1]) for t in ops_list] - return qml.simplify(Hamiltonian(coeffs, terms)) - - if isinstance(H, (Tensor, Observable)): - terms = [op @ copy(H) for op in ops1] - - return qml.simplify(Hamiltonian(coeffs1, terms)) - - return NotImplemented - - def __rmatmul__(self, H: Observable): - r"""The tensor product operation (from the right) between a Hamiltonian and - a Hamiltonian/Tensor/Observable (ie. Hamiltonian.__rmul__(H) = H @ Hamiltonian). - """ - if isinstance(H, Hamiltonian): # can't be accessed by '@' - return H.__matmul__(self) - - coeffs1 = copy(self.coeffs) - ops1 = self.ops.copy() - - if isinstance(H, (Tensor, Observable)): - qml.QueuingManager.remove(H) - qml.QueuingManager.remove(self) - terms = [copy(H) @ op for op in ops1] - return qml.simplify(Hamiltonian(coeffs1, terms)) - - return NotImplemented - - def __add__(self, H: Observable) -> Observable: - r"""The addition operation between a Hamiltonian and a Hamiltonian/Tensor/Observable.""" - ops = self.ops.copy() - self_coeffs = copy(self.coeffs) - - if isinstance(H, numbers.Number) and H == 0: - return self - - if isinstance(H, Hamiltonian): - qml.QueuingManager.remove(H) - qml.QueuingManager.remove(self) - coeffs = qml.math.concatenate([self_coeffs, copy(H.coeffs)], axis=0) - ops.extend(H.ops.copy()) - return qml.simplify(Hamiltonian(coeffs, ops)) - - if isinstance(H, (Tensor, Observable)): - qml.QueuingManager.remove(H) - qml.QueuingManager.remove(self) - coeffs = qml.math.concatenate( - [self_coeffs, qml.math.cast_like([1.0], self_coeffs)], axis=0 - ) - ops.append(H) - return qml.simplify(Hamiltonian(coeffs, ops)) - - return NotImplemented - - __radd__ = __add__ - - def __mul__(self, a: Union[int, float]): - r"""The scalar multiplication operation between a scalar and a Hamiltonian.""" - if isinstance(a, (int, float)): - self_coeffs = copy(self.coeffs) - coeffs = qml.math.multiply(a, self_coeffs) - return Hamiltonian(coeffs, self.ops.copy()) - - return NotImplemented - - __rmul__ = __mul__ - - def __sub__(self, H: Observable) -> Observable: - r"""The subtraction operation between a Hamiltonian and a Hamiltonian/Tensor/Observable.""" - if isinstance(H, (Hamiltonian, Tensor, Observable)): - return self + (-1 * H) - - return NotImplemented - - def __iadd__(self, H: Union[Observable, numbers.Number]): - r"""The inplace addition operation between a Hamiltonian and a Hamiltonian/Tensor/Observable.""" - if isinstance(H, numbers.Number) and H == 0: - return self - - if isinstance(H, Hamiltonian): - self._coeffs = qml.math.concatenate([self._coeffs, H.coeffs], axis=0) - self._ops.extend(H.ops.copy()) - self.simplify() - return self - - if isinstance(H, (Tensor, Observable)): - self._coeffs = qml.math.concatenate( - [self._coeffs, qml.math.cast_like([1.0], self._coeffs)], axis=0 - ) - self._ops.append(H) - self.simplify() - return self - - return NotImplemented - - def __imul__(self, a: Union[int, float]): - r"""The inplace scalar multiplication operation between a scalar and a Hamiltonian.""" - if isinstance(a, (int, float)): - self._coeffs = qml.math.multiply(a, self._coeffs) - if self.pauli_rep is not None: - self._pauli_rep = qml.math.multiply(a, self._pauli_rep) - return self - - return NotImplemented - - def __isub__(self, H: Observable): - r"""The inplace subtraction operation between a Hamiltonian and a Hamiltonian/Tensor/Observable.""" - if isinstance(H, (Hamiltonian, Tensor, Observable)): - self.__iadd__(H.__mul__(-1)) - return self - - return NotImplemented - - def queue( - self, context: Union[qml.QueuingManager, qml.queuing.AnnotatedQueue] = qml.QueuingManager - ): - """Queues a qml.Hamiltonian instance""" - for o in self.ops: - context.remove(o) - context.append(self) - return self - - def map_wires(self, wire_map: dict[Hashable, Hashable]): - """Returns a copy of the current hamiltonian with its wires changed according to the given - wire map. - - Args: - wire_map (dict): dictionary containing the old wires as keys and the new wires as values - - Returns: - .Hamiltonian: new hamiltonian - """ - cls = self.__class__ - new_op = cls.__new__(cls) - new_op.data = copy(self.data) - new_op._wires = Wires( # pylint: disable=protected-access - [wire_map.get(wire, wire) for wire in self.wires] - ) - new_op._ops = [ # pylint: disable=protected-access - op.map_wires(wire_map) for op in self.ops - ] - for attr, value in vars(self).items(): - if attr not in {"data", "_wires", "_ops"}: - setattr(new_op, attr, value) - new_op.hyperparameters["ops"] = new_op._ops # pylint: disable=protected-access - new_op._pauli_rep = "unset" # pylint: disable=protected-access - return new_op - - -# The primitive will be None if jax is not installed in the environment -# If defined, we need to update the implementation to repack the coefficients and observables -# See capture module for more information -if Hamiltonian._primitive is not None: # pylint: disable=protected-access - - @Hamiltonian._primitive.def_impl # pylint: disable=protected-access - def _(*args, n_obs, **kwargs): - coeffs = args[:n_obs] - observables = args[n_obs:] - return type.__call__(Hamiltonian, coeffs, observables, **kwargs) diff --git a/pennylane/optimize/riemannian_gradient.py b/pennylane/optimize/riemannian_gradient.py index 730b45f1852..f60c55678e8 100644 --- a/pennylane/optimize/riemannian_gradient.py +++ b/pennylane/optimize/riemannian_gradient.py @@ -267,7 +267,7 @@ def __init__(self, circuit, stepsize=0.01, restriction=None, exact=False, trotte self.circuit = circuit self.circuit.construct([], {}) self.hamiltonian = circuit.func().obs - if not isinstance(self.hamiltonian, (qml.ops.Hamiltonian, qml.ops.LinearCombination)): + if not isinstance(self.hamiltonian, qml.ops.LinearCombination): raise TypeError( f"circuit must return the expectation value of a Hamiltonian," f"received {type(circuit.func().obs)}" @@ -280,9 +280,7 @@ def __init__(self, circuit, stepsize=0.01, restriction=None, exact=False, trotte f"optimizing a {self.nqubits} qubit circuit may be slow.", UserWarning, ) - if restriction is not None and not isinstance( - restriction, (qml.ops.Hamiltonian, qml.ops.LinearCombination) - ): + if restriction is not None and not isinstance(restriction, qml.ops.LinearCombination): raise TypeError(f"restriction must be a Hamiltonian, received {type(restriction)}") ( self.lie_algebra_basis_ops, diff --git a/pennylane/optimize/shot_adaptive.py b/pennylane/optimize/shot_adaptive.py index 3dbe5d48358..6fc69b01063 100644 --- a/pennylane/optimize/shot_adaptive.py +++ b/pennylane/optimize/shot_adaptive.py @@ -313,7 +313,7 @@ def _single_shot_qnode_gradients(self, qnode, args, kwargs): [expval] = tape.measurements coeffs, observables = ( expval.obs.terms() - if isinstance(expval.obs, (qml.ops.LinearCombination, qml.ops.Hamiltonian)) + if isinstance(expval.obs, qml.ops.LinearCombination) else ([1.0], [expval.obs]) ) diff --git a/pennylane/pauli/__init__.py b/pennylane/pauli/__init__.py index 394be72cb77..1f7d141d5c4 100644 --- a/pennylane/pauli/__init__.py +++ b/pennylane/pauli/__init__.py @@ -33,7 +33,6 @@ diagonalize_pauli_word, diagonalize_qwc_pauli_words, diagonalize_qwc_groupings, - simplify, pauli_eigs, ) diff --git a/pennylane/pauli/conversion.py b/pennylane/pauli/conversion.py index 9e1ddbd38be..859d6ac2670 100644 --- a/pennylane/pauli/conversion.py +++ b/pennylane/pauli/conversion.py @@ -21,18 +21,7 @@ import pennylane as qml from pennylane.math.utils import is_abstract -from pennylane.operation import Tensor -from pennylane.ops import ( - Hamiltonian, - Identity, - LinearCombination, - PauliX, - PauliY, - PauliZ, - Prod, - SProd, - Sum, -) +from pennylane.ops import Identity, LinearCombination, PauliX, PauliY, PauliZ, Prod, SProd, Sum from pennylane.ops.qubit.matrix_ops import _walsh_hadamard_transform from .pauli_arithmetic import I, PauliSentence, PauliWord, X, Y, Z, op_map @@ -230,7 +219,7 @@ def _generalized_pauli_decompose( def pauli_decompose( H, hide_identity=False, wire_order=None, pauli=False, check_hermitian=True -) -> Union[Hamiltonian, PauliSentence]: +) -> Union[LinearCombination, PauliSentence]: r"""Decomposes a Hermitian matrix into a linear combination of Pauli operators. Args: @@ -401,15 +390,6 @@ def _(op: Identity): # pylint:disable=unused-argument return PauliSentence({PauliWord({}): 1.0}) -@_pauli_sentence.register -def _(op: Tensor): - if not is_pauli_word(op): - raise ValueError(f"Op must be a linear combination of Pauli operators only, got: {op}") - - factors = (_pauli_sentence(factor) for factor in op.obs) - return reduce(lambda a, b: a @ b, factors) - - @_pauli_sentence.register def _(op: Prod): factors = (_pauli_sentence(factor) for factor in op) @@ -424,28 +404,6 @@ def _(op: SProd): return ps -@_pauli_sentence.register(qml.ops.Hamiltonian) -def _(op: qml.ops.Hamiltonian): - if not all(is_pauli_word(o) for o in op.ops): - raise ValueError(f"Op must be a linear combination of Pauli operators only, got: {op}") - - def term_2_pauli_word(term): - if isinstance(term, Tensor): - pw = {obs.wires[0]: obs.name[-1] for obs in term.non_identity_obs} - elif isinstance(term, Identity): - pw = {} - else: - pw = dict([(term.wires[0], term.name[-1])]) - return PauliWord(pw) - - ps = PauliSentence() - for coeff, term in zip(*op.terms()): - sub_ps = PauliSentence({term_2_pauli_word(term): coeff}) - ps += sub_ps - - return ps - - @_pauli_sentence.register(LinearCombination) def _(op: LinearCombination): if not all(is_pauli_word(o) for o in op.ops): diff --git a/pennylane/pauli/grouping/group_observables.py b/pennylane/pauli/grouping/group_observables.py index 948d35e50f7..45e1124d066 100644 --- a/pennylane/pauli/grouping/group_observables.py +++ b/pennylane/pauli/grouping/group_observables.py @@ -25,7 +25,6 @@ import rustworkx as rx import pennylane as qml -from pennylane.ops import Prod, SProd from pennylane.pauli.utils import ( are_identical_pauli_words, binary_to_pauli, @@ -477,8 +476,8 @@ def group_observables( graph using graph-colouring heuristic algorithms. Args: - observables (list[Observable]): a list of Pauli word ``Observable`` instances (Pauli - operation instances and :class:`~.Tensor` instances thereof) + observables (list[Operator]): a list of Pauli word ``Observable`` 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. grouping_type (str): The type of binary relation between Pauli words. @@ -537,18 +536,7 @@ def group_observables( wires_obs, grouping_type=grouping_type, graph_colourer=method ) - # Handles legacy op_math - temp_opmath = not qml.operation.active_new_opmath() and any( - isinstance(o, (Prod, SProd)) for o in observables - ) - if temp_opmath: - qml.operation.enable_new_opmath(warn=False) - - try: - partitioned_paulis = pauli_groupper.partition_observables() - finally: - if temp_opmath: - qml.operation.disable_new_opmath(warn=False) + partitioned_paulis = pauli_groupper.partition_observables() # Add observables without wires back to the first partition partitioned_paulis[0].extend(no_wires_obs) @@ -582,15 +570,6 @@ def _partition_coeffs(partitioned_paulis, observables, coefficients): for pauli_word in partition: # find index of this pauli word in remaining original observables, for ind, observable in enumerate(observables): - if isinstance(observable, qml.ops.Hamiltonian): - # are_identical_pauli_words cannot handle Hamiltonian - coeffs, ops = observable.terms() - # Assuming the Hamiltonian has only one term - observable = qml.s_prod(coeffs[0], ops[0]) - if isinstance(pauli_word, qml.ops.Hamiltonian): - # Need to add this case because rx methods do not change type of observables. - coeffs, ops = pauli_word.terms() - pauli_word = qml.s_prod(coeffs[0], ops[0]) if are_identical_pauli_words(pauli_word, observable): indices.append(coeff_indices[ind]) observables.pop(ind) diff --git a/pennylane/pauli/pauli_arithmetic.py b/pennylane/pauli/pauli_arithmetic.py index 9822fd6cadf..b453582397e 100644 --- a/pennylane/pauli/pauli_arithmetic.py +++ b/pennylane/pauli/pauli_arithmetic.py @@ -15,14 +15,12 @@ # pylint:disable=protected-access from copy import copy from functools import lru_cache, reduce -from warnings import warn import numpy as np from scipy import sparse import pennylane as qml from pennylane import math -from pennylane.operation import Tensor from pennylane.ops import Identity, PauliX, PauliY, PauliZ, Prod, SProd, Sum from pennylane.typing import TensorLike from pennylane.wires import Wires @@ -506,7 +504,7 @@ def _get_csr_indices(self, wire_order): current_size *= 2 return indices - def operation(self, wire_order=None, get_as_tensor=False): + def operation(self, wire_order=None): """Returns a native PennyLane :class:`~pennylane.operation.Operation` representing the PauliWord.""" if len(self) == 0: return Identity(wires=wire_order) @@ -517,33 +515,9 @@ def operation(self, wire_order=None, get_as_tensor=False): else: factors = [_make_operation(op, wire) for wire, op in self.items()] - if get_as_tensor: - return factors[0] if len(factors) == 1 else Tensor(*factors) pauli_rep = PauliSentence({self: 1}) return factors[0] if len(factors) == 1 else Prod(*factors, _pauli_rep=pauli_rep) - def hamiltonian(self, wire_order=None): - """Return :class:`~pennylane.Hamiltonian` representing the PauliWord. - - .. warning:: - - :meth:`~pennylane.pauli.PauliWord.hamiltonian` is deprecated. Instead, please use - :meth:`~pennylane.pauli.PauliWord.operation` - - """ - warn( - "PauliWord.hamiltonian() is deprecated. Please use PauliWord.operation() instead.", - qml.PennyLaneDeprecationWarning, - ) - - if len(self) == 0: - if wire_order in (None, [], Wires([])): - raise ValueError("Can't get the Hamiltonian for an empty PauliWord.") - return qml.Hamiltonian([1], [Identity(wires=wire_order)]) - - obs = [_make_operation(op, wire) for wire, op in self.items()] - return qml.Hamiltonian([1], [obs[0] if len(obs) == 1 else Tensor(*obs)]) - def map_wires(self, wire_map: dict) -> "PauliWord": """Return a new PauliWord with the wires mapped.""" return self.__class__({wire_map.get(w, w): op for w, op in self.items()}) @@ -1038,33 +1012,6 @@ def operation(self, wire_order=None): summands.append(pw_op if coeff == 1 else SProd(coeff, pw_op, _pauli_rep=rep)) return summands[0] if len(summands) == 1 else Sum(*summands, _pauli_rep=self) - def hamiltonian(self, wire_order=None): - """Returns a native PennyLane :class:`~pennylane.Hamiltonian` representing the PauliSentence. - - .. warning:: - - :meth:`~pennylane.pauli.PauliSentence.hamiltonian` is deprecated. Instead, please use - :meth:`~pennylane.pauli.PauliSentence.operation` - - """ - warn( - "PauliSentence.hamiltonian() is deprecated. Please use PauliSentence.operation() instead.", - qml.PennyLaneDeprecationWarning, - ) - - if len(self) == 0: - if wire_order in (None, [], Wires([])): - raise ValueError("Can't get the Hamiltonian for an empty PauliSentence.") - return qml.Hamiltonian([], []) - - wire_order = wire_order or self.wires - wire_order = list(wire_order) - - return qml.Hamiltonian( - list(self.values()), - [pw.operation(wire_order=wire_order, get_as_tensor=True) for pw in self], - ) - def simplify(self, tol=1e-8): """Remove any PauliWords in the PauliSentence with coefficients less than the threshold tolerance.""" items = list(self.items()) diff --git a/pennylane/pauli/pauli_interface.py b/pennylane/pauli/pauli_interface.py index 064ebd20170..9d252ab6e60 100644 --- a/pennylane/pauli/pauli_interface.py +++ b/pennylane/pauli/pauli_interface.py @@ -17,19 +17,8 @@ from functools import singledispatch from typing import Union -from pennylane.operation import Tensor -from pennylane.ops import ( - Hamiltonian, - Identity, - LinearCombination, - PauliX, - PauliY, - PauliZ, - Prod, - SProd, -) - -from .conversion import pauli_sentence +from pennylane.ops import Identity, LinearCombination, PauliX, PauliY, PauliZ, Prod, SProd + from .utils import is_pauli_word @@ -74,16 +63,8 @@ def _pw_prefactor_pauli( return 1 -@_pauli_word_prefactor.register -def _pw_prefactor_tensor(observable: Tensor): - if is_pauli_word(observable): - return list(pauli_sentence(observable).values())[0] # only one term, - raise ValueError(f"Expected a valid Pauli word, got {observable}") - - -@_pauli_word_prefactor.register(Hamiltonian) @_pauli_word_prefactor.register(LinearCombination) -def _pw_prefactor_ham(observable: Union[Hamiltonian, LinearCombination]): +def _pw_prefactor_ham(observable: LinearCombination): if is_pauli_word(observable): return observable.coeffs[0] raise ValueError(f"Expected a valid Pauli word, got {observable}") diff --git a/pennylane/pauli/utils.py b/pennylane/pauli/utils.py index 8f0ec975ec0..42ea2600af1 100644 --- a/pennylane/pauli/utils.py +++ b/pennylane/pauli/utils.py @@ -23,22 +23,11 @@ from functools import lru_cache, singledispatch from itertools import product from typing import Union -from warnings import warn import numpy as np import pennylane as qml -from pennylane.operation import Tensor -from pennylane.ops import ( - Hamiltonian, - Identity, - LinearCombination, - PauliX, - PauliY, - PauliZ, - Prod, - SProd, -) +from pennylane.ops import Identity, PauliX, PauliY, PauliZ, Prod, SProd, Sum from pennylane.wires import Wires # To make this quicker later on @@ -68,13 +57,11 @@ def is_pauli_word(observable): * A single pauli operator (see :class:`~.PauliX` for an example). - * A :class:`.Tensor` instance containing Pauli operators. - * A :class:`.Prod` instance containing Pauli operators. * A :class:`.SProd` instance containing a valid Pauli word. - * A :class:`.Hamiltonian` instance with only one term. + * A :class:`.Sum` instance with only one term. .. Warning:: @@ -123,16 +110,10 @@ def _is_pw_pauli( return True -@_is_pauli_word.register -def _is_pw_tensor(observable: Tensor): - pauli_word_names = ["Identity", "PauliX", "PauliY", "PauliZ"] - return set(observable.name).issubset(pauli_word_names) - - -@_is_pauli_word.register(Hamiltonian) -@_is_pauli_word.register(LinearCombination) -def _is_pw_ham(observable: Union[Hamiltonian, LinearCombination]): - return False if len(observable.ops) != 1 else is_pauli_word(observable.ops[0]) +@_is_pauli_word.register(Sum) +def _is_pw_ham(observable: Sum): + ops = observable.terms()[1] + return False if len(ops) != 1 else is_pauli_word(ops[0]) @_is_pauli_word.register @@ -149,20 +130,19 @@ def are_identical_pauli_words(pauli_1, pauli_2): # pylint: disable=isinstance-second-argument-not-valid-type """Performs a check if two Pauli words have the same ``wires`` and ``name`` attributes. - This is a convenience function that checks if two given :class:`~.Tensor` or :class:`~.Prod` + This is a convenience function that checks if two given :class:`~.Prod` instances specify the same Pauli word. Args: - pauli_1 (Union[Identity, PauliX, PauliY, PauliZ, Tensor, Prod, SProd]): the first Pauli word - pauli_2 (Union[Identity, PauliX, PauliY, PauliZ, Tensor, Prod, SProd]): the second Pauli word + pauli_1 (Union[Identity, PauliX, PauliY, PauliZ, Prod, SProd]): the first Pauli word + pauli_2 (Union[Identity, PauliX, PauliY, PauliZ, Prod, SProd]): the second Pauli word Returns: bool: whether ``pauli_1`` and ``pauli_2`` have the same wires and name attributes Raises: TypeError: if ``pauli_1`` or ``pauli_2`` are not :class:`~.Identity`, :class:`~.PauliX`, - :class:`~.PauliY`, :class:`~.PauliZ`, :class:`~.Tensor`, :class:`~.SProd`, or - :class:`~.Prod` instances + :class:`~.PauliY`, :class:`~.PauliZ`, :class:`~.SProd`, or :class:`~.Prod` instances **Example** @@ -173,8 +153,6 @@ def are_identical_pauli_words(pauli_1, pauli_2): >>> are_identical_pauli_words(qml.Z(0) @ qml.Z(1), qml.Z(0) @ qml.X(3)) False """ - if pauli_1.name == "Hamiltonian" or pauli_2.name == "Hamiltonian": - return False if not (is_pauli_word(pauli_1) and is_pauli_word(pauli_2)): raise TypeError(f"Expected Pauli word observables, instead got {pauli_1} and {pauli_2}.") @@ -192,7 +170,7 @@ def pauli_to_binary(pauli_word, n_qubits=None, wire_map=None, check_is_pauli_wor PauliX placements while the last half specify PauliZ placements. Args: - pauli_word (Union[Identity, PauliX, PauliY, PauliZ, Tensor, Prod, SProd]): the Pauli word to be + pauli_word (Union[Identity, PauliX, PauliY, PauliZ, Prod, SProd]): the Pauli word to be converted to binary vector representation n_qubits (int): number of qubits to specify dimension of binary vector representation wire_map (dict): dictionary containing all wire labels used in the Pauli word as keys, and @@ -308,11 +286,9 @@ def binary_to_pauli(binary_vector, wire_map=None): # pylint: disable=too-many-b unique integer labels as their values Returns: - Union[Tensor, Prod]: The Pauli word corresponding to the input binary vector. + Union[Prod]: The Pauli word corresponding to the input binary vector. Note that if a zero vector is input, then the resulting Pauli word will be - an :class:`~.Identity` instance. If new operator arithmetic is enabled via - :func:`~.pennylane.operation.enable_new_opmath`, a :class:`~.Prod` will be - returned, else a :class:`~.Tensor` will be returned. + an :class:`~.Identity` instance. Raises: TypeError: if length of binary vector is not even, or if vector does not have strictly @@ -324,13 +300,13 @@ def binary_to_pauli(binary_vector, wire_map=None): # pylint: disable=too-many-b components, i.e., the ``i`` and ``N+i`` components specify the Pauli operation on wire ``i``, >>> binary_to_pauli([0,1,1,0,1,0]) - Tensor(Y(1), X(2)) + Y(1) @ X(2) An arbitrary labelling can be assigned by using ``wire_map``: >>> wire_map = {'a': 0, 'b': 1, 'c': 2} >>> binary_to_pauli([0,1,1,0,1,0], wire_map=wire_map) - Tensor(Y('b'), X('c')) + Y('b') @ X('c') Note that the values of ``wire_map``, if specified, must be ``0,1,..., N``, where ``N`` is the dimension of the vector divided by two, i.e., @@ -393,13 +369,11 @@ def pauli_word_to_string(pauli_word, wire_map=None): * A single pauli operator (see :class:`~.PauliX` for an example). - * A :class:`.Tensor` instance containing Pauli operators. - * A :class:`.Prod` instance containing Pauli operators. * A :class:`.SProd` instance containing a Pauli operator. - * A :class:`.Hamiltonian` instance with only one term. + * A :class:`.Sum` instance with only one term. Given a Pauli in observable form, convert it into string of characters from ``['I', 'X', 'Y', 'Z']``. This representation is required for @@ -421,8 +395,8 @@ def pauli_word_to_string(pauli_word, wire_map=None): 'X' Args: - pauli_word (Observable): an observable, either a :class:`~.Tensor` instance or - single-qubit observable representing a Pauli group element. + pauli_word (Union[Observable, Prod, SProd, Sum]): 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 @@ -443,9 +417,6 @@ def pauli_word_to_string(pauli_word, wire_map=None): if not is_pauli_word(pauli_word): raise TypeError(f"Expected Pauli word observables, instead got {pauli_word}") - if isinstance(pauli_word, qml.ops.Hamiltonian): - # hamiltonian contains only one term - return _pauli_word_to_string_legacy(pauli_word, wire_map) pr = next(iter(pauli_word.pauli_rep.keys())) @@ -464,36 +435,6 @@ def pauli_word_to_string(pauli_word, wire_map=None): return "".join(pauli_string) -def _pauli_word_to_string_legacy(pauli_word, wire_map): - """Turn a legacy Hamiltonian operator to strings""" - # TODO: Give Hamiltonian a pauli rep to make this branch obsolete - pauli_word = pauli_word.ops[0] - - # If there is no wire map, we must infer from the structure of Paulis - if wire_map is None: - wire_map = {pauli_word.wires.labels[i]: i for i in range(len(pauli_word.wires))} - - character_map = {"Identity": "I", "PauliX": "X", "PauliY": "Y", "PauliZ": "Z"} - - n_qubits = len(wire_map) - - # Set default value of all characters to identity - pauli_string = ["I"] * n_qubits - - # Special case is when there is a single Pauli term - if not isinstance(pauli_word.name, list): - if pauli_word.name != "Identity": - wire_idx = wire_map[pauli_word.wires[0]] - pauli_string[wire_idx] = character_map[pauli_word.name] - return "".join(pauli_string) - - for name, wire_label in zip(pauli_word.name, pauli_word.wires): - wire_idx = wire_map[wire_label] - pauli_string[wire_idx] = character_map[name] - - return "".join(pauli_string) - - def string_to_pauli_word(pauli_string, wire_map=None): """Convert a string in terms of ``'I'``, ``'X'``, ``'Y'``, and ``'Z'`` into a Pauli word for the given wire map. @@ -561,14 +502,24 @@ def string_to_pauli_word(pauli_string, wire_map=None): def pauli_word_to_matrix(pauli_word, wire_map=None): """Convert a Pauli word from a tensor to its matrix representation. + A Pauli word can be either: + + * A single pauli operator (see :class:`~.PauliX` for an example). + + * A :class:`.Prod` instance containing Pauli operators. + + * A :class:`.SProd` instance containing a Pauli operator. + + * A :class:`.Sum` instance with only one term. + The matrix representation of a Pauli word has dimension :math:`2^n \\times 2^n`, where :math:`n` is the number of qubits provided in ``wire_map``. For wires that the Pauli word does not act on, identities must be inserted into the tensor product at the correct positions. Args: - pauli_word (Observable): an observable, either a :class:`~.Tensor`, :class:`~.Prod` or - single-qubit observable representing a Pauli group element. + pauli_word (Union[Observable, Prod, SProd, Sum]): 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 @@ -741,7 +692,7 @@ def observables_to_binary_matrix(observables, n_qubits=None, wire_map=None): being acted on non-trivially by the Pauli words in observables. Args: - observables (list[Union[Identity, PauliX, PauliY, PauliZ, Tensor, Prod, SProd]]): the list + observables (list[Union[Identity, PauliX, PauliY, PauliZ, Prod, SProd]]): the list of Pauli words n_qubits (int): number of qubits to specify dimension of binary vector representation wire_map (dict): dictionary containing all wire labels used in the Pauli words as keys, and @@ -1070,7 +1021,7 @@ def diagonalize_pauli_word(pauli_word): Raises: TypeError: if the input is not a Pauli word, i.e., a Pauli operator, - :class:`~.Identity`, or :class:`~.Tensor` instances thereof + :class:`~.Identity`, or tensor products thereof **Example** @@ -1088,9 +1039,6 @@ def diagonalize_pauli_word(pauli_word): if not components: return qml.Identity(wires=pauli_word.wires) - if isinstance(pauli_word, Tensor): - return components[0] if len(components) == 1 else Tensor(*components) - prod = qml.prod(*components) coeff = pauli_word.pauli_rep[pw] return prod if qml.math.allclose(coeff, 1) else coeff * prod @@ -1133,7 +1081,7 @@ def diagonalize_qwc_pauli_words( new_ops = [] for term in qwc_grouping: pauli_rep = term.pauli_rep - if pauli_rep is None or len(pauli_rep) > 1 or term.name == "Hamiltonian": + if pauli_rep is None or len(pauli_rep) > 1: raise ValueError("This function only supports pauli words.") pw = next(iter(pauli_rep)) for wire, pauli_type in pw.items(): @@ -1205,64 +1153,6 @@ def diagonalize_qwc_groupings(qwc_groupings): return post_rotations, diag_groupings -# from observable_hf.py ------------------------- -def simplify(h, cutoff=1.0e-12): - r"""Add together identical terms in the Hamiltonian. - - The Hamiltonian terms with identical Pauli words are added together and eliminated if the - overall coefficient is smaller than a cutoff value. - - .. warning:: - - :func:`~pennylane.pauli.simplify` is deprecated. Instead, please use :func:`pennylane.simplify` - or :meth:`~pennylane.operation.Operator.simplify`. - - Args: - h (Hamiltonian): PennyLane Hamiltonian - cutoff (float): cutoff value for discarding the negligible terms - - Returns: - Hamiltonian: Simplified PennyLane Hamiltonian - - **Example** - - >>> c = np.array([0.5, 0.5]) - >>> h = qml.Hamiltonian(c, [qml.X(0) @ qml.Y(1), qml.X(0) @ qml.Y(1)]) - >>> print(simplify(h)) - (1.0) [X0 Y1] - """ - warn( - "qml.pauli.simplify() has been deprecated. Instead, please use " - "qml.simplify(op) or op.simplify().", - qml.PennyLaneDeprecationWarning, - ) - wiremap = dict(zip(h.wires, range(len(h.wires) + 1))) - - c, o = [], [] - for i, op in enumerate(h.ops): - op = qml.operation.Tensor(op).prune() - op = qml.pauli.pauli_word_to_string(op, wire_map=wiremap) - if op not in o: - c.append(h.coeffs[i]) - o.append(op) - else: - c[o.index(op)] += h.coeffs[i] - - coeffs, ops = [], [] - c = qml.math.convert_like(c, c[0]) - nonzero_ind = qml.math.argwhere(abs(c) > cutoff).flatten() - for i in nonzero_ind: - coeffs.append(c[i]) - ops.append(qml.pauli.string_to_pauli_word(o[i], wire_map=wiremap)) - - try: - coeffs = qml.math.stack(coeffs) - except ValueError: - pass - - return qml.Hamiltonian(qml.math.array(coeffs), ops) - - pauli_mult_dict = { "XX": "I", "YY": "I", diff --git a/pennylane/pulse/hardware_hamiltonian.py b/pennylane/pulse/hardware_hamiltonian.py index 8139d884a8b..154e8da54d6 100644 --- a/pennylane/pulse/hardware_hamiltonian.py +++ b/pennylane/pulse/hardware_hamiltonian.py @@ -355,9 +355,7 @@ def __add__(self, other): # pylint: disable=too-many-return-statements settings = self.settings pulses = self.pulses - if isinstance( - other, (qml.ops.Hamiltonian, qml.ops.LinearCombination, ParametrizedHamiltonian) - ): + if isinstance(other, (qml.ops.LinearCombination, ParametrizedHamiltonian)): new_coeffs = coeffs + list(other.coeffs.copy()) new_ops = ops + other.ops.copy() return HardwareHamiltonian( diff --git a/pennylane/pulse/parametrized_evolution.py b/pennylane/pulse/parametrized_evolution.py index fc335b65a80..240280c4304 100644 --- a/pennylane/pulse/parametrized_evolution.py +++ b/pennylane/pulse/parametrized_evolution.py @@ -381,7 +381,7 @@ def __init__( id=None, **odeint_kwargs, ): - if not all(op.has_matrix or isinstance(op, qml.ops.Hamiltonian) for op in H.ops): + if not all(op.has_matrix for op in H.ops): raise ValueError( "All operators inside the parametrized hamiltonian must have a matrix defined." ) diff --git a/pennylane/pulse/parametrized_hamiltonian.py b/pennylane/pulse/parametrized_hamiltonian.py index 88e31b567ad..a53bc9748a0 100644 --- a/pennylane/pulse/parametrized_hamiltonian.py +++ b/pennylane/pulse/parametrized_hamiltonian.py @@ -342,7 +342,7 @@ def __add__(self, H): ops = self.ops.copy() coeffs = self.coeffs.copy() - if isinstance(H, (qml.ops.Hamiltonian, qml.ops.LinearCombination, ParametrizedHamiltonian)): + if isinstance(H, (qml.ops.LinearCombination, ParametrizedHamiltonian)): # if Hamiltonian, coeffs array must be converted to list new_coeffs = coeffs + list(H.coeffs.copy()) new_ops = ops + H.ops.copy() @@ -367,7 +367,7 @@ def __radd__(self, H): ops = self.ops.copy() coeffs = self.coeffs.copy() - if isinstance(H, (qml.ops.Hamiltonian, qml.ops.LinearCombination, ParametrizedHamiltonian)): + if isinstance(H, (qml.ops.LinearCombination, ParametrizedHamiltonian)): # if Hamiltonian, coeffs array must be converted to list new_coeffs = list(H.coeffs.copy()) + coeffs new_ops = H.ops.copy() + ops diff --git a/pennylane/qaoa/layers.py b/pennylane/qaoa/layers.py index dde6a39a998..3df3b4b43ab 100644 --- a/pennylane/qaoa/layers.py +++ b/pennylane/qaoa/layers.py @@ -15,7 +15,6 @@ Methods that define cost and mixer layers for use in QAOA workflows. """ import pennylane as qml -from pennylane.operation import Tensor def _diagonal_terms(hamiltonian): @@ -30,9 +29,7 @@ def _diagonal_terms(hamiltonian): """ for op in hamiltonian.terms()[1]: - if isinstance(op, Tensor): - obs = op.obs - elif isinstance(op, qml.ops.Prod): + if isinstance(op, qml.ops.Prod): obs = op.operands else: obs = [op] diff --git a/pennylane/qaoa/mixers.py b/pennylane/qaoa/mixers.py index 76dffccaf29..fbe8d5f7484 100644 --- a/pennylane/qaoa/mixers.py +++ b/pennylane/qaoa/mixers.py @@ -231,11 +231,7 @@ def bit_flip_mixer(graph: Union[nx.Graph, rx.PyGraph], b: int): ] n_coeffs = [[1, sign] for _ in neighbours] - final_terms = ( - [qml.prod(*list(m)).simplify() for m in itertools.product(*n_terms)] - if qml.operation.active_new_opmath() - else [qml.operation.Tensor(*list(m)).prune() for m in itertools.product(*n_terms)] - ) + final_terms = [qml.prod(*list(m)).simplify() for m in itertools.product(*n_terms)] final_coeffs = [ (0.5**degree) * functools.reduce(lambda x, y: x * y, list(m), 1) diff --git a/pennylane/qchem/convert.py b/pennylane/qchem/convert.py index dbba83fe2be..9273f151caf 100644 --- a/pennylane/qchem/convert.py +++ b/pennylane/qchem/convert.py @@ -21,8 +21,6 @@ # pylint: disable= import-outside-toplevel,no-member,too-many-function-args import pennylane as qml -from pennylane.operation import Tensor, active_new_opmath -from pennylane.pauli import pauli_sentence from pennylane.wires import Wires @@ -146,9 +144,6 @@ def _openfermion_to_pennylane(qubit_operator, wires=None, tol=1.0e-16): 0.2 [Y0 Z2] >>> _openfermion_to_pennylane(q_op, wires=['w0','w1','w2','extra_wire']) (tensor([0.1, 0.2], requires_grad=False), [X('w0'), Y('w0') @ Z('w2')]) - - If the new op-math is active, the list of operators will be cast as :class:`~.Prod` instances instead of - :class:`~.Tensor` instances when appropriate. """ n_wires = ( 1 + max(max(i for i, _ in t) if t else 1 for t in qubit_operator.terms) @@ -165,10 +160,7 @@ def _openfermion_to_pennylane(qubit_operator, wires=None, tol=1.0e-16): def _get_op(term, wires): """A function to compute the PL operator associated with the term string.""" if len(term) > 1: - if active_new_opmath(): - return qml.prod(*[xyz2pauli[op[1]](wires=wires[op[0]]) for op in term]) - - return Tensor(*[xyz2pauli[op[1]](wires=wires[op[0]]) for op in term]) + return qml.prod(*[xyz2pauli[op[1]](wires=wires[op[0]]) for op in term]) if len(term) == 1: return xyz2pauli[term[0][1]](wires=wires[term[0][0]]) @@ -228,8 +220,8 @@ def _pennylane_to_openfermion(coeffs, ops, wires=None, tol=1.0e-16): >>> coeffs = np.array([0.1, 0.2, 0.3, 0.4]) >>> ops = [ - ... qml.operation.Tensor(qml.X('w0')), - ... qml.operation.Tensor(qml.Y('w0'), qml.Z('w2')), + ... qml.prod(qml.X('w0')), + ... qml.prod(qml.Y('w0'), qml.Z('w2')), ... qml.sum(qml.Z('w0'), qml.s_prod(-0.5, qml.X('w0'))), ... qml.prod(qml.X('w0'), qml.Z('w1')), ... ] @@ -264,16 +256,8 @@ def _pennylane_to_openfermion(coeffs, ops, wires=None, tol=1.0e-16): q_op = openfermion.QubitOperator() for coeff, op in zip(coeffs, ops): - if isinstance(op, Tensor): - try: - ps = pauli_sentence(op) - except ValueError as e: - raise ValueError( - f"Expected a Pennylane operator with a valid Pauli word representation, " - f"but got {op}." - ) from e - - elif (ps := op.pauli_rep) is None: + + if (ps := op.pauli_rep) is None: raise ValueError( f"Expected a Pennylane operator with a valid Pauli word representation, but got {op}." ) @@ -342,20 +326,9 @@ def import_operator(qubit_observable, format="openfermion", wires=None, tol=1e01 **Example** - >>> assert qml.operation.active_new_opmath() == True >>> h_pl = import_operator(h_of, format='openfermion') >>> print(h_pl) (-0.0548 * X(0 @ X(1) @ Y(2) @ Y(3))) + (0.14297 * Z(0 @ Z(1))) - - If the new op-math is deactivated, a :class:`~Hamiltonian` is returned instead. - - >>> assert qml.operation.active_new_opmath() == False - >>> from openfermion import QubitOperator - >>> h_of = QubitOperator('X0 X1 Y2 Y3', -0.0548) + QubitOperator('Z0 Z1', 0.14297) - >>> h_pl = import_operator(h_of, format='openfermion') - >>> print(h_pl) - (0.14297) [Z0 Z1] - + (-0.0548) [X0 X1 Y2 Y3] """ if format not in ["openfermion"]: raise TypeError(f"Converter does not exist for {format} format.") @@ -369,10 +342,7 @@ def import_operator(qubit_observable, format="openfermion", wires=None, tol=1e01 f" {list(coeffs[np.iscomplex(coeffs)])}" ) - if active_new_opmath(): - return qml.dot(*_openfermion_to_pennylane(qubit_observable, wires=wires)) - - return qml.Hamiltonian(*_openfermion_to_pennylane(qubit_observable, wires=wires)) + return qml.dot(*_openfermion_to_pennylane(qubit_observable, wires=wires)) def _excitations(electrons, orbitals): diff --git a/pennylane/qchem/hamiltonian.py b/pennylane/qchem/hamiltonian.py index 68cd5fd286d..b39eb24d8be 100644 --- a/pennylane/qchem/hamiltonian.py +++ b/pennylane/qchem/hamiltonian.py @@ -19,8 +19,6 @@ # pylint: disable= too-many-branches, too-many-arguments, too-many-locals, too-many-nested-blocks # pylint: disable=consider-using-generator, protected-access import pennylane as qml -from pennylane.operation import active_new_opmath -from pennylane.pauli.utils import simplify from .basis_data import atomic_numbers from .hartree_fock import nuclear_energy, scf @@ -301,7 +299,7 @@ def molecular_hamiltonian(*args, **kwargs): Returns: - tuple[pennylane.Hamiltonian, int]: the fermionic-to-qubit transformed Hamiltonian + tuple[pennylane.Operator, int]: the fermionic-to-qubit transformed Hamiltonian and the number of qubits .. note:: @@ -556,19 +554,11 @@ def _molecular_hamiltonian( else qml.qchem.diff_hamiltonian(mol, core=core, active=active, mapping=mapping)() ) - if active_new_opmath(): - h_as_ps = qml.pauli.pauli_sentence(h) - coeffs = qml.numpy.real(list(h_as_ps.values()), requires_grad=requires_grad) + h_as_ps = qml.pauli.pauli_sentence(h) + coeffs = qml.numpy.real(list(h_as_ps.values()), requires_grad=requires_grad) - h_as_ps = qml.pauli.PauliSentence(dict(zip(h_as_ps.keys(), coeffs))) - h = ( - qml.s_prod(0, qml.Identity(h.wires[0])) - if len(h_as_ps) == 0 - else h_as_ps.operation() - ) - else: - coeffs = qml.numpy.real(h.coeffs, requires_grad=requires_grad) - h = qml.Hamiltonian(coeffs, h.ops) + h_as_ps = qml.pauli.PauliSentence(dict(zip(h_as_ps.keys(), coeffs))) + h = qml.s_prod(0, qml.Identity(h.wires[0])) if len(h_as_ps) == 0 else h_as_ps.operation() if wires: h = qml.map_wires(h, wires_map) @@ -583,24 +573,14 @@ def _molecular_hamiltonian( mapping = mapping.strip().lower() qubits = len(hf.wires) - if active_new_opmath(): - if mapping == "jordan_wigner": - h_pl = qml.jordan_wigner(hf, wire_map=wires_map, tol=1.0e-10) - elif mapping == "parity": - h_pl = qml.parity_transform(hf, qubits, wire_map=wires_map, tol=1.0e-10) - elif mapping == "bravyi_kitaev": - h_pl = qml.bravyi_kitaev(hf, qubits, wire_map=wires_map, tol=1.0e-10) - - h_pl.simplify() - else: - if mapping == "jordan_wigner": - h_pl = qml.jordan_wigner(hf, ps=True, wire_map=wires_map, tol=1.0e-10) - elif mapping == "parity": - h_pl = qml.parity_transform(hf, qubits, ps=True, wire_map=wires_map, tol=1.0e-10) - elif mapping == "bravyi_kitaev": - h_pl = qml.bravyi_kitaev(hf, qubits, ps=True, wire_map=wires_map, tol=1.0e-10) - - h_pl = simplify(h_pl.hamiltonian()) + if mapping == "jordan_wigner": + h_pl = qml.jordan_wigner(hf, wire_map=wires_map, tol=1.0e-10) + elif mapping == "parity": + h_pl = qml.parity_transform(hf, qubits, wire_map=wires_map, tol=1.0e-10) + elif mapping == "bravyi_kitaev": + h_pl = qml.bravyi_kitaev(hf, qubits, wire_map=wires_map, tol=1.0e-10) + + h_pl = h_pl.simplify() return h_pl, len(h_pl.wires) diff --git a/pennylane/qchem/observable_hf.py b/pennylane/qchem/observable_hf.py index 276bfac9104..4ef1fe1f9a3 100644 --- a/pennylane/qchem/observable_hf.py +++ b/pennylane/qchem/observable_hf.py @@ -19,9 +19,7 @@ import pennylane as qml from pennylane.fermi import FermiSentence, FermiWord -from pennylane.operation import active_new_opmath from pennylane.pauli import PauliSentence -from pennylane.pauli.utils import simplify def fermionic_observable(constant, one=None, two=None, cutoff=1.0e-12): @@ -114,25 +112,6 @@ def qubit_observable(o_ferm, cutoff=1.0e-12, mapping="jordan_wigner"): >>> s = qml.fermi.FermiSentence({w1 : 1.2, w2: 3.1}) >>> print(qubit_observable(s)) -0.775j * (Y(0) @ X(1)) + 0.775 * (Y(0) @ Y(1)) + 0.775 * (X(0) @ X(1)) + 0.775j * (X(0) @ Y(1)) - - If the new op-math is deactivated, a legacy :class:`~pennylane.ops.Hamiltonian` instance is returned. - - >>> qml.operation.disable_new_opmath() - UserWarning: Disabling the new Operator arithmetic system for legacy support. - If you need help troubleshooting your code, please visit - https://docs.pennylane.ai/en/stable/news/new_opmath.html - >>> w1 = qml.fermi.FermiWord({(0, 0) : '+', (1, 1) : '-'}) - >>> w2 = qml.fermi.FermiWord({(0, 1) : '+', (1, 2) : '-'}) - >>> s = qml.fermi.FermiSentence({w1 : 1.2, w2: 3.1}) - >>> print(qubit_observable(s)) - (-0.3j) [Y0 X1] - + (0.3j) [X0 Y1] - + (-0.775j) [Y1 X2] - + (0.775j) [X1 Y2] - + ((0.3+0j)) [Y0 Y1] - + ((0.3+0j)) [X0 X1] - + ((0.775+0j)) [Y1 Y2] - + ((0.775+0j)) [X1 X2] """ if mapping == "jordan_wigner": h = qml.jordan_wigner(o_ferm, ps=True, tol=cutoff) @@ -150,19 +129,6 @@ def qubit_observable(o_ferm, cutoff=1.0e-12, mapping="jordan_wigner"): h.simplify(tol=cutoff) - if active_new_opmath(): - if not h.wires: - return h.operation(wire_order=[0]) - return h.operation() - if not h.wires: - h = h.hamiltonian(wire_order=[0]) - return qml.Hamiltonian( - h.coeffs, [qml.Identity(0) if o.name == "Identity" else o for o in h.ops] - ) - - h = h.hamiltonian() - - return simplify( - qml.Hamiltonian(h.coeffs, [qml.Identity(0) if o.name == "Identity" else o for o in h.ops]) - ) + return h.operation(wire_order=[0]) + return h.operation() diff --git a/pennylane/qchem/tapering.py b/pennylane/qchem/tapering.py index 4c7c9b8fd7c..3029de2b555 100644 --- a/pennylane/qchem/tapering.py +++ b/pennylane/qchem/tapering.py @@ -23,8 +23,7 @@ import scipy import pennylane as qml -from pennylane.operation import active_new_opmath, convert_to_opmath -from pennylane.pauli import PauliSentence, PauliWord, pauli_sentence, simplify +from pennylane.pauli import PauliSentence, PauliWord, pauli_sentence from pennylane.pauli.utils import _binary_matrix_from_pws from pennylane.wires import Wires @@ -170,7 +169,7 @@ def symmetry_generators(h): tau[idx] = pauli_map[f"{x}{z}"] ham = qml.pauli.PauliSentence({qml.pauli.PauliWord(tau): 1.0}) - ham = ham.operation(h.wires) if active_new_opmath() else ham.hamiltonian(h.wires) + ham = ham.operation(h.wires) generators.append(ham) return generators @@ -256,7 +255,7 @@ def clifford(generators, paulixops): u = functools.reduce(lambda p, q: p @ q, cliff) - return u.operation() if active_new_opmath() else u.hamiltonian() + return u.operation() def _split_pauli_sentence(pl_sentence, max_size=15000): @@ -320,9 +319,7 @@ def _taper_pauli_sentence(ps_h, generators, paulixops, paulix_sector): c = qml.math.stack(qml.math.multiply(val * complex(1.0), list(ts_ps.values()))) - tapered_ham = ( - qml.simplify(qml.dot(c, o)) if active_new_opmath() else simplify(qml.Hamiltonian(c, o)) - ) + tapered_ham = qml.simplify(qml.dot(c, o)) # If simplified Hamiltonian is missing wires, then add wires manually for consistency if set(wires_tap) != tapered_ham.wires.toset(): identity_op = functools.reduce( @@ -333,12 +330,8 @@ def _taper_pauli_sentence(ps_h, generators, paulixops, paulix_sector): ], ) - if active_new_opmath(): - return tapered_ham + (0.0 * identity_op) + return tapered_ham + (0.0 * identity_op) - tapered_ham = qml.Hamiltonian( - np.array([*tapered_ham.coeffs, 0.0]), [*tapered_ham.ops, identity_op] - ) return tapered_ham @@ -584,21 +577,16 @@ def _build_generator(operation, wire_order, op_gen=None): op_gen.pop(PauliWord({}), 0.0) else: # Single-parameter gates try: - # TODO: simplify when qml.generator has a proper support for "arithmetic". - op_gen = ( - operation.generator() - if active_new_opmath() - else qml.generator(operation, "arithmetic") - ).pauli_rep + op_gen = operation.generator().pauli_rep except (ValueError, qml.operation.GeneratorUndefinedError) as exc: raise NotImplementedError( f"Generator for {operation} is not implemented, please provide it with 'op_gen' args." ) from exc else: # check that user-provided generator is correct - if not isinstance( - op_gen, (qml.ops.Hamiltonian, qml.ops.LinearCombination, PauliSentence) - ) and not isinstance(getattr(op_gen, "pauli_rep", None), PauliSentence): + if not isinstance(op_gen, (qml.ops.LinearCombination, PauliSentence)) and not isinstance( + getattr(op_gen, "pauli_rep", None), PauliSentence + ): raise ValueError( f"Generator for the operation needs to be a valid operator, but got {type(op_gen)}." ) @@ -618,7 +606,7 @@ def _build_generator(operation, wire_order, op_gen=None): raise ValueError( f"Given op_gen: {op_gen} doesn't seem to be the correct generator for the {operation}." ) - op_gen = convert_to_opmath(op_gen).pauli_rep + op_gen = op_gen.pauli_rep return op_gen @@ -771,7 +759,7 @@ def _is_commuting(ps1, ps2): # Obtain the tapered generator for the operation with qml.QueuingManager.stop_recording(): # Get pauli rep for symmetery generators - ps_gen = list(map(lambda x: convert_to_opmath(x).pauli_rep, generators)) + ps_gen = list(map(lambda x: x.pauli_rep, generators)) gen_tapered = PauliSentence({}) if all(_is_commuting(sym, op_gen) for sym in ps_gen) and not qml.math.allclose( diff --git a/pennylane/qcut/cutcircuit.py b/pennylane/qcut/cutcircuit.py index 90f585e4bdb..2cf3d34ba1f 100644 --- a/pennylane/qcut/cutcircuit.py +++ b/pennylane/qcut/cutcircuit.py @@ -52,9 +52,7 @@ def processing_fn(res): # Expand the tapes for handling Hamiltonian with two or more terms tape_meas_ops = tape.measurements - if tape_meas_ops and isinstance( - tape_meas_ops[0].obs, (qml.ops.Hamiltonian, qml.ops.LinearCombination) - ): + if tape_meas_ops and isinstance(tape_meas_ops[0].obs, qml.ops.Sum): if len(tape_meas_ops) > 1: raise NotImplementedError( "Hamiltonian expansion is supported only with a single Hamiltonian" diff --git a/pennylane/qcut/tapes.py b/pennylane/qcut/tapes.py index 1bea5fd5aa6..bf856c70b45 100644 --- a/pennylane/qcut/tapes.py +++ b/pennylane/qcut/tapes.py @@ -25,7 +25,7 @@ import pennylane as qml from pennylane import expval from pennylane.measurements import ExpectationMP, MeasurementProcess, SampleMP -from pennylane.operation import Operator, Tensor +from pennylane.operation import Operator from pennylane.ops.meta import WireCut from pennylane.pauli import string_to_pauli_word from pennylane.queuing import WrappedObj @@ -81,7 +81,7 @@ def tape_to_graph(tape: QuantumScript) -> MultiDiGraph: order += 1 # pylint: disable=undefined-loop-variable for m in tape.measurements: obs = getattr(m, "obs", None) - if obs is not None and isinstance(obs, (Tensor, qml.ops.Prod)): + if obs is not None and isinstance(obs, qml.ops.Prod): if isinstance(m, SampleMP): raise ValueError( "Sampling from tensor products of observables " @@ -204,8 +204,7 @@ def graph_to_tape(graph: MultiDiGraph) -> QuantumScript: if measurement_type is ExpectationMP: if len(observables) > 1: - prod_type = qml.prod if qml.operation.active_new_opmath() else Tensor - measurements_from_graph.append(qml.expval(prod_type(*observables))) + measurements_from_graph.append(qml.expval(qml.prod(*observables))) else: measurements_from_graph.append(qml.expval(obs)) diff --git a/pennylane/shadows/classical_shadow.py b/pennylane/shadows/classical_shadow.py index c3556841b1e..4daee81f6a7 100644 --- a/pennylane/shadows/classical_shadow.py +++ b/pennylane/shadows/classical_shadow.py @@ -266,18 +266,6 @@ def pauli_list_to_word(obs): word = pauli_list_to_word([observable]) return [(1, word)] - if isinstance(observable, qml.operation.Tensor): - word = pauli_list_to_word(observable.obs) - return [(1, word)] - - if isinstance(observable, qml.ops.Hamiltonian): - coeffs_and_words = [] - for coeff, op in zip(observable.data, observable.ops): - coeffs_and_words.extend( - [(coeff * c, w) for c, w in self._convert_to_pauli_words(op)] - ) - return coeffs_and_words - # Support for all operators with a valid pauli_rep if (pr := observable.pauli_rep) is not None: return self._convert_to_pauli_words_with_pauli_rep(pr, num_wires) diff --git a/pennylane/tape/tape.py b/pennylane/tape/tape.py index 3c3e21caabb..6dff9d64479 100644 --- a/pennylane/tape/tape.py +++ b/pennylane/tape/tape.py @@ -78,9 +78,10 @@ def _validate_computational_basis_sampling(tape): with ( QueuingManager.stop_recording() ): # stop recording operations - the constructed operator is just aux - prod_op = qml.ops.Prod if qml.operation.active_new_opmath() else qml.operation.Tensor pauliz_for_cb_obs = ( - qml.Z(all_wires) if len(all_wires) == 1 else prod_op(*[qml.Z(w) for w in all_wires]) + qml.Z(all_wires) + if len(all_wires) == 1 + else qml.ops.Prod(*[qml.Z(w) for w in all_wires]) ) for obs in non_comp_basis_sampling_obs: diff --git a/pennylane/templates/subroutines/qdrift.py b/pennylane/templates/subroutines/qdrift.py index a65152586e0..cf5132c2440 100644 --- a/pennylane/templates/subroutines/qdrift.py +++ b/pennylane/templates/subroutines/qdrift.py @@ -17,12 +17,12 @@ import pennylane as qml from pennylane.math import requires_grad, unwrap from pennylane.operation import Operation -from pennylane.ops import Hamiltonian, LinearCombination, Sum +from pennylane.ops import LinearCombination, Sum from pennylane.wires import Wires def _check_hamiltonian_type(hamiltonian): - if not isinstance(hamiltonian, (Hamiltonian, LinearCombination, Sum)): + if not isinstance(hamiltonian, Sum): raise TypeError( f"The given operator must be a PennyLane ~.Hamiltonian or ~.Sum, got {hamiltonian}" ) @@ -30,9 +30,9 @@ def _check_hamiltonian_type(hamiltonian): def _extract_hamiltonian_coeffs_and_ops(hamiltonian): """Extract the coefficients and operators from a Hamiltonian that is - a ``Hamiltonian``, a ``LinearCombination`` or a ``Sum``.""" + a ``LinearCombination`` or a ``Sum``.""" # Note that potentially_trainable_coeffs does *not* contain all coeffs - if isinstance(hamiltonian, (Hamiltonian, LinearCombination)): + if isinstance(hamiltonian, LinearCombination): coeffs, ops = hamiltonian.terms() elif isinstance(hamiltonian, Sum): @@ -279,7 +279,7 @@ def error(hamiltonian, time, n=1): terms to be added to the product. For more details see `Phys. Rev. Lett. 123, 070503 (2019) `_. Args: - hamiltonian (Union[.Hamiltonian, .Sum]): The Hamiltonian written as a sum of operations + hamiltonian (Sum): The Hamiltonian written as a sum of operations time (float): The time of evolution, namely the parameter :math:`t` in :math:`e^{-iHt}` n (int): An integer representing the number of exponentiated terms. default is 1 diff --git a/pennylane/templates/subroutines/trotter.py b/pennylane/templates/subroutines/trotter.py index 87dfac458d6..2429d43c8bd 100644 --- a/pennylane/templates/subroutines/trotter.py +++ b/pennylane/templates/subroutines/trotter.py @@ -201,7 +201,7 @@ def __init__( # pylint: disable=too-many-arguments f"The order of a TrotterProduct must be 1 or a positive even integer, got {order}." ) - if isinstance(hamiltonian, (qml.ops.Hamiltonian, qml.ops.LinearCombination)): + if isinstance(hamiltonian, qml.ops.LinearCombination): coeffs, ops = hamiltonian.terms() if len(coeffs) < 2: raise ValueError( diff --git a/pennylane/transforms/diagonalize_measurements.py b/pennylane/transforms/diagonalize_measurements.py index c7b8ec4b666..ba424efb4da 100644 --- a/pennylane/transforms/diagonalize_measurements.py +++ b/pennylane/transforms/diagonalize_measurements.py @@ -17,7 +17,6 @@ from functools import singledispatch import pennylane as qml -from pennylane.operation import Tensor from pennylane.ops import CompositeOp, LinearCombination, SymbolicOp from pennylane.pauli import diagonalize_qwc_pauli_words from pennylane.tape.tape import ( @@ -294,28 +293,6 @@ def _change_symbolic_op(observable: SymbolicOp): return diagonalizing_gates, new_observable -@_change_obs_to_Z.register -def _change_tensor(observable: Tensor): - diagonalizing_gates, new_obs = diagonalize_qwc_pauli_words( - observable.obs, - ) - - new_observable = Tensor(*new_obs) - - return diagonalizing_gates, new_observable - - -@_change_obs_to_Z.register -def _change_hamiltonian(observable: qml.ops.Hamiltonian): - diagonalizing_gates, new_ops = diagonalize_qwc_pauli_words( - observable.ops, - ) - - new_observable = qml.ops.Hamiltonian(observable.coeffs, new_ops) - - return diagonalizing_gates, new_observable - - @_change_obs_to_Z.register def _change_linear_combination(observable: LinearCombination): coeffs, obs = observable.terms() @@ -471,34 +448,6 @@ def _diagonalize_symbolic_op( return diagonalizing_gates, new_observable, _visited_obs -@_diagonalize_compound_observable.register -def _diagonalize_tensor( - observable: Tensor, _visited_obs, supported_base_obs=_default_supported_obs -): - diagonalizing_gates, new_obs, _visited_obs = _get_obs_and_gates( - observable.obs, _visited_obs, supported_base_obs - ) - - new_observable = Tensor(*new_obs) - - return diagonalizing_gates, new_observable, _visited_obs - - -@_diagonalize_compound_observable.register -def _diagonalize_hamiltonian( - observable: qml.ops.Hamiltonian, - _visited_obs, - supported_base_obs=_default_supported_obs, -): - diagonalizing_gates, new_ops, _visited_obs = _get_obs_and_gates( - observable.ops, _visited_obs, supported_base_obs - ) - - new_observable = qml.ops.Hamiltonian(observable.coeffs, new_ops) - - return diagonalizing_gates, new_observable, _visited_obs - - @_diagonalize_compound_observable.register def _diagonalize_linear_combination( observable: LinearCombination, _visited_obs, supported_base_obs=_default_supported_obs diff --git a/pennylane/transforms/sign_expand/sign_expand.py b/pennylane/transforms/sign_expand/sign_expand.py index aaad22cb63e..fe5e3868a4f 100644 --- a/pennylane/transforms/sign_expand/sign_expand.py +++ b/pennylane/transforms/sign_expand/sign_expand.py @@ -311,7 +311,7 @@ def circuit(): wires = hamiltonian.wires if ( - not isinstance(hamiltonian, (qml.ops.Hamiltonian, qml.ops.LinearCombination)) + not isinstance(hamiltonian, qml.ops.LinearCombination) or len(tape.measurements) > 1 or tape.measurements[0].return_type not in [qml.measurements.Expectation, qml.measurements.Variance] diff --git a/pennylane/transforms/split_non_commuting.py b/pennylane/transforms/split_non_commuting.py index 3f2775bba18..c940e40cc9d 100644 --- a/pennylane/transforms/split_non_commuting.py +++ b/pennylane/transforms/split_non_commuting.py @@ -23,7 +23,7 @@ import pennylane as qml from pennylane.measurements import ExpectationMP, MeasurementProcess, Shots, StateMP -from pennylane.ops import Hamiltonian, LinearCombination, Prod, SProd, Sum +from pennylane.ops import LinearCombination, Prod, SProd, Sum from pennylane.tape import QuantumScript, QuantumScriptBatch from pennylane.transforms import transform from pennylane.typing import PostprocessingFn, Result, ResultBatch, TensorLike, Union @@ -259,12 +259,12 @@ def circuit(x): if len(tape.measurements) == 0: return [tape], null_postprocessing - # Special case for a single measurement of a Sum or Hamiltonian, in which case + # Special case for a single measurement of a Sum, in which case # the grouping information can be computed and cached in the observable. if ( len(tape.measurements) == 1 and isinstance(tape.measurements[0], ExpectationMP) - and isinstance(tape.measurements[0].obs, (Hamiltonian, Sum)) + and isinstance(tape.measurements[0].obs, Sum) and ( ( grouping_strategy in ("default", "qwc") @@ -292,7 +292,7 @@ def circuit(x): grouping_strategy == "wires" or grouping_strategy == "default" and any( - isinstance(m, ExpectationMP) and isinstance(m.obs, (LinearCombination, Hamiltonian)) + isinstance(m, ExpectationMP) and isinstance(m.obs, LinearCombination) for m in tape.measurements ) or any( @@ -306,7 +306,7 @@ def circuit(x): # compute, but inefficient quantum-wise. If this transform is to be added to a device's # `preprocess`, it will be performed for every circuit execution, which can get very # expensive if there is a large number of observables. The reasoning here is, large - # Hamiltonians typically come in the form of a `LinearCombination` or `Hamiltonian`, so + # Hamiltonians typically come in the form of a `LinearCombination`, so # if we see one of those, use wires grouping to be safe. Otherwise, use qwc grouping. return _split_using_wires_grouping(tape, single_term_obs_mps, offsets) @@ -314,7 +314,7 @@ def circuit(x): def _split_ham_with_grouping(tape: qml.tape.QuantumScript): - """Splits a tape measuring a single Hamiltonian or Sum and group commuting observables.""" + """Splits a tape measuring a single Sum and group commuting observables.""" obs = tape.measurements[0].obs if obs.grouping_indices is None: @@ -322,7 +322,7 @@ def _split_ham_with_grouping(tape: qml.tape.QuantumScript): coeffs, obs_list = obs.terms() - # The constant offset of the Hamiltonian, typically arising from Identity terms. + # The constant offset of the Sum, typically arising from Identity terms. offset = 0 # A dictionary for measurements of each unique single-term observable, mapped to the @@ -348,7 +348,7 @@ def _split_ham_with_grouping(tape: qml.tape.QuantumScript): else: new_mp = qml.expval(obs_list[obs_idx]) if new_mp in single_term_obs_mps: - # If the Hamiltonian contains duplicate observables, it can be reused, + # If the Sum contains duplicate observables, it can be reused, # and the coefficients for each duplicate should be combined. single_term_obs_mps[new_mp] = ( single_term_obs_mps[new_mp][0], @@ -542,7 +542,7 @@ def _split_all_multi_term_obs_mps(tape: qml.tape.QuantumScript): for mp_idx, mp in enumerate(tape.measurements): obs = mp.obs offset = 0 - if isinstance(mp, ExpectationMP) and isinstance(obs, (Hamiltonian, Sum, Prod, SProd)): + if isinstance(mp, ExpectationMP) and isinstance(obs, (Sum, Prod, SProd)): # Break the observable into terms, and construct an ExpectationMP with each term. for c, o in zip(*obs.terms()): # If the observable is an identity, track it with a constant offset @@ -561,7 +561,7 @@ def _split_all_multi_term_obs_mps(tape: qml.tape.QuantumScript): else: if isinstance(obs, SProd): obs = obs.simplify() - if isinstance(obs, (Hamiltonian, Sum)): + if isinstance(obs, Sum): raise RuntimeError( f"Cannot split up terms in sums for MeasurementProcess {type(mp)}" ) diff --git a/pennylane/transforms/transpile.py b/pennylane/transforms/transpile.py index 4a50a283793..edc50779004 100644 --- a/pennylane/transforms/transpile.py +++ b/pennylane/transforms/transpile.py @@ -7,8 +7,7 @@ import networkx as nx import pennylane as qml -from pennylane.operation import Tensor -from pennylane.ops import Hamiltonian, LinearCombination +from pennylane.ops import LinearCombination from pennylane.ops import __all__ as all_ops from pennylane.ops.qubit import SWAP from pennylane.queuing import QueuingManager @@ -143,12 +142,9 @@ def circuit(): f"Not all wires present in coupling map! wires: {wires}, coupling map: {coupling_graph.nodes}" ) - if any( - isinstance(m.obs, (Hamiltonian, LinearCombination, Tensor, qml.ops.Prod)) - for m in tape.measurements - ): + if any(isinstance(m.obs, (LinearCombination, qml.ops.Prod)) for m in tape.measurements): raise NotImplementedError( - "Measuring expectation values of tensor products, Prods, or Hamiltonians is not yet supported" + "Measuring expectation values of tensor products or Hamiltonians is not yet supported" ) if any(len(op.wires) > 2 for op in tape.operations): diff --git a/tests/capture/test_operators.py b/tests/capture/test_operators.py index ca2f98adab4..530c43a289f 100644 --- a/tests/capture/test_operators.py +++ b/tests/capture/test_operators.py @@ -53,7 +53,6 @@ def test_abstract_operator(): # arithmetic dunders integration tested -@pytest.mark.usefixtures("new_opmath_only") def test_operators_constructed_when_plxpr_enabled(): """Test that normal operators can still be constructed when plxpr is enabled.""" @@ -422,7 +421,6 @@ def qfunc(): assert isinstance(eqn.outvars[0].aval, AbstractOperator) - @pytest.mark.usefixtures("new_opmath_only") def test_mul(self): """Test that the scalar multiplication dunder works.""" diff --git a/tests/capture/test_templates.py b/tests/capture/test_templates.py index 3990b2821e4..784a6f248b4 100644 --- a/tests/capture/test_templates.py +++ b/tests/capture/test_templates.py @@ -623,7 +623,6 @@ def qfunc(probs): assert len(q) == 1 assert q.queue[0] == qml.QuantumMonteCarlo(probs, **kwargs) - @pytest.mark.usefixtures("new_opmath_only") def test_qubitization(self): """Test the primitive bind call of Qubitization.""" @@ -654,7 +653,6 @@ def qfunc(): assert len(q) == 1 qml.assert_equal(q.queue[0], qml.Qubitization(**kwargs)) - @pytest.mark.usefixtures("new_opmath_only") def test_qrom(self): """Test the primitive bind call of QROM.""" @@ -689,7 +687,6 @@ def qfunc(): assert len(q) == 1 qml.assert_equal(q.queue[0], qml.QROM(**kwargs)) - @pytest.mark.usefixtures("new_opmath_only") def test_phase_adder(self): """Test the primitive bind call of PhaseAdder.""" @@ -724,7 +721,6 @@ def qfunc(): assert len(q) == 1 qml.assert_equal(q.queue[0], qml.PhaseAdder(**kwargs)) - @pytest.mark.usefixtures("new_opmath_only") def test_adder(self): """Test the primitive bind call of Adder.""" @@ -759,7 +755,6 @@ def qfunc(): assert len(q) == 1 qml.assert_equal(q.queue[0], qml.Adder(**kwargs)) - @pytest.mark.usefixtures("new_opmath_only") def test_multiplier(self): """Test the primitive bind call of Multiplier.""" @@ -794,7 +789,6 @@ def qfunc(): assert len(q) == 1 qml.assert_equal(q.queue[0], qml.Multiplier(**kwargs)) - @pytest.mark.usefixtures("new_opmath_only") def test_out_multiplier(self): """Test the primitive bind call of OutMultiplier.""" @@ -830,7 +824,6 @@ def qfunc(): assert len(q) == 1 qml.assert_equal(q.queue[0], qml.OutMultiplier(**kwargs)) - @pytest.mark.usefixtures("new_opmath_only") def test_out_adder(self): """Test the primitive bind call of OutAdder.""" @@ -866,7 +859,6 @@ def qfunc(): assert len(q) == 1 qml.assert_equal(q.queue[0], qml.OutAdder(**kwargs)) - @pytest.mark.usefixtures("new_opmath_only") def test_mod_exp(self): """Test the primitive bind call of ModExp.""" diff --git a/tests/circuit_graph/test_circuit_graph_hash.py b/tests/circuit_graph/test_circuit_graph_hash.py index 6fb966c25ad..9622b81af97 100644 --- a/tests/circuit_graph/test_circuit_graph_hash.py +++ b/tests/circuit_graph/test_circuit_graph_hash.py @@ -19,7 +19,6 @@ import pennylane as qml from pennylane.circuit_graph import CircuitGraph -from pennylane.operation import Tensor from pennylane.wires import Wires @@ -53,7 +52,7 @@ def test_serialize_numeric_arguments(self, queue, observable_queue, expected_str observable1 = qml.PauliZ(wires=[0]) observable2 = qml.Hermitian(np.array([[1, 0], [0, -1]]), wires=[0]) - observable3 = Tensor(qml.PauliZ(0), qml.PauliZ(1)) + observable3 = qml.prod(qml.PauliZ(0), qml.PauliZ(1)) numeric_observable_queue = [ (returntype1, observable1, "|||ObservableReturnTypes.Expectation!PauliZ[0]"), @@ -65,7 +64,7 @@ def test_serialize_numeric_arguments(self, queue, observable_queue, expected_str ( returntype1, observable3, - "|||ObservableReturnTypes.Expectation!['PauliZ', 'PauliZ'][0, 1]", + "|||ObservableReturnTypes.Expectation!Prod[0, 1]", ), (returntype2, observable1, "|||ObservableReturnTypes.Variance!PauliZ[0]"), ( @@ -76,7 +75,7 @@ def test_serialize_numeric_arguments(self, queue, observable_queue, expected_str ( returntype2, observable3, - "|||ObservableReturnTypes.Variance!['PauliZ', 'PauliZ'][0, 1]", + "|||ObservableReturnTypes.Variance!Prod[0, 1]", ), ] diff --git a/tests/conftest.py b/tests/conftest.py index 82e1f843169..a01254df37b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -18,14 +18,12 @@ import os import pathlib import sys -from warnings import filterwarnings, warn import numpy as np import pytest import pennylane as qml from pennylane.devices import DefaultGaussian -from pennylane.operation import disable_new_opmath_cm, enable_new_opmath_cm sys.path.append(os.path.join(os.path.dirname(__file__), "helpers")) @@ -127,60 +125,6 @@ def tear_down_thermitian(): qml.THermitian._eigs = {} -####################################################################### -# Fixtures for testing under new and old opmath - - -def pytest_addoption(parser): - parser.addoption( - "--disable-opmath", action="store", default="False", help="Whether to disable new_opmath" - ) - - -# pylint: disable=eval-used -@pytest.fixture(scope="session", autouse=True) -def disable_opmath_if_requested(request): - disable_opmath = request.config.getoption("--disable-opmath") - # value from yaml file is a string, convert to boolean - if eval(disable_opmath): - warn( - "Disabling the new Operator arithmetic system for legacy support. " - "If you need help troubleshooting your code, please visit " - "https://docs.pennylane.ai/en/stable/news/new_opmath.html", - UserWarning, - ) - qml.operation.disable_new_opmath(warn=False) - - # Suppressing warnings so that Hamiltonians and Tensors constructed outside tests - # don't raise deprecation warnings - filterwarnings("ignore", "qml.ops.Hamiltonian", qml.PennyLaneDeprecationWarning) - filterwarnings("ignore", "qml.operation.Tensor", qml.PennyLaneDeprecationWarning) - filterwarnings("ignore", "qml.pauli.simplify", qml.PennyLaneDeprecationWarning) - filterwarnings("ignore", "PauliSentence.hamiltonian", qml.PennyLaneDeprecationWarning) - filterwarnings("ignore", "PauliWord.hamiltonian", qml.PennyLaneDeprecationWarning) - - -@pytest.fixture(params=[disable_new_opmath_cm, enable_new_opmath_cm], scope="function") -def use_legacy_and_new_opmath(request): - with request.param(warn=False) as cm: - yield cm - - -@pytest.fixture -def new_opmath_only(): - if not qml.operation.active_new_opmath(): - pytest.skip("This feature only works with new opmath enabled") - - -@pytest.fixture -def legacy_opmath_only(): - if qml.operation.active_new_opmath(): - pytest.skip("This test exclusively tests legacy opmath") - - -####################################################################### - - @pytest.fixture(autouse=True) def restore_global_seed(): original_state = np.random.get_state() diff --git a/tests/data/attributes/operator/test_operator.py b/tests/data/attributes/operator/test_operator.py index a489134916d..29b28363dc5 100644 --- a/tests/data/attributes/operator/test_operator.py +++ b/tests/data/attributes/operator/test_operator.py @@ -24,7 +24,7 @@ import pennylane as qml from pennylane.data.attributes import DatasetOperator, DatasetPyTree from pennylane.data.base.typing_util import get_type_str -from pennylane.operation import Operator, Tensor +from pennylane.operation import Operator pytestmark = pytest.mark.data @@ -71,21 +71,18 @@ ] ] -tensors = [Tensor(qml.PauliX(1), qml.PauliY(2))] +tensors = [qml.prod(qml.PauliX(1), qml.PauliY(2))] -@pytest.mark.usefixtures("use_legacy_and_new_opmath") @pytest.mark.parametrize("attribute_cls", [DatasetOperator, DatasetPyTree]) @pytest.mark.parametrize("obs_in", [*hermitian_ops, *pauli_ops, *identity, *hamiltonians, *tensors]) class TestDatasetOperatorObservable: - """Tests serializing Observable operators using the ``compare()`` method.""" + """Tests serializing Observable operators using the ``qml.equal`` function.""" - def test_value_init(self, attribute_cls, obs_in, recwarn): + def test_value_init(self, attribute_cls, obs_in): """Test that a DatasetOperator can be value-initialized from an observable, and that the deserialized operator is equivalent.""" - if not qml.operation.active_new_opmath() and isinstance(obs_in, qml.ops.LinearCombination): - obs_in = qml.operation.convert_to_legacy_H(obs_in) dset_op = attribute_cls(obs_in) @@ -93,26 +90,11 @@ def test_value_init(self, attribute_cls, obs_in, recwarn): assert dset_op.info["py_type"] == get_type_str(type(obs_in)) obs_out = dset_op.get_value() - if ( - qml.operation.active_new_opmath() - and isinstance(obs_in, Tensor) - and attribute_cls is DatasetOperator - ): - assert isinstance(obs_out, qml.ops.Prod) - for o1, o2 in zip(obs_in.obs, obs_out.operands): - qml.assert_equal(o1, o2) - - # No Tensor deprecation warnings are raised - assert len(recwarn) == 0 - else: - qml.assert_equal(obs_out, obs_in) - assert obs_in.compare(obs_out) - - def test_bind_init(self, attribute_cls, obs_in, recwarn): + qml.assert_equal(obs_out, obs_in) + + def test_bind_init(self, attribute_cls, obs_in): """Test that DatasetOperator can be initialized from a HDF5 group that contains an operator attribute.""" - if not qml.operation.active_new_opmath() and isinstance(obs_in, qml.ops.LinearCombination): - obs_in = qml.operation.convert_to_legacy_H(obs_in) bind = attribute_cls(obs_in).bind @@ -122,20 +104,7 @@ def test_bind_init(self, attribute_cls, obs_in, recwarn): assert dset_op.info["py_type"] == get_type_str(type(obs_in)) obs_out = dset_op.get_value() - if ( - qml.operation.active_new_opmath() - and isinstance(obs_in, Tensor) - and attribute_cls is DatasetOperator - ): - assert isinstance(obs_out, qml.ops.Prod) - for o1, o2 in zip(obs_in.obs, obs_out.operands): - qml.assert_equal(o1, o2) - - # No Tensor deprecation warnings are raised - assert len(recwarn) == 0 - else: - qml.assert_equal(obs_out, obs_in) - assert obs_in.compare(obs_out) + qml.assert_equal(obs_out, obs_in) @pytest.mark.parametrize("attribute_cls", [DatasetOperator, DatasetPyTree]) @@ -156,8 +125,6 @@ def test_value_init(self, attribute_cls, obs_in): """Test that a DatasetOperator can be value-initialized from an observable, and that the deserialized operator is equivalent.""" - if not qml.operation.active_new_opmath() and isinstance(obs_in, qml.ops.LinearCombination): - obs_in = qml.operation.convert_to_legacy_H(obs_in) dset_op = attribute_cls(obs_in) @@ -170,8 +137,6 @@ def test_value_init(self, attribute_cls, obs_in): def test_bind_init(self, attribute_cls, obs_in): """Test that DatasetOperator can be initialized from a HDF5 group that contains an operator attribute.""" - if not qml.operation.active_new_opmath() and isinstance(obs_in, qml.ops.LinearCombination): - obs_in = qml.operation.convert_to_legacy_H(obs_in) bind = attribute_cls(obs_in).bind @@ -200,9 +165,6 @@ def test_value_init(self, attribute_cls, op_in): from an operator, and that the deserialized operator is equivalent.""" - if not qml.operation.active_new_opmath() and isinstance(op_in, qml.ops.LinearCombination): - op_in = qml.operation.convert_to_legacy_H(op_in) - dset_op = attribute_cls(op_in) assert dset_op.info["type_id"] == attribute_cls.type_id @@ -227,9 +189,6 @@ def test_bind_init(self, attribute_cls, op_in): from an operator, and that the deserialized operator is equivalent.""" - if not qml.operation.active_new_opmath() and isinstance(op_in, qml.ops.LinearCombination): - op_in = qml.operation.convert_to_legacy_H(op_in) - bind = attribute_cls(op_in).bind dset_op = attribute_cls(bind=bind) diff --git a/tests/default_qubit_legacy.py b/tests/default_qubit_legacy.py index 183f56c3df5..d6485a02f5b 100644 --- a/tests/default_qubit_legacy.py +++ b/tests/default_qubit_legacy.py @@ -23,7 +23,7 @@ from string import ascii_letters as ABC import numpy as np -from scipy.sparse import csr_matrix +from scipy.sparse import coo_matrix, csr_matrix import pennylane as qml from pennylane import BasisState, Snapshot, StatePrep @@ -603,7 +603,7 @@ def expval(self, observable, shot_range=None, bin_size=None): # intercept other Hamiltonians # TODO: Ideally, this logic should not live in the Device, but be moved # to a component that can be re-used by devices as needed. - if observable.name not in ("Hamiltonian", "SparseHamiltonian", "LinearCombination"): + if observable.name not in ("SparseHamiltonian", "LinearCombination"): return super().expval(observable, shot_range=shot_range, bin_size=bin_size) assert self.shots is None, f"{observable.name} must be used with shots=None" @@ -612,7 +612,7 @@ def expval(self, observable, shot_range=None, bin_size=None): backprop_mode = ( not isinstance(self.state, np.ndarray) or any(not isinstance(d, (float, np.ndarray)) for d in observable.data) - ) and observable.name in ["Hamiltonian", "LinearCombination"] + ) and observable.name == "LinearCombination" if backprop_mode: # TODO[dwierichs]: This branch is not adapted to broadcasting yet @@ -635,7 +635,8 @@ def expval(self, observable, shot_range=None, bin_size=None): # that the user provided. for op, coeff in zip(observable.ops, observable.data): # extract a scipy.sparse.coo_matrix representation of this Pauli word - coo = qml.operation.Tensor(op).sparse_matrix(wire_order=self.wires, format="coo") + sparse_mat = qml.prod(op).sparse_matrix(wire_order=self.wires) + coo = coo_matrix(sparse_mat) Hmat = qml.math.cast(qml.math.convert_like(coo.data, self.state), self.C_DTYPE) product = ( @@ -1087,7 +1088,6 @@ def _get_diagonalizing_gates(self, circuit: qml.tape.QuantumScript) -> list[Oper meas_filtered = [ m for m in circuit.measurements - if m.obs is None - or not isinstance(m.obs, (qml.ops.Hamiltonian, qml.ops.LinearCombination)) + if m.obs is None or not isinstance(m.obs, qml.ops.LinearCombination) ] return super()._get_diagonalizing_gates(qml.tape.QuantumScript(measurements=meas_filtered)) diff --git a/tests/devices/default_qubit/test_default_qubit.py b/tests/devices/default_qubit/test_default_qubit.py index f41bbdafe12..2f9a53eca92 100644 --- a/tests/devices/default_qubit/test_default_qubit.py +++ b/tests/devices/default_qubit/test_default_qubit.py @@ -821,9 +821,7 @@ def f(dev, scale, n_wires=10, offset=0.1, style="sum"): t1 = 2.5 * qml.prod(*(qml.PauliZ(i) for i in range(n_wires))) t2 = 6.2 * qml.prod(*(qml.PauliY(i) for i in range(n_wires))) H = t1 + t2 - if style == "hamiltonian": - H = H.pauli_rep.hamiltonian() - elif style == "hermitian": + if style == "hermitian": H = qml.Hermitian(H.matrix(), wires=H.wires) qs = qml.tape.QuantumScript(ops, [qml.expval(H)]) return dev.execute(qs) @@ -836,12 +834,9 @@ def f_hashable(scale, n_wires=10, offset=0.1, style="sum"): t1 = 2.5 * qml.prod(*(qml.PauliZ(i) for i in range(n_wires))) t2 = 6.2 * qml.prod(*(qml.PauliY(i) for i in range(n_wires))) H = t1 + t2 - if style == "hamiltonian": - H = H.pauli_rep.hamiltonian() - elif style == "hermitian": + if style == "hermitian": H = qml.Hermitian(H.matrix(), wires=H.wires) qs = qml.tape.QuantumScript(ops, [qml.expval(H)]) - qs = qml.tape.QuantumScript(ops, [qml.expval(H)]) return DefaultQubit().execute(qs) @staticmethod @@ -853,7 +848,7 @@ def expected(scale, n_wires=10, offset=0.1, like="numpy"): return 2.5 * qml.math.prod(cosines) + 6.2 * qml.math.prod(sines) @pytest.mark.autograd - @pytest.mark.parametrize("style", ("sum", "hamiltonian", "hermitian")) + @pytest.mark.parametrize("style", ("sum", "hermitian")) def test_autograd_backprop(self, style): """Test that backpropagation derivatives work in autograd with hamiltonians and large sums.""" dev = DefaultQubit() @@ -868,7 +863,7 @@ def test_autograd_backprop(self, style): @pytest.mark.jax @pytest.mark.parametrize("use_jit", (True, False)) - @pytest.mark.parametrize("style", ("sum", "hamiltonian", "hermitian")) + @pytest.mark.parametrize("style", ("sum", "hermitian")) def test_jax_backprop(self, style, use_jit): """Test that backpropagation derivatives work with jax with hamiltonians and large sums.""" import jax @@ -885,7 +880,7 @@ def test_jax_backprop(self, style, use_jit): assert qml.math.allclose(g, expected_g) @pytest.mark.torch - @pytest.mark.parametrize("style", ("sum", "hamiltonian", "hermitian")) + @pytest.mark.parametrize("style", ("sum", "hermitian")) def test_torch_backprop(self, style): """Test that backpropagation derivatives work with torch with hamiltonians and large sums.""" import torch @@ -903,7 +898,7 @@ def test_torch_backprop(self, style): assert qml.math.allclose(x.grad, x2.grad) @pytest.mark.tf - @pytest.mark.parametrize("style", ("sum", "hamiltonian", "hermitian")) + @pytest.mark.parametrize("style", ("sum", "hermitian")) def test_tf_backprop(self, style): """Test that backpropagation derivatives work with tensorflow with hamiltonians and large sums.""" import tensorflow as tf @@ -2162,9 +2157,6 @@ def test_differentiate_jitted_qnode(self, measurement_func): """Test that a jitted qnode can be correctly differentiated""" import jax - if measurement_func is qml.var and not qml.operation.active_new_opmath(): - pytest.skip(reason="Variance for this test circuit not supported with legacy opmath") - dev = DefaultQubit() def qfunc(x, y): diff --git a/tests/devices/default_qubit/test_default_qubit_native_mcm.py b/tests/devices/default_qubit/test_default_qubit_native_mcm.py index eed50f29e0f..ae764a0b025 100644 --- a/tests/devices/default_qubit/test_default_qubit_native_mcm.py +++ b/tests/devices/default_qubit/test_default_qubit_native_mcm.py @@ -136,12 +136,6 @@ def test_multiple_measurements_and_reset(mcm_method, shots, params, postselect, and a conditional gate. Multiple measurements of the mid-circuit measurement value are performed. This function also tests `reset` parametrizing over the parameter.""" - if mcm_method == "tree-traversal" and not qml.operation.active_new_opmath(): - pytest.xfail( - "The tree-traversal method does not work with legacy opmath with " - "`qml.var` of pauli observables in the circuit." - ) - if mcm_method == "one-shot" and shots is None: pytest.skip("`mcm_method='one-shot'` is incompatible with analytic mode (`shots=None`)") diff --git a/tests/devices/default_qubit/test_default_qubit_preprocessing.py b/tests/devices/default_qubit/test_default_qubit_preprocessing.py index 11a4bf001c2..fec4593bfee 100644 --- a/tests/devices/default_qubit/test_default_qubit_preprocessing.py +++ b/tests/devices/default_qubit/test_default_qubit_preprocessing.py @@ -575,32 +575,6 @@ def test_preprocess_check_validity_fail(self): with pytest.raises(qml.DeviceError, match="Operator NoMatNoDecompOp"): program(tapes) - @pytest.mark.usefixtures("legacy_opmath_only") - @pytest.mark.parametrize( - "ops, measurement, message", - [ - ( - [qml.RX(0.1, wires=0)], - [qml.probs(op=qml.PauliX(0))], - "adjoint diff supports either all expectation values or", - ), - ( - [qml.RX(0.1, wires=0)], - [qml.expval(qml.ops.Hamiltonian([1], [qml.PauliZ(0)]))], - "not supported on adjoint", - ), - ], - ) - @pytest.mark.filterwarnings("ignore:Differentiating with respect to") - def test_preprocess_invalid_tape_adjoint_legacy_opmath(self, ops, measurement, message): - """Test that preprocessing fails if adjoint differentiation is requested and an - invalid tape is used""" - qs = qml.tape.QuantumScript(ops, measurement) - execution_config = qml.devices.ExecutionConfig(gradient_method="adjoint") - program, _ = qml.device("default.qubit").preprocess(execution_config) - with pytest.raises(qml.DeviceError, match=message): - program([qs]) - @pytest.mark.parametrize( "ops, measurement, message", [ @@ -876,22 +850,6 @@ def test_u3_non_trainable_params(self): assert len(res.operations) == 5 assert res.trainable_params == [0, 1, 2, 3, 4] - @pytest.mark.usefixtures( - "legacy_opmath_only" - ) # this is only an issue for legacy Hamiltonian that does not define a matrix method - def test_unsupported_obs_legacy_opmath(self): - """Test that the correct error is raised if a Hamiltonian measurement is differentiated""" - obs = qml.Hamiltonian([2, 0.5], [qml.PauliZ(0), qml.PauliY(1)]) - qs = qml.tape.QuantumScript([qml.RX(0.5, wires=1)], [qml.expval(obs)]) - qs.trainable_params = {0} - - program = qml.device("default.qubit").preprocess( - ExecutionConfig(gradient_method="adjoint") - )[0] - - with pytest.raises(qml.DeviceError, match=r"Observable "): - program((qs,)) - def test_trainable_hermitian_warns(self): """Test attempting to compute the gradient of a tape that obtains the expectation value of a Hermitian operator emits a warning if the diff --git a/tests/devices/default_tensor/test_scalability.py b/tests/devices/default_tensor/test_scalability.py index af6ec5b6fc4..cb330fc5122 100644 --- a/tests/devices/default_tensor/test_scalability.py +++ b/tests/devices/default_tensor/test_scalability.py @@ -113,18 +113,6 @@ def circuit(): _ = qml.QNode(circuit, dev)() - def test_tensor(self, method): - """Test that the device can compute the expval of a multi-qubit Tensor.""" - - wires = 30 - dev = qml.device("default.tensor", wires=wires, method=method) - - def circuit(): - return qml.expval(qml.operation.Tensor(*(qml.PauliY(i) for i in range(wires)))) - - _ = qml.QNode(circuit, dev)() - - @pytest.mark.usefixtures("new_opmath_only") def test_hamiltonian(self, method): """Test that the device can compute the expval of a multi-qubit Hamiltonian.""" diff --git a/tests/devices/default_tensor/test_tensor_var.py b/tests/devices/default_tensor/test_tensor_var.py index 6c29b3bda26..794798db8a5 100644 --- a/tests/devices/default_tensor/test_tensor_var.py +++ b/tests/devices/default_tensor/test_tensor_var.py @@ -367,9 +367,6 @@ def test_PauliZ_hadamard_PauliY(self, theta, phi, varphi, dev): assert np.allclose(calculated_val, reference_val, tol) -# This test is only for the new opmath since there is an error -# in the tape computation with `default.qubit`, that we use as reference. -@pytest.mark.usefixtures("new_opmath_only") @pytest.mark.parametrize("theta, phi", list(zip(THETA, PHI))) @pytest.mark.parametrize("method", ["mps", "tn"]) def test_multi_qubit_gates(theta, phi, method): diff --git a/tests/devices/qubit/test_measure.py b/tests/devices/qubit/test_measure.py index 8c6b7959463..8ddc076d028 100644 --- a/tests/devices/qubit/test_measure.py +++ b/tests/devices/qubit/test_measure.py @@ -97,30 +97,6 @@ def test_sum_sum_of_terms_when_backprop(self): state = qml.numpy.zeros(2) assert get_measurement_function(qml.expval(S), state) is sum_of_terms_method - @pytest.mark.usefixtures("legacy_opmath_only") - def test_hamiltonian_with_multi_wire_obs(self): - """Check that a Hamiltonian with a multi-wire observable uses the sum of terms method.""" - - S = qml.Hamiltonian( - [0.5, 0.5], - [ - qml.X(0), - qml.Hermitian( - np.array( - [ - [0.5, 1.0j, 0.0, -3j], - [-1.0j, -1.1, 0.0, -0.1], - [0.0, 0.0, -0.9, 12.0], - [3j, -0.1, 12.0, 0.0], - ] - ), - wires=[0, 1], - ), - ], - ) - state = np.zeros(2) - assert get_measurement_function(qml.expval(S), state) is sum_of_terms_method - def test_no_sparse_matrix(self): """Tests Hamiltonians/Sums containing observables that do not have a sparse matrix.""" @@ -230,7 +206,6 @@ def qnode(t1, t2): t1, t2 = 0.5, 1.0 assert qml.math.allclose(qnode(t1, t2), jax.jit(qnode)(t1, t2)) - @pytest.mark.usefixtures("new_opmath_only") def test_measure_identity_no_wires(self): """Test that measure can handle the expectation value of identity on no wires.""" @@ -441,8 +416,8 @@ def f(scale, coeffs, n_wires=10, offset=0.1, convert_to_hamiltonian=False): H = qml.Hamiltonian( coeffs, [ - qml.operation.Tensor(*(qml.PauliZ(i) for i in range(n_wires))), - qml.operation.Tensor(*(qml.PauliY(i) for i in range(n_wires))), + qml.prod(*(qml.PauliZ(i) for i in range(n_wires))), + qml.prod(*(qml.PauliY(i) for i in range(n_wires))), ], ) else: diff --git a/tests/devices/qubit/test_sampling.py b/tests/devices/qubit/test_sampling.py index 1675508b98b..c2c140e4941 100644 --- a/tests/devices/qubit/test_sampling.py +++ b/tests/devices/qubit/test_sampling.py @@ -517,7 +517,6 @@ def test_identity_on_no_wires(self): [result] = measure_with_samples([mp], state, shots=qml.measurements.Shots(1)) assert qml.math.allclose(result, 1.0) - @pytest.mark.usefixtures("new_opmath_only") def test_identity_on_no_wires_with_other_observables(self): """Test that measuring an identity on no wires can be used in conjunction with other measurements.""" @@ -1150,7 +1149,6 @@ class TestHamiltonianSamples: """Test that the measure_with_samples function works as expected for Hamiltonian and Sum observables""" - @pytest.mark.usefixtures("use_legacy_and_new_opmath") def test_hamiltonian_expval(self, seed): """Test that sampling works well for Hamiltonian observables""" x, y = np.array(0.67), np.array(0.95) diff --git a/tests/devices/qubit/test_simulate.py b/tests/devices/qubit/test_simulate.py index c926a4d655c..f399201b2bb 100644 --- a/tests/devices/qubit/test_simulate.py +++ b/tests/devices/qubit/test_simulate.py @@ -1306,16 +1306,6 @@ def test_simple_dynamic_circuit(self, shots, measure_f, postselect, reset, meas_ The above combinations should work for finite shots, shot vectors and post-selecting of either the 0 or 1 branch. """ - if ( - isinstance(meas_obj, (qml.X, qml.Z, qml.Y)) - and measure_f in (qml.var,) - and not qml.operation.active_new_opmath() - ): - pytest.xfail( - "The tree-traversal method does not work with legacy opmath with " - "`qml.var` of pauli observables in the circuit." - ) - if measure_f in (qml.expval, qml.var) and ( isinstance(meas_obj, list) or meas_obj == "mcm_list" ): diff --git a/tests/devices/qutrit_mixed/test_qutrit_mixed_measure.py b/tests/devices/qutrit_mixed/test_qutrit_mixed_measure.py index 56d61fa339b..8898b00e129 100644 --- a/tests/devices/qutrit_mixed/test_qutrit_mixed_measure.py +++ b/tests/devices/qutrit_mixed/test_qutrit_mixed_measure.py @@ -427,7 +427,6 @@ def test_variance_measurement(self, observable, ml_framework, two_qutrit_batched assert np.allclose(res, expected) -@pytest.mark.usefixtures("use_legacy_and_new_opmath") class TestSumOfTermsDifferentiability: x = 0.52 diff --git a/tests/devices/qutrit_mixed/test_qutrit_mixed_preprocessing.py b/tests/devices/qutrit_mixed/test_qutrit_mixed_preprocessing.py index a49265ce082..f72f433ff51 100644 --- a/tests/devices/qutrit_mixed/test_qutrit_mixed_preprocessing.py +++ b/tests/devices/qutrit_mixed/test_qutrit_mixed_preprocessing.py @@ -144,7 +144,6 @@ def test_accepted_operator(self, op, expected): (qml.QutritDepolarizingChannel(0.4, 0), False), (qml.GellMann(0, 1), True), (qml.Snapshot(), False), - (qml.operation.Tensor(qml.GellMann(0, 1), qml.GellMann(3, 3)), True), (qml.ops.op_math.SProd(1.2, qml.GellMann(0, 1)), True), (qml.sum(qml.ops.op_math.SProd(1.2, qml.GellMann(0, 1)), qml.GellMann(1, 3)), True), (qml.ops.op_math.Prod(qml.GellMann(0, 1), qml.GellMann(3, 3)), True), diff --git a/tests/devices/qutrit_mixed/test_qutrit_mixed_sampling.py b/tests/devices/qutrit_mixed/test_qutrit_mixed_sampling.py index 176d9c92b31..618fa08da97 100644 --- a/tests/devices/qutrit_mixed/test_qutrit_mixed_sampling.py +++ b/tests/devices/qutrit_mixed/test_qutrit_mixed_sampling.py @@ -373,7 +373,7 @@ def test_sample_observables(self): qml.sample(qml.GellMann(0, 1) @ qml.GellMann(1, 1)), state, shots=shots ) assert results_gel_1s.shape == (shots.total_shots,) - assert results_gel_1s.dtype == np.float64 if qml.operation.active_new_opmath() else np.int64 + assert results_gel_1s.dtype == np.float64 assert sorted(np.unique(results_gel_1s)) == [-1, 0, 1] @flaky @@ -654,13 +654,9 @@ class TestHamiltonianSamples: """Test that the measure_with_samples function works as expected for Hamiltonian and Sum observables""" - @pytest.mark.usefixtures("use_legacy_and_new_opmath") def test_hamiltonian_expval(self, obs, seed): """Test that sampling works well for Hamiltonian and Sum observables""" - if not qml.operation.active_new_opmath(): - obs = qml.operation.convert_to_legacy_H(obs) - shots = qml.measurements.Shots(10000) x, y = np.array(0.67), np.array(0.95) ops = [qml.TRY(x, wires=0), qml.TRZ(y, wires=0)] @@ -674,13 +670,9 @@ def test_hamiltonian_expval(self, obs, seed): assert isinstance(res, np.float64) assert np.allclose(res, expected, atol=APPROX_ATOL) - @pytest.mark.usefixtures("use_legacy_and_new_opmath") def test_hamiltonian_expval_shot_vector(self, obs, seed): """Test that sampling works well for Hamiltonian and Sum observables with a shot vector""" - if not qml.operation.active_new_opmath(): - obs = qml.operation.convert_to_legacy_H(obs) - shots = qml.measurements.Shots((10000, 100000)) x, y = np.array(0.67), np.array(0.95) ops = [qml.TRY(x, wires=0), qml.TRZ(y, wires=0)] diff --git a/tests/devices/qutrit_mixed/test_qutrit_mixed_tracking.py b/tests/devices/qutrit_mixed/test_qutrit_mixed_tracking.py index 41a324aab7b..73f801253a7 100644 --- a/tests/devices/qutrit_mixed/test_qutrit_mixed_tracking.py +++ b/tests/devices/qutrit_mixed/test_qutrit_mixed_tracking.py @@ -201,7 +201,6 @@ def test_single_expval(self, mps, expected_exec, expected_shots): assert dev.tracker.totals["simulations"] == 1 assert dev.tracker.totals["shots"] == 3 * expected_shots - @pytest.mark.usefixtures("use_legacy_and_new_opmath") def test_multiple_expval_with_prods(self): """ Test tracker tracks default qutrit mixed execute number of shots for new and old opmath tensors. diff --git a/tests/devices/test_default_clifford.py b/tests/devices/test_default_clifford.py index ba871dc961b..9f3b55933af 100644 --- a/tests/devices/test_default_clifford.py +++ b/tests/devices/test_default_clifford.py @@ -256,7 +256,6 @@ def circuit_fn(): assert qml.math.shape(samples[2]) == (shots,) -@pytest.mark.usefixtures("use_legacy_and_new_opmath") @pytest.mark.parametrize("tableau", [True, False]) @pytest.mark.parametrize("shots", [None, 50000]) @pytest.mark.parametrize( diff --git a/tests/devices/test_default_qutrit_mixed.py b/tests/devices/test_default_qutrit_mixed.py index 8c9c635509a..e7a84344862 100644 --- a/tests/devices/test_default_qutrit_mixed.py +++ b/tests/devices/test_default_qutrit_mixed.py @@ -749,7 +749,6 @@ def test_tf(self): assert qml.math.allclose(jacobian_1, jacobian_3) -@pytest.mark.usefixtures("use_legacy_and_new_opmath") class TestSumOfTermsDifferentiability: """Tests Hamiltonian and sum expvals are still differentiable. This is a copy of the tests in `test_qutrit_mixed_measure.py`, but using the device instead. @@ -1142,7 +1141,6 @@ def test_different_executions_same_prng_key(self): qml.s_prod(0.8, qml.GellMann(0, 3)) + qml.s_prod(0.5, qml.GellMann(0, 1)), ], ) -@pytest.mark.usefixtures("use_legacy_and_new_opmath") class TestHamiltonianSamples: """Test that the measure_with_samples function works as expected for Hamiltonian and Sum observables. @@ -1151,8 +1149,6 @@ class TestHamiltonianSamples: def test_hamiltonian_expval(self, obs, seed): """Tests that sampling works well for Hamiltonian and Sum observables.""" - if not qml.operation.active_new_opmath(): - obs = qml.operation.convert_to_legacy_H(obs) x, y = np.array(0.67), np.array(0.95) ops = [qml.TRY(x, wires=0), qml.TRZ(y, wires=0)] @@ -1167,9 +1163,6 @@ def test_hamiltonian_expval(self, obs, seed): def test_hamiltonian_expval_shot_vector(self, obs, seed): """Test that sampling works well for Hamiltonian and Sum observables with a shot vector.""" - if not qml.operation.active_new_opmath(): - obs = qml.operation.convert_to_legacy_H(obs) - shots = qml.measurements.Shots((10000, 100000)) x, y = np.array(0.67), np.array(0.95) ops = [qml.TRY(x, wires=0), qml.TRZ(y, wires=0)] @@ -1245,9 +1238,6 @@ def test_differentiate_jitted_qnode(self, measurement_func): """Test that a jitted qnode can be correctly differentiated""" import jax - if measurement_func is qml.var and not qml.operation.active_new_opmath(): - pytest.skip(reason="Variance for this test circuit not supported with legacy opmath") - dev = qml.device("default.qutrit.mixed") def qfunc(x, y): diff --git a/tests/devices/test_legacy_device.py b/tests/devices/test_legacy_device.py index dacf7b3b1d6..671425692d6 100644 --- a/tests/devices/test_legacy_device.py +++ b/tests/devices/test_legacy_device.py @@ -314,7 +314,6 @@ def test_check_validity_on_valid_queue(self, mock_device_supporting_paulis): # Raises an error if queue or observables are invalid dev.check_validity(queue, observables) - @pytest.mark.usefixtures("new_opmath_only") def test_check_validity_containing_prod(self, mock_device_supporting_prod): """Tests that the function Device.check_validity works with Prod""" @@ -332,7 +331,6 @@ def test_check_validity_containing_prod(self, mock_device_supporting_prod): dev.check_validity(queue, observables) - @pytest.mark.usefixtures("new_opmath_only") def test_prod_containing_unsupported_nested_observables(self, mock_device_supporting_prod): """Tests that the observables nested within Prod are checked for validity""" @@ -350,24 +348,6 @@ def test_prod_containing_unsupported_nested_observables(self, mock_device_suppor with pytest.raises(qml.DeviceError, match="Observable PauliY not supported"): dev.check_validity(queue, unsupported_nested_observables) - @pytest.mark.usefixtures("legacy_opmath_only") - def test_check_validity_on_tensor_support_legacy_opmath(self, mock_device_supporting_paulis): - """Tests the function Device.check_validity with tensor support capability""" - dev = mock_device_supporting_paulis() - - queue = [ - qml.PauliX(wires=0), - qml.PauliY(wires=1), - qml.PauliZ(wires=2), - ] - - observables = [qml.expval(qml.PauliZ(0) @ qml.PauliX(1))] - - # mock device does not support Tensor product - with pytest.raises(qml.DeviceError, match="Tensor observables not supported"): - dev.check_validity(queue, observables) - - @pytest.mark.usefixtures("new_opmath_only") def test_check_validity_on_prod_support(self, mock_device_supporting_paulis): """Tests the function Device.check_validity with prod support capability""" dev = mock_device_supporting_paulis() @@ -384,32 +364,6 @@ def test_check_validity_on_prod_support(self, mock_device_supporting_paulis): with pytest.raises(qml.DeviceError, match="Observable Prod not supported"): dev.check_validity(queue, observables) - @pytest.mark.usefixtures("legacy_opmath_only") - def test_check_validity_on_invalid_observable_with_tensor_support(self, monkeypatch): - """Tests the function Device.check_validity with tensor support capability - but with an invalid observable""" - queue = [ - qml.PauliX(wires=0), - qml.PauliY(wires=1), - qml.PauliZ(wires=2), - ] - - observables = [qml.expval(qml.PauliZ(0) @ qml.Hadamard(1))] - - D = Device - with monkeypatch.context() as m: - m.setattr(D, "__abstractmethods__", frozenset()) - m.setattr(D, "operations", ["PauliX", "PauliY", "PauliZ"]) - m.setattr(D, "observables", ["PauliX", "PauliY", "PauliZ"]) - m.setattr(D, "capabilities", lambda self: {"supports_tensor_observables": True}) - m.setattr(D, "short_name", "Dummy") - - dev = D() - - # mock device supports Tensor products but not hadamard - with pytest.raises(qml.DeviceError, match="Observable Hadamard not supported"): - dev.check_validity(queue, observables) - def test_check_validity_on_invalid_queue(self, mock_device_supporting_paulis): """Tests the function Device.check_validity with invalid queue and valid observables""" dev = mock_device_supporting_paulis() diff --git a/tests/devices/test_null_qubit.py b/tests/devices/test_null_qubit.py index d0565b1b9e6..928a4c7ea90 100644 --- a/tests/devices/test_null_qubit.py +++ b/tests/devices/test_null_qubit.py @@ -658,9 +658,7 @@ def f(dev, scale, n_wires=10, offset=0.1, style="sum"): t1 = 2.5 * qml.prod(*(qml.PauliZ(i) for i in range(n_wires))) t2 = 6.2 * qml.prod(*(qml.PauliY(i) for i in range(n_wires))) H = t1 + t2 - if style == "hamiltonian": - H = H.pauli_rep.hamiltonian() - elif style == "hermitian": + if style == "hermitian": H = qml.Hermitian(H.matrix(), wires=H.wires) qs = qml.tape.QuantumScript(ops, [qml.expval(H)]) config = ExecutionConfig(interface=qml.math.get_interface(scale)) @@ -674,12 +672,9 @@ def f_hashable(scale, n_wires=10, offset=0.1, style="sum"): t1 = 2.5 * qml.prod(*(qml.PauliZ(i) for i in range(n_wires))) t2 = 6.2 * qml.prod(*(qml.PauliY(i) for i in range(n_wires))) H = t1 + t2 - if style == "hamiltonian": - H = H.pauli_rep.hamiltonian() - elif style == "hermitian": + if style == "hermitian": H = qml.Hermitian(H.matrix(), wires=H.wires) qs = qml.tape.QuantumScript(ops, [qml.expval(H)]) - qs = qml.tape.QuantumScript(ops, [qml.expval(H)]) return NullQubit().execute(qs) @staticmethod @@ -691,7 +686,7 @@ def expected(scale, n_wires=10, offset=0.1, like="numpy"): return 2.5 * qml.math.prod(cosines) + 6.2 * qml.math.prod(sines) @pytest.mark.autograd - @pytest.mark.parametrize("style", ("sum", "hamiltonian", "hermitian")) + @pytest.mark.parametrize("style", ("sum", "hermitian")) def test_autograd_backprop(self, style): """Test that backpropagation derivatives work in autograd with hamiltonians and large sums.""" dev = NullQubit() @@ -701,7 +696,7 @@ def test_autograd_backprop(self, style): @pytest.mark.jax @pytest.mark.parametrize("use_jit", (True, False)) - @pytest.mark.parametrize("style", ("sum", "hamiltonian", "hermitian")) + @pytest.mark.parametrize("style", ("sum", "hermitian")) def test_jax_backprop(self, style, use_jit): """Test that backpropagation derivatives work with jax with hamiltonians and large sums.""" import jax @@ -714,7 +709,7 @@ def test_jax_backprop(self, style, use_jit): @pytest.mark.xfail(reason="torch backprop does not work") @pytest.mark.torch - @pytest.mark.parametrize("style", ("sum", "hamiltonian", "hermitian")) + @pytest.mark.parametrize("style", ("sum", "hermitian")) def test_torch_backprop(self, style): """Test that backpropagation derivatives work with torch with hamiltonians and large sums.""" import torch @@ -730,7 +725,7 @@ def test_torch_backprop(self, style): @pytest.mark.xfail(reason="tf can't track derivatives") @pytest.mark.tf - @pytest.mark.parametrize("style", ("sum", "hamiltonian", "hermitian")) + @pytest.mark.parametrize("style", ("sum", "hermitian")) def test_tf_backprop(self, style): """Test that backpropagation derivatives work with tensorflow with hamiltonians and large sums.""" import tensorflow as tf diff --git a/tests/devices/test_preprocess.py b/tests/devices/test_preprocess.py index 9a6c4a2e472..861f1ff078d 100644 --- a/tests/devices/test_preprocess.py +++ b/tests/devices/test_preprocess.py @@ -306,14 +306,6 @@ def test_invalid_tensor_observable(self): with pytest.raises(qml.DeviceError, match="not supported on device"): validate_observables(tape, lambda obj: obj.name == "PauliX") - @pytest.mark.usefixtures("legacy_opmath_only") # only required for legacy observables - def test_valid_tensor_observable_legacy_opmath(self): - """Test that a valid tensor ovservable passes without error.""" - tape = QuantumScript([], [qml.expval(qml.PauliZ(0) @ qml.PauliY(1))]) - assert ( - validate_observables(tape, lambda obs: obs.name in {"PauliZ", "PauliY"})[0][0] is tape - ) - class TestValidateMeasurements: """Tests for the validate measurements transform.""" diff --git a/tests/fermi/test_bravyi_kitaev.py b/tests/fermi/test_bravyi_kitaev.py index 07a1fea5187..a78e0cb3735 100644 --- a/tests/fermi/test_bravyi_kitaev.py +++ b/tests/fermi/test_bravyi_kitaev.py @@ -427,412 +427,7 @@ def test_error_is_raised_for_dimension_mismatch(): ), ] -with qml.operation.disable_new_opmath_cm(warn=False): - FERMI_WORDS_AND_OPS_LEGACY = [ - ( - FermiWord({(0, 0): "+"}), - 1, - # trivial case of a creation operator with one qubit, 0^ -> (X_0 - iY_0) / 2 : Same as Jordan-Wigner - ([0.5, -0.5j], [qml.PauliX(0), qml.PauliY(0)]), - ), - ( - FermiWord({(0, 0): "-"}), - 1, - # trivial case of an annihilation operator with one qubit , 0 -> (X_0 + iY_0) / 2 : Same as Jordan-Wigner - ([(0.5 + 0j), (0.0 + 0.5j)], [qml.PauliX(0), qml.PauliY(0)]), - ), - ( - FermiWord({(0, 0): "+"}), - 2, - # trivial case of a creation operator with two qubits, 0^ -> (X_0 @ X_1 - iY_0 @ X_1) / 2 - ([0.5, -0.5j], [qml.PauliX(0) @ qml.PauliX(1), qml.PauliY(0) @ qml.PauliX(1)]), - ), - ( - FermiWord({(0, 0): "-"}), - 2, - # trivial case of an annihilation operator with two qubits , 0 -> (X_0 @ X_1 + iY_0 @ X_1) / 2 - ( - [(0.5 + 0j), (0.0 + 0.5j)], - [qml.PauliX(0) @ qml.PauliX(1), qml.PauliY(0) @ qml.PauliX(1)], - ), - ), - ( - FermiWord({(0, 0): "+", (1, 0): "-"}), - 4, - # obtained with openfermion using: binary_code_transform(FermionOperator('0^ 0'), parity_code(n_qubits)) for n_qubits = 4 - # reformatted the original openfermion output: (0.5+0j) [] + (-0.5+0j) [Z0] - ([(0.5 + 0j), (-0.5 + 0j)], [qml.Identity(0), qml.PauliZ(0)]), - ), - ( - FermiWord({(0, 0): "-", (1, 0): "+"}), - 4, - # obtained with openfermion using: binary_code_transform(FermionOperator('0 0^'), parity_code(n_qubits)) for n_qubits = 4 - # reformatted the original openfermion output: (0.5+0j) [] + (0.5+0j) [Z0] - ([(0.5 + 0j), (0.5 + 0j)], [qml.Identity(0), qml.PauliZ(0)]), - ), - ( - FermiWord({(0, 0): "-", (1, 1): "+"}), - 4, - # obtained with openfermion using: binary_code_transform(FermionOperator('0 1^'), parity_code(n_qubits)) for n_qubits = 4 - # reformatted the original openfermion output: - # (-0.25+0j) [X0] + - # 0.25 [X0 Z1] + - # (-0-0.25j) [Y0] + - # 0.25j [Y0 Z1] - ( - [(-0.25 + 0j), 0.25, (-0 - 0.25j), (0.25j)], - [ - qml.PauliX(0), - qml.PauliX(0) @ qml.PauliZ(1), - qml.PauliY(0), - qml.PauliY(0) @ qml.PauliZ(1), - ], - ), - ), - ( - FermiWord({(0, 3): "+", (1, 0): "-"}), - 4, - # obtained with openfermion using: binary_code_transform(FermionOperator('3^ 0'), parity_code(n_qubits)) for n_qubits = 4 - # reformatted the original openfermion output - # (-0.25+0j) [X0 X1 Z3] + - # 0.25j [X0 Y1 Z2] + - # -0.25j [Y0 X1 Z3] + - # (-0.25+0j) [Y0 Y1 Z2] - ( - [(-0.25 + 0j), 0.25j, (-0 - 0.25j), -0.25], - [ - qml.PauliX(0) @ qml.PauliX(1) @ qml.PauliZ(3), - qml.PauliX(0) @ qml.PauliY(1) @ qml.PauliZ(2), - qml.PauliY(0) @ qml.PauliX(1) @ qml.PauliZ(3), - qml.PauliY(0) @ qml.PauliY(1) @ qml.PauliZ(2), - ], - ), - ), - ( - FermiWord({(0, 5): "+", (1, 5): "-", (2, 5): "+", (3, 5): "-"}), - 6, - # obtained with openfermion using: binary_code_transform(FermionOperator('5^ 5 5^ 5'), parity_code(n_qubits)) with 6 qubits - ( - [(0.5 + 0j), (-0.5 + 0j)], - [qml.Identity(0), qml.PauliZ(4) @ qml.PauliZ(5)], - ), - ), - ( - FermiWord({(0, 3): "+", (1, 3): "-", (2, 3): "+", (3, 1): "-"}), - 6, - # obtained with openfermion using: binary_code_transform(FermionOperator('3^ 3 3^ 1'), parity_code(n_qubits)) with 6 qubits - # (-0.25+0j) [Z0 X1 Z3] + - # 0.25j [Z0 Y1 Z2] + - # (0.25+0j) [X1 Z2] + - # -0.25j [Y1 Z3] - ( - [-0.25, 0.25j, -0.25j, 0.25], - [ - qml.PauliZ(0) @ qml.PauliX(1) @ qml.PauliZ(3), - qml.PauliZ(0) @ qml.PauliY(1) @ qml.PauliZ(2), - qml.PauliY(1) @ qml.PauliZ(3), - qml.PauliX(1) @ qml.PauliZ(2), - ], - ), - ), - ( - FermiWord({(0, 1): "+", (1, 0): "-", (2, 1): "+", (3, 1): "-"}), - 6, - # obtained with openfermion using: binary_code_transform(FermionOperator('1^ 0 1^ 1'), parity_code(n_qubits)) with 6 qubits - ([0], [qml.Identity(0)]), - ), - ] - - FERMI_OPS_COMPLEX_LEGACY = [ - ( - FermiWord({(0, 2): "-", (1, 0): "+", (2, 3): "+"}), - 4, - # obtained with openfermion using: binary_code_transform(FermionOperator('2 0^ 3^'), parity_code(n_qubits)) for n_qubits = 4 - # reformatted the original openfermion output - # (0.125+0j) [X0 X1 X2 X3] + - # 0.125j [X0 X1 Y2 X3] + - # (0.125+0j) [X0 Y1 X2 Y3] + - # 0.125j [X0 Y1 Y2 Y3] + - # -0.125j [Y0 X1 X2 X3] + - # (0.125+0j) [Y0 X1 Y2 X3] + - # -0.125j [Y0 Y1 X2 Y3] + - # (0.125+0j) [Y0 Y1 Y2 Y3] - ( - [0.125, 0.125j, 0.125 + 0j, 0.125j, -0.125j, 0.125 + 0j, -0.125j, 0.125], - [ - qml.PauliX(0) @ qml.PauliX(1) @ qml.PauliX(2) @ qml.PauliX(3), - qml.PauliX(0) @ qml.PauliX(1) @ qml.PauliY(2) @ qml.PauliX(3), - qml.PauliX(0) @ qml.PauliY(1) @ qml.PauliX(2) @ qml.PauliY(3), - qml.PauliX(0) @ qml.PauliY(1) @ qml.PauliY(2) @ qml.PauliY(3), - qml.PauliY(0) @ qml.PauliX(1) @ qml.PauliX(2) @ qml.PauliX(3), - qml.PauliY(0) @ qml.PauliX(1) @ qml.PauliY(2) @ qml.PauliX(3), - qml.PauliY(0) @ qml.PauliY(1) @ qml.PauliX(2) @ qml.PauliY(3), - qml.PauliY(0) @ qml.PauliY(1) @ qml.PauliY(2) @ qml.PauliY(3), - ], - ), - ), - ( - FermiWord({(0, 0): "-", (1, 3): "+"}), - 4, - # obtained with openfermion using: binary_code_transform(FermionOperator('0 3^'), parity_code(n_qubits)) for n_qubits = 4 - # reformatted the original openfermion output - # (0.25+0j) [X0 X1 Z3] + - # -0.25j [X0 Y1 Z2] + - # 0.25j [Y0 X1 Z3] + - # (0.25+0j) [Y0 Y1 Z2] - ( - [0.25, -0.25j, 0.25j, 0.25], - [ - qml.PauliX(0) @ qml.PauliX(1) @ qml.PauliZ(3), - qml.PauliX(0) @ qml.PauliY(1) @ qml.PauliZ(2), - qml.PauliY(0) @ qml.PauliX(1) @ qml.PauliZ(3), - qml.PauliY(0) @ qml.PauliY(1) @ qml.PauliZ(2), - ], - ), - ), - ( - FermiWord({(0, 3): "+", (1, 1): "+", (2, 3): "-", (3, 1): "-"}), - 6, - # obtained with openfermion using: binary_code_transform(FermionOperator('3^ 1^ 3 1'), parity_code(n_qubits)) with 6 qubits - # -0.25 [] + - # 0.25 [Z0 Z1] + - # -0.25 [Z0 Z2 Z3] + - # 0.25 [Z1 Z2 Z3] - # reformatted the original openfermion output - ( - [(-0.25 + 0j), (0.25 + 0j), (-0.25 + 0j), (0.25 + 0j)], - [ - qml.Identity(0), - qml.PauliZ(0) @ qml.PauliZ(1), - qml.PauliZ(0) @ qml.PauliZ(2) @ qml.PauliZ(3), - qml.PauliZ(1) @ qml.PauliZ(2) @ qml.PauliZ(3), - ], - ), - ), - ( - FermiWord({(0, 1): "+", (1, 4): "-", (2, 3): "-", (3, 4): "+"}), - 6, - # obtained with openfermion using: binary_code_transform(FermionOperator('1^ 4 3 4^'), parity_code(n_qubits)) with 6 qubits - # reformatted the original openfermion output - # (0.125+0j) [Z0 X1 Z3] + - # (0.125+0j) [Z0 X1 Z3 Z4] + - # 0.125j [Z0 Y1 Z2] + - # 0.125j [Z0 Y1 Z2 Z4] + - # (-0.125+0j) [X1 Z2] + - # (-0.125+0j) [X1 Z2 Z4] + - # -0.125j [Y1 Z3] + - # -0.125j [Y1 Z3 Z4] - ( - [0.125, 0.125, 0.125j, 0.125j, -0.125, -0.125, -0.125j, -0.125j], - [ - qml.PauliZ(0) @ qml.PauliX(1) @ qml.PauliZ(3), - qml.PauliZ(0) @ qml.PauliX(1) @ qml.PauliZ(3) @ qml.PauliZ(4), - qml.PauliZ(0) @ qml.PauliY(1) @ qml.PauliZ(2), - qml.PauliZ(0) @ qml.PauliY(1) @ qml.PauliZ(2) @ qml.PauliZ(4), - qml.PauliX(1) @ qml.PauliZ(2), - qml.PauliX(1) @ qml.PauliZ(2) @ qml.PauliZ(4), - qml.PauliY(1) @ qml.PauliZ(3), - qml.PauliY(1) @ qml.PauliZ(3) @ qml.PauliZ(4), - ], - ), - ), - ( - FermiWord({(0, 3): "+", (1, 1): "-", (2, 3): "+", (3, 1): "-"}), - 6, - # obtained with openfermion using: binary_code_transform(FermionOperator('3^ 1 3^ 1'), parity_code(n_qubits)) with 6 qubits - ([0], [qml.Identity(3)]), - ), - ( - FermiWord({(0, 1): "+", (1, 0): "+", (2, 4): "-", (3, 5): "-"}), - 6, - # obtained with openfermion using: binary_code_transform(FermionOperator('1^ 0^ 4 5'), parity_code(n_qubits)) with 6 qubits - # (0.0625+0j) [X0 Z1 X4] + - # (0.0625+0j) [X0 Z1 X4 Z5] + - # 0.0625j [X0 Z1 Y4] + - # 0.0625j [X0 Z1 Y4 Z5] + - # (0.0625+0j) [X0 X4] + - # (0.0625+0j) [X0 X4 Z5] + - # 0.0625j [X0 Y4] + - # 0.0625j [X0 Y4 Z5] + - # -0.0625j [Y0 Z1 X4] + - # -0.0625j [Y0 Z1 X4 Z5] + - # (0.0625+0j) [Y0 Z1 Y4] + - # (0.0625+0j) [Y0 Z1 Y4 Z5] + - # -0.0625j [Y0 X4] + - # -0.0625j [Y0 X4 Z5] + - # (0.0625+0j) [Y0 Y4] + - # (0.0625+0j) [Y0 Y4 Z5] - ( - [ - 0.0625, - 0.0625, - 0.0625j, - 0.0625j, - 0.0625, - 0.0625, - 0.0625j, - 0.0625j, - -0.0625j, - -0.0625j, - 0.0625, - 0.0625, - -0.0625j, - -0.0625j, - 0.0625, - 0.0625, - ], - [ - qml.PauliX(0) @ qml.PauliZ(1) @ qml.PauliX(4), - qml.PauliX(0) @ qml.PauliZ(1) @ qml.PauliX(4) @ qml.PauliZ(5), - qml.PauliX(0) @ qml.PauliZ(1) @ qml.PauliY(4), - qml.PauliX(0) @ qml.PauliZ(1) @ qml.PauliY(4) @ qml.PauliZ(5), - qml.PauliX(0) @ qml.PauliX(4), - qml.PauliX(0) @ qml.PauliX(4) @ qml.PauliZ(5), - qml.PauliX(0) @ qml.PauliY(4), - qml.PauliX(0) @ qml.PauliY(4) @ qml.PauliZ(5), - qml.PauliY(0) @ qml.PauliZ(1) @ qml.PauliX(4), - qml.PauliY(0) @ qml.PauliZ(1) @ qml.PauliX(4) @ qml.PauliZ(5), - qml.PauliY(0) @ qml.PauliZ(1) @ qml.PauliY(4), - qml.PauliY(0) @ qml.PauliZ(1) @ qml.PauliY(4) @ qml.PauliZ(5), - qml.PauliY(0) @ qml.PauliX(4), - qml.PauliY(0) @ qml.PauliX(4) @ qml.PauliZ(5), - qml.PauliY(0) @ qml.PauliY(4), - qml.PauliY(0) @ qml.PauliY(4) @ qml.PauliZ(5), - ], - ), - ), - ( - FermiWord({(0, 1): "-", (1, 0): "+"}), - 4, - # obtained with openfermion using: binary_code_transform(FermionOperator('1 0^'), parity_code(n_qubits)) for n_qubits = 4 - # reformatted the original openfermion output: - # -0.25 [X0] + - # 0.25 [X0 Z1] + - # 0.25j [Y0] + - # (-0-0.25j) [Y0 Z1] - ( - [(-0.25 + 0j), 0.25, 0.25j, (-0 - 0.25j)], - [ - qml.PauliX(0), - qml.PauliX(0) @ qml.PauliZ(1), - qml.PauliY(0), - qml.PauliY(0) @ qml.PauliZ(1), - ], - ), - ), - ( - FermiWord( - {(0, 1): "+", (1, 1): "-", (2, 3): "+", (3, 4): "-", (4, 2): "+", (5, 5): "-"} - ), - 6, - # obtained with openfermion using: binary_code_transform(FermionOperator('1^ 1 3^ 4 2^ 5'), parity_code(n_qubits)) with 6 qubits - # (0.03125+0j) [Z0 Z1 X2 X4] + - # (0.03125+0j) [Z0 Z1 X2 X4 Z5] + - # 0.03125j [Z0 Z1 X2 Y4] + - # 0.03125j [Z0 Z1 X2 Y4 Z5] + - # -0.03125j [Z0 Z1 Y2 X4] + - # -0.03125j [Z0 Z1 Y2 X4 Z5] + - # (0.03125+0j) [Z0 Z1 Y2 Y4] + - # (0.03125+0j) [Z0 Z1 Y2 Y4 Z5] + - # (0.03125+0j) [Z0 X2 Z3 X4] + - # (0.03125+0j) [Z0 X2 Z3 X4 Z5] + - # 0.03125j [Z0 X2 Z3 Y4] + - # 0.03125j [Z0 X2 Z3 Y4 Z5] + - # -0.03125j [Z0 Y2 Z3 X4] + - # -0.03125j [Z0 Y2 Z3 X4 Z5] + - # (0.03125+0j) [Z0 Y2 Z3 Y4] + - # (0.03125+0j) [Z0 Y2 Z3 Y4 Z5] + - # (-0.03125+0j) [Z1 X2 Z3 X4] + - # (-0.03125+0j) [Z1 X2 Z3 X4 Z5] + - # -0.03125j [Z1 X2 Z3 Y4] + - # -0.03125j [Z1 X2 Z3 Y4 Z5] + - # 0.03125j [Z1 Y2 Z3 X4] + - # 0.03125j [Z1 Y2 Z3 X4 Z5] + - # (-0.03125+0j) [Z1 Y2 Z3 Y4] + - # (-0.03125+0j) [Z1 Y2 Z3 Y4 Z5] + - # (-0.03125+0j) [X2 X4] + - # (-0.03125+0j) [X2 X4 Z5] + - # -0.03125j [X2 Y4] + - # -0.03125j [X2 Y4 Z5] + - # 0.03125j [Y2 X4] + - # 0.03125j [Y2 X4 Z5] + - # (-0.03125+0j) [Y2 Y4] + - # (-0.03125+0j) [Y2 Y4 Z5] - ( - [ - 0.03125, - 0.03125, - 0.03125j, - 0.03125j, - -0.03125j, - -0.03125j, - 0.03125, - 0.03125, - 0.03125, - 0.03125, - 0.03125j, - 0.03125j, - -0.03125j, - -0.03125j, - 0.03125, - 0.03125, - -0.03125, - -0.03125, - -0.03125j, - -0.03125j, - 0.03125j, - 0.03125j, - -0.03125, - -0.03125, - -0.03125, - -0.03125, - -0.03125j, - -0.03125j, - 0.03125j, - 0.03125j, - -0.03125, - -0.03125, - ], - [ - qml.PauliZ(0) @ qml.PauliZ(1) @ qml.PauliX(2) @ qml.PauliX(4), - qml.PauliZ(0) @ qml.PauliZ(1) @ qml.PauliX(2) @ qml.PauliX(4) @ qml.PauliZ(5), - qml.PauliZ(0) @ qml.PauliZ(1) @ qml.PauliX(2) @ qml.PauliY(4), - qml.PauliZ(0) @ qml.PauliZ(1) @ qml.PauliX(2) @ qml.PauliY(4) @ qml.PauliZ(5), - qml.PauliZ(0) @ qml.PauliZ(1) @ qml.PauliY(2) @ qml.PauliX(4), - qml.PauliZ(0) @ qml.PauliZ(1) @ qml.PauliY(2) @ qml.PauliX(4) @ qml.PauliZ(5), - qml.PauliZ(0) @ qml.PauliZ(1) @ qml.PauliY(2) @ qml.PauliY(4), - qml.PauliZ(0) @ qml.PauliZ(1) @ qml.PauliY(2) @ qml.PauliY(4) @ qml.PauliZ(5), - qml.PauliZ(0) @ qml.PauliX(2) @ qml.PauliZ(3) @ qml.PauliX(4), - qml.PauliZ(0) @ qml.PauliX(2) @ qml.PauliZ(3) @ qml.PauliX(4) @ qml.PauliZ(5), - qml.PauliZ(0) @ qml.PauliX(2) @ qml.PauliZ(3) @ qml.PauliY(4), - qml.PauliZ(0) @ qml.PauliX(2) @ qml.PauliZ(3) @ qml.PauliY(4) @ qml.PauliZ(5), - qml.PauliZ(0) @ qml.PauliY(2) @ qml.PauliZ(3) @ qml.PauliX(4), - qml.PauliZ(0) @ qml.PauliY(2) @ qml.PauliZ(3) @ qml.PauliX(4) @ qml.PauliZ(5), - qml.PauliZ(0) @ qml.PauliY(2) @ qml.PauliZ(3) @ qml.PauliY(4), - qml.PauliZ(0) @ qml.PauliY(2) @ qml.PauliZ(3) @ qml.PauliY(4) @ qml.PauliZ(5), - qml.PauliZ(1) @ qml.PauliX(2) @ qml.PauliZ(3) @ qml.PauliX(4), - qml.PauliZ(1) @ qml.PauliX(2) @ qml.PauliZ(3) @ qml.PauliX(4) @ qml.PauliZ(5), - qml.PauliZ(1) @ qml.PauliX(2) @ qml.PauliZ(3) @ qml.PauliY(4), - qml.PauliZ(1) @ qml.PauliX(2) @ qml.PauliZ(3) @ qml.PauliY(4) @ qml.PauliZ(5), - qml.PauliZ(1) @ qml.PauliY(2) @ qml.PauliZ(3) @ qml.PauliX(4), - qml.PauliZ(1) @ qml.PauliY(2) @ qml.PauliZ(3) @ qml.PauliX(4) @ qml.PauliZ(5), - qml.PauliZ(1) @ qml.PauliY(2) @ qml.PauliZ(3) @ qml.PauliY(4), - qml.PauliZ(1) @ qml.PauliY(2) @ qml.PauliZ(3) @ qml.PauliY(4) @ qml.PauliZ(5), - qml.PauliX(2) @ qml.PauliX(4), - qml.PauliX(2) @ qml.PauliX(4) @ qml.PauliZ(5), - qml.PauliX(2) @ qml.PauliY(4), - qml.PauliX(2) @ qml.PauliY(4) @ qml.PauliZ(5), - qml.PauliY(2) @ qml.PauliX(4), - qml.PauliY(2) @ qml.PauliX(4) @ qml.PauliZ(5), - qml.PauliY(2) @ qml.PauliY(4), - qml.PauliY(2) @ qml.PauliY(4) @ qml.PauliZ(5), - ], - ), - ), - ] - -@pytest.mark.usefixtures("new_opmath_only") @pytest.mark.parametrize("fermionic_op, n_qubits, result", FERMI_WORDS_AND_OPS + FERMI_OPS_COMPLEX) def test_bravyi_kitaev_fermi_word_ps(fermionic_op, n_qubits, result): """Test that the parity_transform function returns the correct qubit operator.""" @@ -847,24 +442,6 @@ def test_bravyi_kitaev_fermi_word_ps(fermionic_op, n_qubits, result): assert qubit_op == expected_op -@pytest.mark.usefixtures("legacy_opmath_only") -@pytest.mark.parametrize( - "fermionic_op, n_qubits, result", FERMI_WORDS_AND_OPS_LEGACY + FERMI_OPS_COMPLEX_LEGACY -) -def test_bravyi_kitaev_fermi_word_ps_legacy(fermionic_op, n_qubits, result): - """Test that the parity_transform function returns the correct qubit operator.""" - # convert FermiWord to PauliSentence and simplify - qubit_op = bravyi_kitaev(fermionic_op, n_qubits, ps=True) - qubit_op.simplify() - - # get expected op as PauliSentence and simplify - expected_op = pauli_sentence(qml.Hamiltonian(result[0], result[1])) - expected_op.simplify() - - assert qubit_op == expected_op - - -@pytest.mark.usefixtures("new_opmath_only") @pytest.mark.parametrize("fermionic_op, n_qubits, result", FERMI_WORDS_AND_OPS) def test_bravyi_kitaev_fermi_word_operation(fermionic_op, n_qubits, result): wires = fermionic_op.wires or [0] @@ -877,19 +454,6 @@ def test_bravyi_kitaev_fermi_word_operation(fermionic_op, n_qubits, result): qml.assert_equal(qubit_op.simplify(), expected_op.simplify()) -@pytest.mark.usefixtures("legacy_opmath_only") -@pytest.mark.parametrize("fermionic_op, n_qubits, result", FERMI_WORDS_AND_OPS_LEGACY) -def test_bravyi_kitaev_fermi_word_operation_legacy(fermionic_op, n_qubits, result): - wires = fermionic_op.wires or [0] - - qubit_op = bravyi_kitaev(fermionic_op, n_qubits) - - expected_op = pauli_sentence(qml.Hamiltonian(result[0], result[1])) - expected_op = expected_op.operation(wires) - - qml.assert_equal(qubit_op.simplify(), expected_op.simplify()) - - def test_bravyi_kitaev_for_identity(): """Test that the bravyi_kitaev function returns the correct qubit operator for Identity.""" qml.assert_equal(bravyi_kitaev(FermiWord({}), 2), qml.Identity(0)) diff --git a/tests/fermi/test_fermi_mapping.py b/tests/fermi/test_fermi_mapping.py index 3853ce26a99..492d7195157 100644 --- a/tests/fermi/test_fermi_mapping.py +++ b/tests/fermi/test_fermi_mapping.py @@ -349,336 +349,6 @@ ] -with qml.operation.disable_new_opmath_cm(warn=False): - FERMI_WORDS_AND_OPS_LEGACY = [ - ( - FermiWord({(0, 0): "+"}), - # trivial case of a creation operator, 0^ -> (X_0 - iY_0) / 2 - ([0.5, -0.5j], [qml.PauliX(0), qml.PauliY(0)]), - ), - ( - FermiWord({(0, 0): "-"}), - # trivial case of an annihilation operator, 0 -> (X_0 + iY_0) / 2 - ([(0.5 + 0j), (0.0 + 0.5j)], [qml.PauliX(0), qml.PauliY(0)]), - ), - ( - FermiWord({(0, 0): "+", (1, 0): "-"}), - # obtained with openfermion using: jordan_wigner(FermionOperator('0^ 0', 1)) - # reformatted the original openfermion output: (0.5+0j) [] + (-0.5+0j) [Z0] - ([(0.5 + 0j), (-0.5 + 0j)], [qml.Identity(0), qml.PauliZ(0)]), - ), - ( - FermiWord({(0, 0): "-", (1, 0): "+"}), - # obtained with openfermion using: jordan_wigner(FermionOperator('0 0^')) - # reformatted the original openfermion output: (0.5+0j) [] + (0.5+0j) [Z0] - ([(0.5 + 0j), (0.5 + 0j)], [qml.Identity(0), qml.PauliZ(0)]), - ), - ( - FermiWord({(0, 0): "-", (1, 1): "+"}), - # obtained with openfermion using: jordan_wigner(FermionOperator('0 1^')) - # reformatted the original openfermion output: - # (-0.25+0j) [X0 X1] + - # 0.25j [X0 Y1] + - # -0.25j [Y0 X1] + - # (-0.25+0j) [Y0 Y1] - ( - [(-0.25 + 0j), 0.25j, -0.25j, (-0.25 + 0j)], - [ - qml.PauliX(0) @ qml.PauliX(1), - qml.PauliX(0) @ qml.PauliY(1), - qml.PauliY(0) @ qml.PauliX(1), - qml.PauliY(0) @ qml.PauliY(1), - ], - ), - ), - ( - FermiWord({(0, 1): "-", (1, 0): "+"}), - # obtained with openfermion using: jordan_wigner(FermionOperator('1 0^')) - # reformatted the original openfermion output: - # (-0.25+0j) [X0 X1] + - # -0.25j [X0 Y1] + - # 0.25j [Y0 X1] + - # (-0.25+0j) [Y0 Y1] - ( - [(-0.25 + 0j), -0.25j, 0.25j, (-0.25 + 0j)], - [ - qml.PauliX(0) @ qml.PauliX(1), - qml.PauliX(0) @ qml.PauliY(1), - qml.PauliY(0) @ qml.PauliX(1), - qml.PauliY(0) @ qml.PauliY(1), - ], - ), - ), - ( - FermiWord({(0, 3): "+", (1, 0): "-"}), - # obtained with openfermion using: jordan_wigner(FermionOperator('3^ 0', 1)) - # reformatted the original openfermion output - ( - [(0.25 + 0j), -0.25j, 0.25j, (0.25 + 0j)], - [ - qml.PauliX(0) @ qml.PauliZ(1) @ qml.PauliZ(2) @ qml.PauliX(3), - qml.PauliX(0) @ qml.PauliZ(1) @ qml.PauliZ(2) @ qml.PauliY(3), - qml.PauliY(0) @ qml.PauliZ(1) @ qml.PauliZ(2) @ qml.PauliX(3), - qml.PauliY(0) @ qml.PauliZ(1) @ qml.PauliZ(2) @ qml.PauliY(3), - ], - ), - ), - ( - FermiWord({(0, 0): "-", (1, 3): "+"}), - # obtained with openfermion using: jordan_wigner(FermionOperator('0 3^')) - # reformatted the original openfermion output - ( - [(-0.25 + 0j), 0.25j, -0.25j, (-0.25 + 0j)], - [ - qml.PauliX(0) @ qml.PauliZ(1) @ qml.PauliZ(2) @ qml.PauliX(3), - qml.PauliX(0) @ qml.PauliZ(1) @ qml.PauliZ(2) @ qml.PauliY(3), - qml.PauliY(0) @ qml.PauliZ(1) @ qml.PauliZ(2) @ qml.PauliX(3), - qml.PauliY(0) @ qml.PauliZ(1) @ qml.PauliZ(2) @ qml.PauliY(3), - ], - ), - ), - ( - FermiWord({(0, 3): "-", (1, 0): "+"}), - # obtained with openfermion using: jordan_wigner(FermionOperator('3 0^')) - # reformatted the original openfermion output - ( - [(-0.25 + 0j), -0.25j, 0.25j, (-0.25 + 0j)], - [ - qml.PauliX(0) @ qml.PauliZ(1) @ qml.PauliZ(2) @ qml.PauliX(3), - qml.PauliX(0) @ qml.PauliZ(1) @ qml.PauliZ(2) @ qml.PauliY(3), - qml.PauliY(0) @ qml.PauliZ(1) @ qml.PauliZ(2) @ qml.PauliX(3), - qml.PauliY(0) @ qml.PauliZ(1) @ qml.PauliZ(2) @ qml.PauliY(3), - ], - ), - ), - ( - FermiWord({(0, 1): "+", (1, 4): "-"}), - # obtained with openfermion using: jordan_wigner(FermionOperator('1^ 4', 1)) - # reformatted the original openfermion output - ( - [(0.25 + 0j), 0.25j, -0.25j, (0.25 + 0j)], - [ - qml.PauliX(1) @ qml.PauliZ(2) @ qml.PauliZ(3) @ qml.PauliX(4), - qml.PauliX(1) @ qml.PauliZ(2) @ qml.PauliZ(3) @ qml.PauliY(4), - qml.PauliY(1) @ qml.PauliZ(2) @ qml.PauliZ(3) @ qml.PauliX(4), - qml.PauliY(1) @ qml.PauliZ(2) @ qml.PauliZ(3) @ qml.PauliY(4), - ], - ), - ), - ( - FermiWord({(0, 1): "+", (1, 1): "+", (2, 1): "-", (3, 1): "-"}), # [1, 1, 1, 1], - # obtained with openfermion using: jordan_wigner(FermionOperator('1^ 1^ 1 1', 1)) - ([0], [qml.Identity(1)]), - ), - ( - FermiWord({(0, 3): "+", (1, 1): "+", (2, 3): "-", (3, 1): "-"}), # [3, 1, 3, 1], - # obtained with openfermion using: jordan_wigner(FermionOperator('3^ 1^ 3 1', 1)) - # reformatted the original openfermion output - ( - [(-0.25 + 0j), (0.25 + 0j), (-0.25 + 0j), (0.25 + 0j)], - [qml.Identity(0), qml.PauliZ(1), qml.PauliZ(1) @ qml.PauliZ(3), qml.PauliZ(3)], - ), - ), - ( - FermiWord({(0, 3): "+", (1, 1): "-", (2, 3): "+", (3, 1): "-"}), # [3, 1, 3, 1], - # obtained with openfermion using: jordan_wigner(FermionOperator('3^ 1 3^ 1', 1)) - ([0], [qml.Identity(1)]), - ), - ( - FermiWord({(0, 1): "+", (1, 0): "-", (2, 1): "+", (3, 1): "-"}), # [1, 0, 1, 1], - # obtained with openfermion using: jordan_wigner(FermionOperator('1^ 0 1^ 1', 1)) - ([0], [qml.Identity(0)]), - ), - ( - FermiWord({(0, 1): "+", (1, 1): "-", (2, 0): "+", (3, 0): "-"}), # [1, 1, 0, 0], - # obtained with openfermion using: jordan_wigner(FermionOperator('1^ 1 0^ 0', 1)) - ( - [(0.25 + 0j), (-0.25 + 0j), (0.25 + 0j), (-0.25 + 0j)], - [qml.Identity(0), qml.PauliZ(0), qml.PauliZ(0) @ qml.PauliZ(1), qml.PauliZ(1)], - ), - ), - ( - FermiWord({(0, 5): "+", (1, 5): "-", (2, 5): "+", (3, 5): "-"}), # [5, 5, 5, 5], - # obtained with openfermion using: jordan_wigner(FermionOperator('5^ 5 5^ 5', 1)) - ( - [(0.5 + 0j), (-0.5 + 0j)], - [qml.Identity(0), qml.PauliZ(5)], - ), - ), - ( - FermiWord({(0, 3): "+", (1, 3): "-", (2, 3): "+", (3, 1): "-"}), - # obtained with openfermion using: jordan_wigner(FermionOperator('3^ 3 3^ 1', 1)) - ( - [(0.25 + 0j), (-0.25j), (0.25j), (0.25 + 0j)], - [ - qml.PauliX(1) @ qml.PauliZ(2) @ qml.PauliX(3), - qml.PauliX(1) @ qml.PauliZ(2) @ qml.PauliY(3), - qml.PauliY(1) @ qml.PauliZ(2) @ qml.PauliX(3), - qml.PauliY(1) @ qml.PauliZ(2) @ qml.PauliY(3), - ], - ), - ), - ] - - # can't be tested with conversion to operators yet, because the resulting operators - # are too complicated for qml.equal to successfully compare - FERMI_WORDS_AND_OPS_EXTENDED_LEGACY = [ - ( - FermiWord({(0, 3): "+", (1, 0): "-", (2, 2): "+", (3, 1): "-"}), - # obtained with openfermion using: jordan_wigner(FermionOperator('3^ 0 2^ 1', 1)) - ( - [ - (-0.0625 + 0j), - 0.0625j, - 0.0625j, - (0.0625 + 0j), - -0.0625j, - (-0.0625 + 0j), - (-0.0625 + 0j), - 0.0625j, - -0.0625j, - (-0.0625 + 0j), - (-0.0625 + 0j), - 0.0625j, - (0.0625 + 0j), - -0.0625j, - -0.0625j, - (-0.0625 + 0j), - ], - [ - qml.PauliX(0) @ qml.PauliX(1) @ qml.PauliX(2) @ qml.PauliX(3), - qml.PauliX(0) @ qml.PauliX(1) @ qml.PauliX(2) @ qml.PauliY(3), - qml.PauliX(0) @ qml.PauliX(1) @ qml.PauliY(2) @ qml.PauliX(3), - qml.PauliX(0) @ qml.PauliX(1) @ qml.PauliY(2) @ qml.PauliY(3), - qml.PauliX(0) @ qml.PauliY(1) @ qml.PauliX(2) @ qml.PauliX(3), - qml.PauliX(0) @ qml.PauliY(1) @ qml.PauliX(2) @ qml.PauliY(3), - qml.PauliX(0) @ qml.PauliY(1) @ qml.PauliY(2) @ qml.PauliX(3), - qml.PauliX(0) @ qml.PauliY(1) @ qml.PauliY(2) @ qml.PauliY(3), - qml.PauliY(0) @ qml.PauliX(1) @ qml.PauliX(2) @ qml.PauliX(3), - qml.PauliY(0) @ qml.PauliX(1) @ qml.PauliX(2) @ qml.PauliY(3), - qml.PauliY(0) @ qml.PauliX(1) @ qml.PauliY(2) @ qml.PauliX(3), - qml.PauliY(0) @ qml.PauliX(1) @ qml.PauliY(2) @ qml.PauliY(3), - qml.PauliY(0) @ qml.PauliY(1) @ qml.PauliX(2) @ qml.PauliX(3), - qml.PauliY(0) @ qml.PauliY(1) @ qml.PauliX(2) @ qml.PauliY(3), - qml.PauliY(0) @ qml.PauliY(1) @ qml.PauliY(2) @ qml.PauliX(3), - qml.PauliY(0) @ qml.PauliY(1) @ qml.PauliY(2) @ qml.PauliY(3), - ], - ), - ), - ( - FermiWord({(0, 3): "-", (1, 0): "+", (2, 2): "-", (3, 1): "+"}), - # obtained with openfermion using: jordan_wigner(FermionOperator('3 0^ 2 1^')) - ( - [ - (-0.0625 + 0j), - -0.0625j, - -0.0625j, - (0.0625 + 0j), - 0.0625j, - (-0.0625 + 0j), - (-0.0625 + 0j), - -0.0625j, - 0.0625j, - (-0.0625 + 0j), - (-0.0625 + 0j), - -0.0625j, - (0.0625 + 0j), - 0.0625j, - 0.0625j, - (-0.0625 + 0j), - ], - [ - qml.PauliX(0) @ qml.PauliX(1) @ qml.PauliX(2) @ qml.PauliX(3), - qml.PauliX(0) @ qml.PauliX(1) @ qml.PauliX(2) @ qml.PauliY(3), - qml.PauliX(0) @ qml.PauliX(1) @ qml.PauliY(2) @ qml.PauliX(3), - qml.PauliX(0) @ qml.PauliX(1) @ qml.PauliY(2) @ qml.PauliY(3), - qml.PauliX(0) @ qml.PauliY(1) @ qml.PauliX(2) @ qml.PauliX(3), - qml.PauliX(0) @ qml.PauliY(1) @ qml.PauliX(2) @ qml.PauliY(3), - qml.PauliX(0) @ qml.PauliY(1) @ qml.PauliY(2) @ qml.PauliX(3), - qml.PauliX(0) @ qml.PauliY(1) @ qml.PauliY(2) @ qml.PauliY(3), - qml.PauliY(0) @ qml.PauliX(1) @ qml.PauliX(2) @ qml.PauliX(3), - qml.PauliY(0) @ qml.PauliX(1) @ qml.PauliX(2) @ qml.PauliY(3), - qml.PauliY(0) @ qml.PauliX(1) @ qml.PauliY(2) @ qml.PauliX(3), - qml.PauliY(0) @ qml.PauliX(1) @ qml.PauliY(2) @ qml.PauliY(3), - qml.PauliY(0) @ qml.PauliY(1) @ qml.PauliX(2) @ qml.PauliX(3), - qml.PauliY(0) @ qml.PauliY(1) @ qml.PauliX(2) @ qml.PauliY(3), - qml.PauliY(0) @ qml.PauliY(1) @ qml.PauliY(2) @ qml.PauliX(3), - qml.PauliY(0) @ qml.PauliY(1) @ qml.PauliY(2) @ qml.PauliY(3), - ], - ), - ), - ( - FermiWord({(0, 3): "-", (1, 0): "+", (2, 2): "-", (3, 1): "+"}), - # obtained with openfermion using: jordan_wigner(FermionOperator('3 0^ 2 1^')) - ( - [ - (-0.0625 + 0j), - -0.0625j, - -0.0625j, - (0.0625 + 0j), - 0.0625j, - (-0.0625 + 0j), - (-0.0625 + 0j), - -0.0625j, - 0.0625j, - (-0.0625 + 0j), - (-0.0625 + 0j), - -0.0625j, - (0.0625 + 0j), - 0.0625j, - 0.0625j, - (-0.0625 + 0j), - ], - [ - qml.PauliX(0) @ qml.PauliX(1) @ qml.PauliX(2) @ qml.PauliX(3), - qml.PauliX(0) @ qml.PauliX(1) @ qml.PauliX(2) @ qml.PauliY(3), - qml.PauliX(0) @ qml.PauliX(1) @ qml.PauliY(2) @ qml.PauliX(3), - qml.PauliX(0) @ qml.PauliX(1) @ qml.PauliY(2) @ qml.PauliY(3), - qml.PauliX(0) @ qml.PauliY(1) @ qml.PauliX(2) @ qml.PauliX(3), - qml.PauliX(0) @ qml.PauliY(1) @ qml.PauliX(2) @ qml.PauliY(3), - qml.PauliX(0) @ qml.PauliY(1) @ qml.PauliY(2) @ qml.PauliX(3), - qml.PauliX(0) @ qml.PauliY(1) @ qml.PauliY(2) @ qml.PauliY(3), - qml.PauliY(0) @ qml.PauliX(1) @ qml.PauliX(2) @ qml.PauliX(3), - qml.PauliY(0) @ qml.PauliX(1) @ qml.PauliX(2) @ qml.PauliY(3), - qml.PauliY(0) @ qml.PauliX(1) @ qml.PauliY(2) @ qml.PauliX(3), - qml.PauliY(0) @ qml.PauliX(1) @ qml.PauliY(2) @ qml.PauliY(3), - qml.PauliY(0) @ qml.PauliY(1) @ qml.PauliX(2) @ qml.PauliX(3), - qml.PauliY(0) @ qml.PauliY(1) @ qml.PauliX(2) @ qml.PauliY(3), - qml.PauliY(0) @ qml.PauliY(1) @ qml.PauliY(2) @ qml.PauliX(3), - qml.PauliY(0) @ qml.PauliY(1) @ qml.PauliY(2) @ qml.PauliY(3), - ], - ), - ), - ( - FermiWord({(0, 0): "-", (1, 0): "+", (2, 2): "+", (3, 1): "-"}), - # obtained with openfermion using: jordan_wigner(FermionOperator('0 0^ 2^ 1')) - ( - [ - (0.125 + 0j), - -0.125j, - 0.125j, - (0.125 + 0j), - (0.125 + 0j), - -0.125j, - 0.125j, - (0.125 + 0j), - ], - [ - qml.PauliZ(0) @ qml.PauliX(1) @ qml.PauliX(2), - qml.PauliZ(0) @ qml.PauliX(1) @ qml.PauliY(2), - qml.PauliZ(0) @ qml.PauliY(1) @ qml.PauliX(2), - qml.PauliZ(0) @ qml.PauliY(1) @ qml.PauliY(2), - qml.PauliX(1) @ qml.PauliX(2), - qml.PauliX(1) @ qml.PauliY(2), - qml.PauliY(1) @ qml.PauliX(2), - qml.PauliY(1) @ qml.PauliY(2), - ], - ), - ), - ] - - -@pytest.mark.usefixtures("new_opmath_only") @pytest.mark.parametrize("fermionic_op, result", FERMI_WORDS_AND_OPS + FERMI_WORDS_AND_OPS_EXTENDED) def test_jordan_wigner_fermi_word_ps(fermionic_op, result): """Test that the jordan_wigner function returns the correct qubit operator.""" @@ -693,24 +363,6 @@ def test_jordan_wigner_fermi_word_ps(fermionic_op, result): assert qubit_op == expected_op -@pytest.mark.usefixtures("legacy_opmath_only") -@pytest.mark.parametrize( - "fermionic_op, result", FERMI_WORDS_AND_OPS_LEGACY + FERMI_WORDS_AND_OPS_EXTENDED_LEGACY -) -def test_jordan_wigner_fermi_word_ps_legacy(fermionic_op, result): - """Test that the jordan_wigner function returns the correct qubit operator.""" - # convert FermiWord to PauliSentence and simplify - qubit_op = jordan_wigner(fermionic_op, ps=True) - qubit_op.simplify() - - # get expected op as PauliSentence and simplify - expected_op = pauli_sentence(qml.Hamiltonian(result[0], result[1])) - expected_op.simplify() - - assert qubit_op == expected_op - - -@pytest.mark.usefixtures("new_opmath_only") # TODO: if qml.equal is extended to compare layers of nested ops, also test with FERMI_WORDS_AND_OPS_EXTENDED @pytest.mark.parametrize("fermionic_op, result", FERMI_WORDS_AND_OPS) def test_jordan_wigner_fermi_word_operation(fermionic_op, result): @@ -724,20 +376,6 @@ def test_jordan_wigner_fermi_word_operation(fermionic_op, result): qml.assert_equal(qubit_op.simplify(), expected_op.simplify()) -@pytest.mark.usefixtures("legacy_opmath_only") -# TODO: if qml.equal is extended to compare layers of nested ops, also test with FERMI_WORDS_AND_OPS_EXTENDED -@pytest.mark.parametrize("fermionic_op, result", FERMI_WORDS_AND_OPS_LEGACY) -def test_jordan_wigner_fermi_word_operation_legacy(fermionic_op, result): - wires = fermionic_op.wires or [0] - - qubit_op = jordan_wigner(fermionic_op) - - expected_op = pauli_sentence(qml.Hamiltonian(result[0], result[1])) - expected_op = expected_op.operation(wires) - - qml.assert_equal(qubit_op.simplify(), expected_op.simplify()) - - def test_jordan_wigner_for_identity(): """Test that the jordan_wigner function returns the correct qubit operator for Identity.""" qml.assert_equal(jordan_wigner(FermiWord({})), qml.Identity(0)) diff --git a/tests/fermi/test_parity_mapping.py b/tests/fermi/test_parity_mapping.py index 537be708afe..87ac986bcb7 100644 --- a/tests/fermi/test_parity_mapping.py +++ b/tests/fermi/test_parity_mapping.py @@ -437,422 +437,7 @@ def test_error_is_raised_for_dimension_mismatch(): ), ] -with qml.operation.disable_new_opmath_cm(warn=False): - FERMI_WORDS_AND_OPS_LEGACY = [ - ( - FermiWord({(0, 0): "+"}), - 1, - # trivial case of a creation operator with one qubit, 0^ -> (X_0 - iY_0) / 2 : Same as Jordan-Wigner - ([0.5, -0.5j], [qml.PauliX(0), qml.PauliY(0)]), - ), - ( - FermiWord({(0, 0): "-"}), - 1, - # trivial case of an annihilation operator with one qubit , 0 -> (X_0 + iY_0) / 2 : Same as Jordan-Wigner - ([(0.5 + 0j), (0.0 + 0.5j)], [qml.PauliX(0), qml.PauliY(0)]), - ), - ( - FermiWord({(0, 0): "+"}), - 2, - # trivial case of a creation operator with two qubits, 0^ -> (X_0 @ X_1 - iY_0 @ X_1) / 2 - ([0.5, -0.5j], [qml.PauliX(0) @ qml.PauliX(1), qml.PauliY(0) @ qml.PauliX(1)]), - ), - ( - FermiWord({(0, 0): "-"}), - 2, - # trivial case of an annihilation operator with two qubits , 0 -> (X_0 @ X_1 + iY_0 @ X_1) / 2 - ( - [(0.5 + 0j), (0.0 + 0.5j)], - [qml.PauliX(0) @ qml.PauliX(1), qml.PauliY(0) @ qml.PauliX(1)], - ), - ), - ( - FermiWord({(0, 0): "+", (1, 0): "-"}), - 4, - # obtained with openfermion using: binary_code_transform(FermionOperator('0^ 0'), parity_code(n_qubits)) for n_qubits = 4 - # reformatted the original openfermion output: (0.5+0j) [] + (-0.5+0j) [Z0] - ([(0.5 + 0j), (-0.5 + 0j)], [qml.Identity(0), qml.PauliZ(0)]), - ), - ( - FermiWord({(0, 0): "-", (1, 0): "+"}), - 4, - # obtained with openfermion using: binary_code_transform(FermionOperator('0 0^'), parity_code(n_qubits)) for n_qubits = 4 - # reformatted the original openfermion output: (0.5+0j) [] + (0.5+0j) [Z0] - ([(0.5 + 0j), (0.5 + 0j)], [qml.Identity(0), qml.PauliZ(0)]), - ), - ( - FermiWord({(0, 0): "-", (1, 1): "+"}), - 4, - # obtained with openfermion using: binary_code_transform(FermionOperator('0 1^'), parity_code(n_qubits)) for n_qubits = 4 - # reformatted the original openfermion output: - # (-0.25+0j) [X0] + - # 0.25 [X0 Z1] + - # (-0-0.25j) [Y0] + - # 0.25j [Y0 Z1] - ( - [(-0.25 + 0j), 0.25, (-0 - 0.25j), (0.25j)], - [ - qml.PauliX(0), - qml.PauliX(0) @ qml.PauliZ(1), - qml.PauliY(0), - qml.PauliY(0) @ qml.PauliZ(1), - ], - ), - ), - ( - FermiWord({(0, 3): "+", (1, 0): "-"}), - 4, - # obtained with openfermion using: binary_code_transform(FermionOperator('3^ 0'), parity_code(n_qubits)) for n_qubits = 4 - # reformatted the original openfermion output - # -0.25 [X0 X1 X2 Z3] + - # 0.25j [X0 X1 Y2] + - # (-0-0.25j) [Y0 X1 X2 Z3] + - # -0.25 [Y0 X1 Y2] - ( - [(-0.25 + 0j), 0.25j, (-0 - 0.25j), -0.25], - [ - qml.PauliX(0) @ qml.PauliX(1) @ qml.PauliX(2) @ qml.PauliZ(3), - qml.PauliX(0) @ qml.PauliX(1) @ qml.PauliY(2), - qml.PauliY(0) @ qml.PauliX(1) @ qml.PauliX(2) @ qml.PauliZ(3), - qml.PauliY(0) @ qml.PauliX(1) @ qml.PauliY(2), - ], - ), - ), - ( - FermiWord({(0, 5): "+", (1, 5): "-", (2, 5): "+", (3, 5): "-"}), - 6, - # obtained with openfermion using: binary_code_transform(FermionOperator('5^ 5 5^ 5'), parity_code(n_qubits)) with 6 qubits - ( - [(0.5 + 0j), (-0.5 + 0j)], - [qml.Identity(0), qml.PauliZ(4) @ qml.PauliZ(5)], - ), - ), - ( - FermiWord({(0, 3): "+", (1, 3): "-", (2, 3): "+", (3, 1): "-"}), - 6, - # obtained with openfermion using: binary_code_transform(FermionOperator('3^ 3 3^ 1'), parity_code(n_qubits)) with 6 qubits - # -0.25 [Z0 X1 X2 Z3] + - # 0.25j [Z0 X1 Y2] + - # (-0-0.25j) [Y1 X2 Z3] + - # -0.25 [Y1 Y2] - ( - [-0.25, 0.25j, -0.25j, -0.25], - [ - qml.PauliZ(0) @ qml.PauliX(1) @ qml.PauliX(2) @ qml.PauliZ(3), - qml.PauliZ(0) @ qml.PauliX(1) @ qml.PauliY(2), - qml.PauliY(1) @ qml.PauliX(2) @ qml.PauliZ(3), - qml.PauliY(1) @ qml.PauliY(2), - ], - ), - ), - ( - FermiWord({(0, 1): "+", (1, 0): "-", (2, 1): "+", (3, 1): "-"}), - 6, - # obtained with openfermion using: binary_code_transform(FermionOperator('1^ 0 1^ 1'), parity_code(n_qubits)) with 6 qubits - ([0], [qml.Identity(0)]), - ), - ] - - FERMI_OPS_COMPLEX_LEGACY = [ - ( - FermiWord({(0, 2): "-", (1, 0): "+", (2, 3): "+"}), - 4, - # obtained with openfermion using: binary_code_transform(FermionOperator('2 0^ 3^'), parity_code(n_qubits)) for n_qubits = 4 - # reformatted the original openfermion output - # (-0-0.125j) [X0 X1 Z2 Y3] + - # 0.125 [X0 X1 X3] + - # 0.125j [X0 Y1 Z2 X3] + - # 0.125 [X0 Y1 Y3] + - # -0.125 [Y0 X1 Z2 Y3] + - # (-0-0.125j) [Y0 X1 X3] + - # 0.125 [Y0 Y1 Z2 X3] + - # (-0-0.125j) [Y0 Y1 Y3] - ( - [(-0 - 0.125j), 0.125, 0.125j, 0.125, -0.125, -0.125j, 0.125, -0.125j], - [ - qml.PauliX(0) @ qml.PauliX(1) @ qml.PauliZ(2) @ qml.PauliY(3), - qml.PauliX(0) @ qml.PauliX(1) @ qml.PauliX(3), - qml.PauliX(0) @ qml.PauliY(1) @ qml.PauliZ(2) @ qml.PauliX(3), - qml.PauliX(0) @ qml.PauliY(1) @ qml.PauliY(3), - qml.PauliY(0) @ qml.PauliX(1) @ qml.PauliZ(2) @ qml.PauliY(3), - qml.PauliY(0) @ qml.PauliX(1) @ qml.PauliX(3), - qml.PauliY(0) @ qml.PauliY(1) @ qml.PauliZ(2) @ qml.PauliX(3), - qml.PauliY(0) @ qml.PauliY(1) @ qml.PauliY(3), - ], - ), - ), - ( - FermiWord({(0, 0): "-", (1, 3): "+"}), - 4, - # obtained with openfermion using: binary_code_transform(FermionOperator('0 3^'), parity_code(n_qubits)) for n_qubits = 4 - # reformatted the original openfermion output - # 0.25 [X0 X1 X2 Z3] + - # (-0-0.25j) [X0 X1 Y2] + - # 0.25j [Y0 X1 X2 Z3] + - # 0.25 [Y0 X1 Y2] - ( - [0.25, -0.25j, 0.25j, 0.25], - [ - qml.PauliX(0) @ qml.PauliX(1) @ qml.PauliX(2) @ qml.PauliZ(3), - qml.PauliX(0) @ qml.PauliX(1) @ qml.PauliY(2), - qml.PauliY(0) @ qml.PauliX(1) @ qml.PauliX(2) @ qml.PauliZ(3), - qml.PauliY(0) @ qml.PauliX(1) @ qml.PauliY(2), - ], - ), - ), - ( - FermiWord({(0, 3): "+", (1, 1): "+", (2, 3): "-", (3, 1): "-"}), - 6, - # obtained with openfermion using: binary_code_transform(FermionOperator('3^ 1^ 3 1'), parity_code(n_qubits)) with 6 qubits - # -0.25 [] + - # 0.25 [Z0 Z1] + - # -0.25 [Z0 Z1 Z2 Z3] + - # 0.25 [Z2 Z3] - # reformatted the original openfermion output - ( - [(-0.25 + 0j), (0.25 + 0j), (-0.25 + 0j), (0.25 + 0j)], - [ - qml.Identity(0), - qml.PauliZ(0) @ qml.PauliZ(1), - qml.PauliZ(0) @ qml.PauliZ(1) @ qml.PauliZ(2) @ qml.PauliZ(3), - qml.PauliZ(2) @ qml.PauliZ(3), - ], - ), - ), - ( - FermiWord({(0, 1): "+", (1, 4): "-", (2, 3): "-", (3, 4): "+"}), - 6, - # obtained with openfermion using: binary_code_transform(FermionOperator('1^ 4 3 4^'), parity_code(n_qubits)) with 6 qubits - # reformatted the original openfermion output - # 0.125 [Z0 X1 X2 Z3] + - # 0.125 [Z0 X1 X2 Z4] + - # 0.125j [Z0 X1 Y2] + - # 0.125j [Z0 X1 Y2 Z3 Z4] + - # (-0-0.125j) [Y1 X2 Z3] + - # (-0-0.125j) [Y1 X2 Z4] + - # 0.125 [Y1 Y2] + - # 0.125 [Y1 Y2 Z3 Z4] - ( - [0.125, 0.125, 0.125j, 0.125j, -0.125j, -0.125j, 0.125, 0.125], - [ - qml.PauliZ(0) @ qml.PauliX(1) @ qml.PauliX(2) @ qml.PauliZ(3), - qml.PauliZ(0) @ qml.PauliX(1) @ qml.PauliX(2) @ qml.PauliZ(4), - qml.PauliZ(0) @ qml.PauliX(1) @ qml.PauliY(2), - qml.PauliZ(0) @ qml.PauliX(1) @ qml.PauliY(2) @ qml.PauliZ(3) @ qml.PauliZ(4), - qml.PauliY(1) @ qml.PauliX(2) @ qml.PauliZ(3), - qml.PauliY(1) @ qml.PauliX(2) @ qml.PauliZ(4), - qml.PauliY(1) @ qml.PauliY(2), - qml.PauliY(1) @ qml.PauliY(2) @ qml.PauliZ(3) @ qml.PauliZ(4), - ], - ), - ), - ( - FermiWord({(0, 3): "+", (1, 1): "-", (2, 3): "+", (3, 1): "-"}), - 6, - # obtained with openfermion using: binary_code_transform(FermionOperator('3^ 1 3^ 1'), parity_code(n_qubits)) with 6 qubits - ([0], [qml.Identity(3)]), - ), - ( - FermiWord({(0, 1): "+", (1, 0): "+", (2, 4): "-", (3, 5): "-"}), - 6, - # obtained with openfermion using: binary_code_transform(FermionOperator('1^ 0^ 4 5^'), parity_code(n_qubits)) with 6 qubits - # 0.0625 [X0 Z1 Z3 X4 Z5] + - # 0.0625j [X0 Z1 Z3 Y4] + - # 0.0625 [X0 Z1 X4] + - # 0.0625j [X0 Z1 Y4 Z5] + - # 0.0625 [X0 Z3 X4 Z5] + - # 0.0625j [X0 Z3 Y4] + - # 0.0625 [X0 X4] + - # 0.0625j [X0 Y4 Z5] + - # (-0-0.0625j) [Y0 Z1 Z3 X4 Z5] + - # 0.0625 [Y0 Z1 Z3 Y4] + - # (-0-0.0625j) [Y0 Z1 X4] + - # 0.0625 [Y0 Z1 Y4 Z5] + - # (-0-0.0625j) [Y0 Z3 X4 Z5] + - # 0.0625 [Y0 Z3 Y4] + - # (-0-0.0625j) [Y0 X4] + - # 0.0625 [Y0 Y4 Z5]) - ( - [ - 0.0625, - 0.0625j, - 0.0625, - 0.0625j, - 0.0625, - 0.0625j, - 0.0625, - 0.0625j, - -0.0625j, - 0.0625, - -0.0625j, - 0.0625, - -0.0625j, - 0.0625, - -0.0625j, - 0.0625, - ], - [ - qml.PauliX(0) @ qml.PauliZ(1) @ qml.PauliZ(3) @ qml.PauliX(4) @ qml.PauliZ(5), - qml.PauliX(0) @ qml.PauliZ(1) @ qml.PauliZ(3) @ qml.PauliY(4), - qml.PauliX(0) @ qml.PauliZ(1) @ qml.PauliX(4), - qml.PauliX(0) @ qml.PauliZ(1) @ qml.PauliY(4) @ qml.PauliZ(5), - qml.PauliX(0) @ qml.PauliZ(3) @ qml.PauliX(4) @ qml.PauliZ(5), - qml.PauliX(0) @ qml.PauliZ(3) @ qml.PauliY(4), - qml.PauliX(0) @ qml.PauliX(4), - qml.PauliX(0) @ qml.PauliY(4) @ qml.PauliZ(5), - qml.PauliY(0) @ qml.PauliZ(1) @ qml.PauliZ(3) @ qml.PauliX(4) @ qml.PauliZ(5), - qml.PauliY(0) @ qml.PauliZ(1) @ qml.PauliZ(3) @ qml.PauliY(4), - qml.PauliY(0) @ qml.PauliZ(1) @ qml.PauliX(4), - qml.PauliY(0) @ qml.PauliZ(1) @ qml.PauliY(4) @ qml.PauliZ(5), - qml.PauliY(0) @ qml.PauliZ(3) @ qml.PauliX(4) @ qml.PauliZ(5), - qml.PauliY(0) @ qml.PauliZ(3) @ qml.PauliY(4), - qml.PauliY(0) @ qml.PauliX(4), - qml.PauliY(0) @ qml.PauliY(4) @ qml.PauliZ(5), - ], - ), - ), - ( - FermiWord({(0, 1): "-", (1, 0): "+"}), - 4, - # obtained with openfermion using: binary_code_transform(FermionOperator('1 0^'), parity_code(n_qubits)) for n_qubits = 4 - # reformatted the original openfermion output: - # -0.25 [X0] + - # 0.25 [X0 Z1] + - # 0.25j [Y0] + - # (-0-0.25j) [Y0 Z1] - ( - [(-0.25 + 0j), 0.25, 0.25j, (-0 - 0.25j)], - [ - qml.PauliX(0), - qml.PauliX(0) @ qml.PauliZ(1), - qml.PauliY(0), - qml.PauliY(0) @ qml.PauliZ(1), - ], - ), - ), - ( - FermiWord( - {(0, 1): "+", (1, 1): "-", (2, 3): "+", (3, 4): "-", (4, 2): "+", (5, 5): "-"} - ), - 6, - # obtained with openfermion using: binary_code_transform(FermionOperator('1^ 1 3^ 4 2^ 5'), parity_code(n_qubits)) with 6 qubits - # 0.03125 [Z0 Z1 X2 Z3 X4 Z5] + - # 0.03125j [Z0 Z1 X2 Z3 Y4] + - # 0.03125 [Z0 Z1 X2 X4] + - # 0.03125j [Z0 Z1 X2 Y4 Z5] + - # (-0-0.03125j) [Z0 Z1 Y2 Z3 X4] + - # 0.03125 [Z0 Z1 Y2 Z3 Y4 Z5] + - # (-0-0.03125j) [Z0 Z1 Y2 X4 Z5] + - # 0.03125 [Z0 Z1 Y2 Y4] + - # 0.03125 [Z0 X2 Z3 X4] + - # 0.03125j [Z0 X2 Z3 Y4 Z5] + - # 0.03125 [Z0 X2 X4 Z5] + - # 0.03125j [Z0 X2 Y4] + - # (-0-0.03125j) [Z0 Y2 Z3 X4 Z5] + - # 0.03125 [Z0 Y2 Z3 Y4] + - # (-0-0.03125j) [Z0 Y2 X4] + - # 0.03125 [Z0 Y2 Y4 Z5] + - # -0.03125 [Z1 X2 Z3 X4] + - # (-0-0.03125j) [Z1 X2 Z3 Y4 Z5] + - # -0.03125 [Z1 X2 X4 Z5] + - # (-0-0.03125j) [Z1 X2 Y4] + - # 0.03125j [Z1 Y2 Z3 X4 Z5] + - # -0.03125 [Z1 Y2 Z3 Y4] + - # 0.03125j [Z1 Y2 X4] + - # -0.03125 [Z1 Y2 Y4 Z5] + - # -0.03125 [X2 Z3 X4 Z5] + - # (-0-0.03125j) [X2 Z3 Y4] + - # -0.03125 [X2 X4] + - # (-0-0.03125j) [X2 Y4 Z5] + - # 0.03125j [Y2 Z3 X4] + - # -0.03125 [Y2 Z3 Y4 Z5] + - # 0.03125j [Y2 X4 Z5] + - # -0.03125 [Y2 Y4] - ( - [ - 0.03125, - 0.03125j, - 0.03125, - 0.03125j, - -0.03125j, - 0.03125, - -0.03125j, - 0.03125, - 0.03125, - 0.03125j, - 0.03125, - 0.03125j, - -0.03125j, - 0.03125, - -0.03125j, - 0.03125, - -0.03125, - -0.03125j, - -0.03125, - -0.03125j, - 0.03125j, - -0.03125, - 0.03125j, - -0.03125, - -0.03125, - -0.03125j, - -0.03125, - -0.03125j, - 0.03125j, - -0.03125, - 0.03125j, - -0.03125, - ], - [ - qml.PauliZ(0) - @ qml.PauliZ(1) - @ qml.PauliX(2) - @ qml.PauliZ(3) - @ qml.PauliX(4) - @ qml.PauliZ(5), - qml.PauliZ(0) @ qml.PauliZ(1) @ qml.PauliX(2) @ qml.PauliZ(3) @ qml.PauliY(4), - qml.PauliZ(0) @ qml.PauliZ(1) @ qml.PauliX(2) @ qml.PauliX(4), - qml.PauliZ(0) @ qml.PauliZ(1) @ qml.PauliX(2) @ qml.PauliY(4) @ qml.PauliZ(5), - qml.PauliZ(0) @ qml.PauliZ(1) @ qml.PauliY(2) @ qml.PauliZ(3) @ qml.PauliX(4), - qml.PauliZ(0) - @ qml.PauliZ(1) - @ qml.PauliY(2) - @ qml.PauliZ(3) - @ qml.PauliY(4) - @ qml.PauliZ(5), - qml.PauliZ(0) @ qml.PauliZ(1) @ qml.PauliY(2) @ qml.PauliX(4) @ qml.PauliZ(5), - qml.PauliZ(0) @ qml.PauliZ(1) @ qml.PauliY(2) @ qml.PauliY(4), - qml.PauliZ(0) @ qml.PauliX(2) @ qml.PauliZ(3) @ qml.PauliX(4), - qml.PauliZ(0) @ qml.PauliX(2) @ qml.PauliZ(3) @ qml.PauliY(4) @ qml.PauliZ(5), - qml.PauliZ(0) @ qml.PauliX(2) @ qml.PauliX(4) @ qml.PauliZ(5), - qml.PauliZ(0) @ qml.PauliX(2) @ qml.PauliY(4), - qml.PauliZ(0) @ qml.PauliY(2) @ qml.PauliZ(3) @ qml.PauliX(4) @ qml.PauliZ(5), - qml.PauliZ(0) @ qml.PauliY(2) @ qml.PauliZ(3) @ qml.PauliY(4), - qml.PauliZ(0) @ qml.PauliY(2) @ qml.PauliX(4), - qml.PauliZ(0) @ qml.PauliY(2) @ qml.PauliY(4) @ qml.PauliZ(5), - qml.PauliZ(1) @ qml.PauliX(2) @ qml.PauliZ(3) @ qml.PauliX(4), - qml.PauliZ(1) @ qml.PauliX(2) @ qml.PauliZ(3) @ qml.PauliY(4) @ qml.PauliZ(5), - qml.PauliZ(1) @ qml.PauliX(2) @ qml.PauliX(4) @ qml.PauliZ(5), - qml.PauliZ(1) @ qml.PauliX(2) @ qml.PauliY(4), - qml.PauliZ(1) @ qml.PauliY(2) @ qml.PauliZ(3) @ qml.PauliX(4) @ qml.PauliZ(5), - qml.PauliZ(1) @ qml.PauliY(2) @ qml.PauliZ(3) @ qml.PauliY(4), - qml.PauliZ(1) @ qml.PauliY(2) @ qml.PauliX(4), - qml.PauliZ(1) @ qml.PauliY(2) @ qml.PauliY(4) @ qml.PauliZ(5), - qml.PauliX(2) @ qml.PauliZ(3) @ qml.PauliX(4) @ qml.PauliZ(5), - qml.PauliX(2) @ qml.PauliZ(3) @ qml.PauliY(4), - qml.PauliX(2) @ qml.PauliX(4), - qml.PauliX(2) @ qml.PauliY(4) @ qml.PauliZ(5), - qml.PauliY(2) @ qml.PauliZ(3) @ qml.PauliX(4), - qml.PauliY(2) @ qml.PauliZ(3) @ qml.PauliY(4) @ qml.PauliZ(5), - qml.PauliY(2) @ qml.PauliX(4) @ qml.PauliZ(5), - qml.PauliY(2) @ qml.PauliY(4), - ], - ), - ), - ] - -@pytest.mark.usefixtures("new_opmath_only") @pytest.mark.parametrize("fermionic_op, n_qubits, result", FERMI_WORDS_AND_OPS + FERMI_OPS_COMPLEX) def test_parity_transform_fermi_word_ps(fermionic_op, n_qubits, result): """Test that the parity_transform function returns the correct qubit operator.""" @@ -867,24 +452,6 @@ def test_parity_transform_fermi_word_ps(fermionic_op, n_qubits, result): assert qubit_op == expected_op -@pytest.mark.usefixtures("legacy_opmath_only") -@pytest.mark.parametrize( - "fermionic_op, n_qubits, result", FERMI_WORDS_AND_OPS_LEGACY + FERMI_OPS_COMPLEX_LEGACY -) -def test_parity_transform_fermi_word_ps_legacy(fermionic_op, n_qubits, result): - """Test that the parity_transform function returns the correct qubit operator.""" - # convert FermiWord to PauliSentence and simplify - qubit_op = parity_transform(fermionic_op, n_qubits, ps=True) - qubit_op.simplify() - - # get expected op as PauliSentence and simplify - expected_op = pauli_sentence(qml.Hamiltonian(result[0], result[1])) - expected_op.simplify() - - assert qubit_op == expected_op - - -@pytest.mark.usefixtures("new_opmath_only") @pytest.mark.parametrize("fermionic_op, n_qubits, result", FERMI_WORDS_AND_OPS) def test_parity_transform_fermi_word_operation(fermionic_op, n_qubits, result): wires = fermionic_op.wires or [0] @@ -897,19 +464,6 @@ def test_parity_transform_fermi_word_operation(fermionic_op, n_qubits, result): qml.assert_equal(qubit_op.simplify(), expected_op.simplify()) -@pytest.mark.usefixtures("legacy_opmath_only") -@pytest.mark.parametrize("fermionic_op, n_qubits, result", FERMI_WORDS_AND_OPS_LEGACY) -def test_parity_transform_fermi_word_operation_legacy(fermionic_op, n_qubits, result): - wires = fermionic_op.wires or [0] - - qubit_op = parity_transform(fermionic_op, n_qubits) - - expected_op = pauli_sentence(qml.Hamiltonian(result[0], result[1])) - expected_op = expected_op.operation(wires) - - qml.assert_equal(qubit_op.simplify(), expected_op.simplify()) - - def test_parity_transform_for_identity(): """Test that the parity_transform function returns the correct qubit operator for Identity.""" qml.assert_equal(parity_transform(FermiWord({}), 2), qml.Identity(0)) diff --git a/tests/gradients/core/test_adjoint_diff.py b/tests/gradients/core/test_adjoint_diff.py index ec907a5827f..bf7aa2e606b 100644 --- a/tests/gradients/core/test_adjoint_diff.py +++ b/tests/gradients/core/test_adjoint_diff.py @@ -55,25 +55,6 @@ def test_finite_shots_warns(self): ): dev.adjoint_jacobian(tape) - @pytest.mark.usefixtures("legacy_opmath_only") - def test_hamiltonian_error_legacy_opmath(self, dev): - """Test that error is raised for qml.Hamiltonian""" - - with qml.queuing.AnnotatedQueue() as q: - qml.expval( - qml.Hamiltonian( - [np.array(-0.05), np.array(0.17)], - [qml.PauliX(0), qml.PauliZ(0)], - ) - ) - - tape = qml.tape.QuantumScript.from_queue(q) - with pytest.raises( - qml.QuantumFunctionError, - match="Adjoint differentiation method does not support Hamiltonian observables", - ): - dev.adjoint_jacobian(tape) - def test_linear_combination_adjoint_warning(self, dev): """Test that error is raised for qml.Hamiltonian""" diff --git a/tests/gradients/core/test_metric_tensor.py b/tests/gradients/core/test_metric_tensor.py index 361a9ec156a..387a7f15d63 100644 --- a/tests/gradients/core/test_metric_tensor.py +++ b/tests/gradients/core/test_metric_tensor.py @@ -1506,7 +1506,7 @@ def circuit1(x, z): qml.metric_tensor(circuit1, approx=None, allow_nonunitary=allow_nonunitary)(x, z) -def test_no_error_missing_aux_wire_not_used(recwarn): +def test_no_error_missing_aux_wire_not_used(): """Tests that a no error is raised if the requested (or default, if not given) auxiliary wire for the Hadamard test is missing but it is not used, either because ``approx`` is used or because there only is a diagonal contribution.""" @@ -1541,9 +1541,6 @@ def circuit_multi_block(x, z): qml.metric_tensor(circuit_multi_block, approx="block-diag")(x, z) qml.metric_tensor(circuit_multi_block, approx="block-diag", aux_wire="aux_wire")(x, z) - if qml.operation.active_new_opmath(): - assert len(recwarn) == 0 - def test_raises_circuit_that_uses_missing_wire(): """Test that an error in the original circuit is reraised properly and not caught. This avoids diff --git a/tests/gradients/core/test_pulse_gradient.py b/tests/gradients/core/test_pulse_gradient.py index 164c047c776..f8f09bf0a45 100644 --- a/tests/gradients/core/test_pulse_gradient.py +++ b/tests/gradients/core/test_pulse_gradient.py @@ -16,7 +16,6 @@ """ import copy -import warnings import numpy as np import pytest @@ -182,33 +181,6 @@ def test_with_general_ob(self, ham, params, time, ob, seed): # Check that the inserted exponential is correct qml.assert_equal(qml.exp(qml.dot([-1j * exp_shift], [ob])), _ops[1]) - @pytest.mark.usefixtures("legacy_opmath_only") # this is only an issue with legacy Hamiltonian - def test_warnings_legacy_opmath(self): - """Test that a warning is raised for computing eigenvalues of a Hamiltonian - for more than four wires but not for fewer wires.""" - import jax - - jax.config.update("jax_enable_x64", True) - ham = qml.pulse.constant * qml.PauliY(0) - op = qml.evolve(ham)([0.3], 2.0) - ob = qml.Hamiltonian( - [0.4, 0.2], [qml.operation.Tensor(*[qml.PauliY(i) for i in range(5)]), qml.PauliX(0)] - ) - with pytest.warns(UserWarning, match="the eigenvalues will be computed numerically"): - _split_evol_ops(op, ob, tau=0.4) - - ob = qml.Hamiltonian( - [0.4, 0.2], [qml.operation.Tensor(*[qml.PauliY(i) for i in range(4)]), qml.PauliX(0)] - ) - with warnings.catch_warnings(): - warnings.filterwarnings("error") - warnings.filterwarnings( - "ignore", - "qml.operation.Tensor uses the old approach", - qml.PennyLaneDeprecationWarning, - ) - _split_evol_ops(op, ob, tau=0.4) - @pytest.mark.jax class TestSplitEvolTapes: diff --git a/tests/gradients/parameter_shift/test_parameter_shift.py b/tests/gradients/parameter_shift/test_parameter_shift.py index 57ac1741bfa..3e58192b660 100644 --- a/tests/gradients/parameter_shift/test_parameter_shift.py +++ b/tests/gradients/parameter_shift/test_parameter_shift.py @@ -1514,7 +1514,6 @@ def test_gradients_agree_finite_differences(self, tol): assert np.allclose(grad_A, grad_F1, atol=tol, rtol=0) assert np.allclose(grad_A, grad_F2, atol=tol, rtol=0) - @pytest.mark.usefixtures("use_legacy_and_new_opmath") def test_variance_gradients_agree_finite_differences(self, tol): """Tests that the variance parameter-shift rule agrees with the first and second order finite differences""" @@ -2418,7 +2417,6 @@ def test_recycling_unshifted_tape_result(self): # + 2 operations x 2 shifted positions + 1 unshifted term <-- assert len(tapes) == (2 * 2 + 1) + (2 * 2 + 1) - @pytest.mark.usefixtures("use_legacy_and_new_opmath") @pytest.mark.parametrize("state", [[1], [0, 1]]) # Basis state and state vector def test_projector_variance(self, state, tol): """Test that the variance of a projector is correctly returned""" @@ -3321,7 +3319,6 @@ def cost_fn(x): assert np.allclose(res, expected, atol=tol, rtol=0) -@pytest.mark.usefixtures("use_legacy_and_new_opmath") @pytest.mark.parametrize("broadcast", [True, False]) class TestHamiltonianExpvalGradients: """Test that tapes ending with expval(H) can be 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 b1f6e86b9b7..8dc982f6b39 100644 --- a/tests/gradients/parameter_shift/test_parameter_shift_shot_vec.py +++ b/tests/gradients/parameter_shift/test_parameter_shift_shot_vec.py @@ -2007,7 +2007,8 @@ def test_multi_measure_no_warning(self, broadcast): # TODO: allow broadcast=True -@pytest.mark.usefixtures("use_legacy_and_new_opmath") + + @pytest.mark.parametrize("broadcast", [False]) class TestHamiltonianExpvalGradients: """Test that tapes ending with expval(H) can be diff --git a/tests/interfaces/test_autograd.py b/tests/interfaces/test_autograd.py index 0e0bb8f3f09..56dad40a4c7 100644 --- a/tests/interfaces/test_autograd.py +++ b/tests/interfaces/test_autograd.py @@ -770,7 +770,6 @@ def cost_fn(x): @pytest.mark.parametrize("execute_kwargs, shots, device_name", test_matrix) -@pytest.mark.usefixtures("use_legacy_and_new_opmath") class TestHamiltonianWorkflows: """Test that tapes ending with expectations of Hamiltonians provide correct results and gradients""" @@ -784,13 +783,11 @@ def cost_fn(self, execute_kwargs, shots, device_name, seed): def _cost_fn(weights, coeffs1, coeffs2): obs1 = [qml.PauliZ(0), qml.PauliZ(0) @ qml.PauliX(1), qml.PauliY(0)] H1 = qml.Hamiltonian(coeffs1, obs1) - if qml.operation.active_new_opmath(): - H1 = qml.pauli.pauli_sentence(H1).operation() + H1 = qml.pauli.pauli_sentence(H1).operation() obs2 = [qml.PauliZ(0)] H2 = qml.Hamiltonian(coeffs2, obs2) - if qml.operation.active_new_opmath(): - H2 = qml.pauli.pauli_sentence(H2).operation() + H2 = qml.pauli.pauli_sentence(H2).operation() with qml.queuing.AnnotatedQueue() as q: qml.RX(weights[0], wires=0) @@ -832,12 +829,9 @@ def cost_fn_jacobian(weights, coeffs1, coeffs2): ] ) - def test_multiple_hamiltonians_not_trainable(self, execute_kwargs, cost_fn, shots): + def test_multiple_hamiltonians_not_trainable(self, cost_fn, shots): """Test hamiltonian with no trainable parameters.""" - if execute_kwargs["diff_method"] == "adjoint" and not qml.operation.active_new_opmath(): - pytest.skip("adjoint differentiation does not support hamiltonians.") - coeffs1 = pnp.array([0.1, 0.2, 0.3], requires_grad=False) coeffs2 = pnp.array([0.7], requires_grad=False) weights = pnp.array([0.4, 0.5], requires_grad=True) @@ -858,12 +852,11 @@ def test_multiple_hamiltonians_not_trainable(self, execute_kwargs, cost_fn, shot else: assert np.allclose(res, expected, atol=atol_for_shots(shots), rtol=0) + @pytest.mark.xfail(reason="parameter shift derivatives do not yet support sums.") def test_multiple_hamiltonians_trainable(self, execute_kwargs, cost_fn, shots): """Test hamiltonian with trainable parameters.""" if execute_kwargs["diff_method"] == "adjoint": pytest.skip("trainable hamiltonians not supported with adjoint") - if qml.operation.active_new_opmath(): - pytest.skip("parameter shift derivatives do not yet support sums.") coeffs1 = pnp.array([0.1, 0.2, 0.3], requires_grad=True) coeffs2 = pnp.array([0.7], requires_grad=True) diff --git a/tests/interfaces/test_jax.py b/tests/interfaces/test_jax.py index 12a354eccb3..8222804bcfd 100644 --- a/tests/interfaces/test_jax.py +++ b/tests/interfaces/test_jax.py @@ -730,7 +730,6 @@ def cost_fn(x): @pytest.mark.parametrize("execute_kwargs, shots, device_name", test_matrix) -@pytest.mark.usefixtures("use_legacy_and_new_opmath") class TestHamiltonianWorkflows: """Test that tapes ending with expectations of Hamiltonians provide correct results and gradients""" @@ -744,13 +743,11 @@ def cost_fn(self, execute_kwargs, shots, device_name, seed): def _cost_fn(weights, coeffs1, coeffs2): obs1 = [qml.PauliZ(0), qml.PauliZ(0) @ qml.PauliX(1), qml.PauliY(0)] H1 = qml.Hamiltonian(coeffs1, obs1) - if qml.operation.active_new_opmath(): - H1 = qml.pauli.pauli_sentence(H1).operation() + H1 = qml.pauli.pauli_sentence(H1).operation() obs2 = [qml.PauliZ(0)] H2 = qml.Hamiltonian(coeffs2, obs2) - if qml.operation.active_new_opmath(): - H2 = qml.pauli.pauli_sentence(H2).operation() + H2 = qml.pauli.pauli_sentence(H2).operation() with qml.queuing.AnnotatedQueue() as q: qml.RX(weights[0], wires=0) @@ -795,12 +792,9 @@ def cost_fn_jacobian(weights, coeffs1, coeffs2): ] ) - def test_multiple_hamiltonians_not_trainable(self, execute_kwargs, cost_fn, shots): + def test_multiple_hamiltonians_not_trainable(self, cost_fn, shots): """Test hamiltonian with no trainable parameters.""" - if execute_kwargs["diff_method"] == "adjoint" and not qml.operation.active_new_opmath(): - pytest.skip("adjoint differentiation does not suppport hamiltonians.") - coeffs1 = jnp.array([0.1, 0.2, 0.3]) coeffs2 = jnp.array([0.7]) weights = jnp.array([0.4, 0.5]) @@ -821,12 +815,11 @@ def test_multiple_hamiltonians_not_trainable(self, execute_kwargs, cost_fn, shot else: assert np.allclose(res, expected, atol=atol_for_shots(shots), rtol=0) + @pytest.mark.xfail(reason="parameter shift derivatives do not yet support sums.") def test_multiple_hamiltonians_trainable(self, execute_kwargs, cost_fn, shots): """Test hamiltonian with trainable parameters.""" if execute_kwargs["diff_method"] == "adjoint": pytest.skip("trainable hamiltonians not supported with adjoint") - if qml.operation.active_new_opmath(): - pytest.skip("parameter shift derivatives do not yet support sums.") coeffs1 = jnp.array([0.1, 0.2, 0.3]) coeffs2 = jnp.array([0.7]) diff --git a/tests/interfaces/test_tensorflow.py b/tests/interfaces/test_tensorflow.py index edd43c75c3c..c13268dc6e8 100644 --- a/tests/interfaces/test_tensorflow.py +++ b/tests/interfaces/test_tensorflow.py @@ -729,7 +729,6 @@ def cost_fn(x): @pytest.mark.parametrize("execute_kwargs, shots, device_name", test_matrix) -@pytest.mark.usefixtures("use_legacy_and_new_opmath") class TestHamiltonianWorkflows: """Test that tapes ending with expectations of Hamiltonians provide correct results and gradients""" @@ -743,13 +742,11 @@ def cost_fn(self, execute_kwargs, shots, device_name, seed): def _cost_fn(weights, coeffs1, coeffs2): obs1 = [qml.PauliZ(0), qml.PauliZ(0) @ qml.PauliX(1), qml.PauliY(0)] H1 = qml.Hamiltonian(coeffs1, obs1) - if qml.operation.active_new_opmath(): - H1 = qml.pauli.pauli_sentence(H1).operation() + H1 = qml.pauli.pauli_sentence(H1).operation() obs2 = [qml.PauliZ(0)] H2 = qml.Hamiltonian(coeffs2, obs2) - if qml.operation.active_new_opmath(): - H2 = qml.pauli.pauli_sentence(H2).operation() + H2 = qml.pauli.pauli_sentence(H2).operation() with qml.queuing.AnnotatedQueue() as q: qml.RX(weights[0], wires=0) @@ -794,9 +791,6 @@ def cost_fn_jacobian(weights, coeffs1, coeffs2): def test_multiple_hamiltonians_not_trainable(self, execute_kwargs, cost_fn, shots): """Test hamiltonian with no trainable parameters.""" - if execute_kwargs["diff_method"] == "adjoint" and not qml.operation.active_new_opmath(): - pytest.skip("adjoint differentiation does not suppport hamiltonians.") - device_vjp = execute_kwargs.get("device_vjp", False) coeffs1 = tf.constant([0.1, 0.2, 0.3], dtype=tf.float64) @@ -813,12 +807,11 @@ def test_multiple_hamiltonians_not_trainable(self, execute_kwargs, cost_fn, shot expected = self.cost_fn_jacobian(weights, coeffs1, coeffs2)[:, :2] assert np.allclose(jac, expected, atol=atol_for_shots(shots), rtol=0) + @pytest.mark.xfail(reason="parameter shift derivatives do not yet support sums.") def test_multiple_hamiltonians_trainable(self, cost_fn, execute_kwargs, shots): """Test hamiltonian with trainable parameters.""" if execute_kwargs["diff_method"] == "adjoint": pytest.skip("trainable hamiltonians not supported with adjoint") - if qml.operation.active_new_opmath(): - pytest.skip("parameter shift derivatives do not yet support sums.") coeffs1 = tf.Variable([0.1, 0.2, 0.3], dtype=tf.float64) coeffs2 = tf.Variable([0.7], dtype=tf.float64) diff --git a/tests/interfaces/test_torch.py b/tests/interfaces/test_torch.py index 82814821222..a926a9ddcb6 100644 --- a/tests/interfaces/test_torch.py +++ b/tests/interfaces/test_torch.py @@ -758,7 +758,6 @@ def cost_fn(x): @pytest.mark.parametrize("execute_kwargs, shots, device_name", test_matrix) -@pytest.mark.usefixtures("use_legacy_and_new_opmath") class TestHamiltonianWorkflows: """Test that tapes ending with expectations of Hamiltonians provide correct results and gradients""" @@ -771,13 +770,11 @@ def cost_fn(self, execute_kwargs, shots, device_name, seed): def _cost_fn(weights, coeffs1, coeffs2): obs1 = [qml.PauliZ(0), qml.PauliZ(0) @ qml.PauliX(1), qml.PauliY(0)] H1 = qml.Hamiltonian(coeffs1, obs1) - if qml.operation.active_new_opmath(): - H1 = qml.pauli.pauli_sentence(H1).operation() + H1 = qml.pauli.pauli_sentence(H1).operation() obs2 = [qml.PauliZ(0)] H2 = qml.Hamiltonian(coeffs2, obs2) - if qml.operation.active_new_opmath(): - H2 = qml.pauli.pauli_sentence(H2).operation() + H2 = qml.pauli.pauli_sentence(H2).operation() with qml.queuing.AnnotatedQueue() as q: qml.RX(weights[0], wires=0) @@ -827,12 +824,9 @@ def cost_fn_jacobian(weights, coeffs1, coeffs2): ] ) - def test_multiple_hamiltonians_not_trainable(self, execute_kwargs, cost_fn, shots): + def test_multiple_hamiltonians_not_trainable(self, cost_fn, shots): """Test hamiltonian with no trainable parameters.""" - if execute_kwargs["diff_method"] == "adjoint" and not qml.operation.active_new_opmath(): - pytest.skip("adjoint differentiation does not suppport hamiltonians.") - coeffs1 = torch.tensor([0.1, 0.2, 0.3], requires_grad=False) coeffs2 = torch.tensor([0.7], requires_grad=False) weights = torch.tensor([0.4, 0.5], requires_grad=True) @@ -853,12 +847,11 @@ def test_multiple_hamiltonians_not_trainable(self, execute_kwargs, cost_fn, shot else: assert torch.allclose(res, expected, atol=atol_for_shots(shots), rtol=0) + @pytest.mark.xfail(reason="parameter shift derivatives do not yet support sums.") def test_multiple_hamiltonians_trainable(self, execute_kwargs, cost_fn, shots): """Test hamiltonian with trainable parameters.""" if execute_kwargs["diff_method"] == "adjoint": pytest.skip("trainable hamiltonians not supported with adjoint") - if qml.operation.active_new_opmath(): - pytest.skip("parameter shift derivatives do not yet support sums.") coeffs1 = torch.tensor([0.1, 0.2, 0.3], requires_grad=True) coeffs2 = torch.tensor([0.7], requires_grad=True) diff --git a/tests/measurements/test_classical_shadow.py b/tests/measurements/test_classical_shadow.py index 7d019093628..5290224c490 100644 --- a/tests/measurements/test_classical_shadow.py +++ b/tests/measurements/test_classical_shadow.py @@ -604,11 +604,7 @@ def test_non_pauli_error(self): """Test that an error is raised when a non-Pauli observable is passed""" circuit = hadamard_circuit(3) - legacy_msg = "Observable must be a linear combination of Pauli observables" - new_opmath_msg = "Observable must have a valid pauli representation." - msg = new_opmath_msg if qml.operation.active_new_opmath() else legacy_msg - - with pytest.raises(ValueError, match=msg): + with pytest.raises(ValueError, match="Observable must have a valid pauli representation."): circuit(qml.Hadamard(0) @ qml.Hadamard(2)) diff --git a/tests/ops/functions/conftest.py b/tests/ops/functions/conftest.py index 6f174cafb51..fd1a56566e1 100644 --- a/tests/ops/functions/conftest.py +++ b/tests/ops/functions/conftest.py @@ -16,14 +16,13 @@ Generates parametrizations of operators to test in test_assert_valid.py. """ -import warnings from inspect import getmembers, isclass import numpy as np import pytest import pennylane as qml -from pennylane.operation import Channel, Observable, Operation, Operator, Tensor +from pennylane.operation import Channel, Observable, Operation, Operator from pennylane.ops.op_math.adjoint import Adjoint, AdjointObs, AdjointOperation, AdjointOpObs from pennylane.ops.op_math.pow import PowObs, PowOperation, PowOpObs @@ -52,7 +51,6 @@ (qml.BlockEncode([[0.1, 0.2], [0.3, 0.4]], wires=[0, 1]), {"skip_differentiation": True}), (qml.adjoint(qml.PauliX(0)), {}), (qml.adjoint(qml.RX(1.1, 0)), {}), - (Tensor(qml.PauliX(0), qml.PauliX(1)), {}), (qml.ops.LinearCombination([1.1, 2.2], [qml.PauliX(0), qml.PauliZ(0)]), {}), (qml.s_prod(1.1, qml.RX(1.1, 0)), {}), (qml.prod(qml.PauliX(0), qml.PauliY(1), qml.PauliZ(0)), {}), @@ -69,17 +67,6 @@ ] """Valid operator instances that could not be auto-generated.""" -with warnings.catch_warnings(): - warnings.filterwarnings("ignore", "qml.ops.Hamiltonian uses", qml.PennyLaneDeprecationWarning) - _INSTANCES_TO_TEST.append( - ( - qml.operation.convert_to_legacy_H( - qml.Hamiltonian([1.1, 2.2], [qml.PauliX(0), qml.PauliZ(0)]) - ), - {}, - ) - ) - _INSTANCES_TO_FAIL = [ ( diff --git a/tests/ops/functions/test_assert_valid.py b/tests/ops/functions/test_assert_valid.py index 70cea786063..d67c71d5511 100644 --- a/tests/ops/functions/test_assert_valid.py +++ b/tests/ops/functions/test_assert_valid.py @@ -402,11 +402,7 @@ def test_generated_list_of_ops(class_to_validate, str_wires): def test_explicit_list_of_ops(valid_instance_and_kwargs): """Test the validity of operators that could not be auto-generated.""" valid_instance, kwargs = valid_instance_and_kwargs - if valid_instance.name == "Hamiltonian": - with qml.operation.disable_new_opmath_cm(warn=False): - assert_valid(valid_instance, **kwargs) - else: - assert_valid(valid_instance, **kwargs) + assert_valid(valid_instance, **kwargs) @pytest.mark.jax diff --git a/tests/ops/functions/test_bind_new_parameters.py b/tests/ops/functions/test_bind_new_parameters.py index 94872282052..00dc631b64a 100644 --- a/tests/ops/functions/test_bind_new_parameters.py +++ b/tests/ops/functions/test_bind_new_parameters.py @@ -14,14 +14,12 @@ """ This module contains unit tests for ``qml.bind_parameters``. """ -import warnings import numpy as np import pytest from gate_data import GELL_MANN, I, X, Y, Z import pennylane as qml -from pennylane.operation import Tensor from pennylane.ops.functions import bind_new_parameters @@ -185,70 +183,6 @@ def test_controlled_sequence(): qml.assert_equal(new_op.base, qml.RX(0.5, wires=3)) -with warnings.catch_warnings(): - warnings.filterwarnings("ignore", "qml.ops.Hamiltonian uses", qml.PennyLaneDeprecationWarning) - warnings.filterwarnings("ignore", "qml.operation.Tensor uses", qml.PennyLaneDeprecationWarning) - - TEST_BIND_LEGACY_HAMILTONIAN = [ - ( - qml.ops.Hamiltonian( - [1.1, 2.1, 3.1], - [Tensor(qml.PauliZ(0), qml.PauliX(1)), qml.Hadamard(1), qml.PauliY(0)], - ), - [1.2, 2.2, 3.2], - qml.ops.Hamiltonian( - [1.2, 2.2, 3.2], - [Tensor(qml.PauliZ(0), qml.PauliX(1)), qml.Hadamard(1), qml.PauliY(0)], - ), - ), - ( - qml.ops.Hamiltonian([1.6, -1], [qml.Hermitian(X, wires=1), qml.PauliX(1)]), - [-1, 1.6], - qml.ops.Hamiltonian([-1, 1.6], [qml.Hermitian(X, wires=1), qml.PauliX(1)]), - ), - ] - - TEST_BIND_LEGACY_TENSOR = [ - ( - Tensor(qml.Hermitian(Y, wires=0), qml.PauliZ(1)), - [X], - Tensor(qml.Hermitian(X, wires=0), qml.PauliZ(1)), - ), - ( - Tensor(qml.Hermitian(qml.math.kron(X, Z), wires=[0, 1]), qml.Hermitian(I, wires=2)), - [qml.math.kron(I, I), Z], - Tensor(qml.Hermitian(qml.math.kron(I, I), wires=[0, 1]), qml.Hermitian(Z, wires=2)), - ), - (Tensor(qml.PauliZ(0), qml.PauliX(1)), [], Tensor(qml.PauliZ(0), qml.PauliX(1))), - ] - - -@pytest.mark.parametrize( - "H, new_coeffs, expected_H", - TEST_BIND_LEGACY_HAMILTONIAN, -) -def test_hamiltonian_legacy_opmath(H, new_coeffs, expected_H): - """Test that `bind_new_parameters` with `Hamiltonian` returns a new - operator with the new parameters without mutating the original - operator.""" - new_H = bind_new_parameters(H, new_coeffs) - - qml.assert_equal(new_H, expected_H) - assert new_H is not H - - -@pytest.mark.parametrize("op, new_params, expected_op", TEST_BIND_LEGACY_TENSOR) -def test_tensor(op, new_params, expected_op): - """Test that `bind_new_parameters` with `Tensor` returns a new - operator with the new parameters without mutating the original - operator.""" - new_op = bind_new_parameters(op, new_params) - - qml.assert_equal(new_op, expected_op) - assert new_op is not op - assert all(n_obs is not obs for n_obs, obs in zip(new_op.obs, op.obs)) - - TEST_BIND_LINEARCOMBINATION = [ ( # LinearCombination with only data being the coeffs qml.ops.LinearCombination( @@ -340,7 +274,6 @@ def test_linear_combination(H, new_coeffs, expected_H): assert new_H is not H -@pytest.mark.usefixtures("use_legacy_and_new_opmath") def test_hamiltonian_grouping_indices(): """Test that bind_new_parameters with a Hamiltonian preserves the grouping indices.""" H = qml.Hamiltonian([1.0, 2.0], [qml.PauliX(0), qml.PauliX(1)]) diff --git a/tests/ops/functions/test_commutator.py b/tests/ops/functions/test_commutator.py index 98585e501c2..2ca0285c3aa 100644 --- a/tests/ops/functions/test_commutator.py +++ b/tests/ops/functions/test_commutator.py @@ -19,7 +19,7 @@ import pennylane as qml from pennylane.operation import Operator -from pennylane.ops import SProd, Sum +from pennylane.ops import SProd from pennylane.pauli import PauliSentence, PauliWord X, Y, Z, Id = qml.PauliX, qml.PauliY, qml.PauliZ, qml.Identity @@ -46,31 +46,6 @@ def _id(p): return p -class TestLegacySupport: - """Test support for legacy operator classes like Tensor and Hamiltonian""" - - def test_Hamiltonian_single(self): - """Test that Hamiltonians get transformed to new operator classes and return the correct result""" - H1 = qml.Hamiltonian([1.0], [qml.PauliX(0)]) - H2 = qml.Hamiltonian([1.0], [qml.PauliY(0)]) - res = qml.commutator(H1, H2) - true_res = qml.s_prod(2j, qml.PauliZ(0)) - assert isinstance(res, SProd) - assert true_res == res - - def test_Hamiltonian_sum(self): - """Test that Hamiltonians with Tensors and sums get transformed to new operator classes and return the correct result""" - H1 = qml.Hamiltonian([1.0], [qml.PauliX(0) @ qml.PauliX(1)]) - H2 = qml.Hamiltonian([1.0], [qml.PauliY(0) + qml.PauliY(1)]) - true_res = qml.sum( - qml.s_prod(2j, qml.PauliZ(0) @ qml.PauliX(1)), - qml.s_prod(2j, qml.PauliX(0) @ qml.PauliZ(1)), - ) - res = qml.commutator(H1, H2).simplify() - assert isinstance(res, Sum) - qml.assert_equal(true_res, res) - - def test_alias(): """Test that the alias qml.comm() works as expected""" res1 = qml.comm(X0, Y0) diff --git a/tests/ops/functions/test_dot.py b/tests/ops/functions/test_dot.py index 26ed43d9071..48216986355 100644 --- a/tests/ops/functions/test_dot.py +++ b/tests/ops/functions/test_dot.py @@ -307,9 +307,10 @@ def test_dot_returns_pauli_sentence(self): def test_coeffs_and_ops(self): """Test that the coefficients and operators of the returned PauliSentence are correct.""" ps = qml.dot(coeffs0, ops0, pauli=True) - h = ps.hamiltonian() - assert qml.math.allequal(h.coeffs, coeffs0) - for _op1, _op2 in zip(h.ops, ops0): + h = ps.operation() + hcoeffs, hops = h.terms() + assert qml.math.allequal(hcoeffs, coeffs0) + for _op1, _op2 in zip(hops, ops0): qml.assert_equal(_op1, _op2) def test_dot_simplifies_linear_combination(self): @@ -318,15 +319,15 @@ def test_dot_simplifies_linear_combination(self): coeffs=[0.12, 1.2, 12], ops=[qml.PauliX(0), qml.PauliX(0), qml.PauliX(0)], pauli=True ) assert len(ps) == 1 - h = ps.hamiltonian() - assert len(h.ops) == 1 - qml.assert_equal(h.ops[0], qml.PauliX(0)) + h = ps.operation() + assert isinstance(h, qml.ops.SProd) + qml.assert_equal(h.base, qml.PauliX(0)) def test_dot_returns_hamiltonian_simplified(self): """Test that hamiltonian computed from the PauliSentence created by the dot function is equal to the simplified hamiltonian.""" ps = qml.dot(coeffs0, ops0, pauli=True) - h_ps = ps.hamiltonian() + h_ps = ps.operation() h = qml.Hamiltonian(coeffs0, ops0) h.simplify() qml.assert_equal(h_ps, h) diff --git a/tests/ops/functions/test_eigvals.py b/tests/ops/functions/test_eigvals.py index 569e9cc1969..edb9e774b40 100644 --- a/tests/ops/functions/test_eigvals.py +++ b/tests/ops/functions/test_eigvals.py @@ -118,13 +118,6 @@ def test_ctrl(self): expected = np.linalg.eigvals(qml.matrix(qml.CNOT(wires=[0, 1]))) assert np.allclose(np.sort(res), np.sort(expected)) - @pytest.mark.usefixtures("legacy_opmath_only") - def test_tensor_product_legacy_opmath(self): - """Test a tensor product""" - res = qml.eigvals(qml.PauliX(0) @ qml.Identity(1) @ qml.PauliZ(1)) - expected = reduce(np.kron, [[1, -1], [1, 1], [1, -1]]) - assert np.allclose(res, expected) - def test_tensor_product(self): """Test a tensor product""" res = qml.eigvals(qml.prod(qml.PauliX(0), qml.Identity(1), qml.PauliZ(1), lazy=False)) @@ -140,17 +133,6 @@ def test_hamiltonian(self): expected = np.linalg.eigvalsh(reduce(np.kron, [Z, Y]) - 0.5 * reduce(np.kron, [I, X])) assert np.allclose(res, expected) - @pytest.mark.usefixtures("legacy_opmath_only") - def test_hamiltonian_legacy_opmath(self): - """Test that the matrix of a Hamiltonian is correctly returned""" - ham = qml.PauliZ(0) @ qml.PauliY(1) - 0.5 * qml.PauliX(1) - - with pytest.warns(UserWarning, match="the eigenvalues will be computed numerically"): - res = qml.eigvals(ham) - - expected = np.linalg.eigvalsh(reduce(np.kron, [Z, Y]) - 0.5 * reduce(np.kron, [I, X])) - assert np.allclose(res, expected) - @pytest.mark.xfail( reason="This test will fail because Hamiltonians are not queued to tapes yet!" ) diff --git a/tests/ops/functions/test_equal.py b/tests/ops/functions/test_equal.py index 6ed3a953b7e..7151b357050 100644 --- a/tests/ops/functions/test_equal.py +++ b/tests/ops/functions/test_equal.py @@ -236,23 +236,6 @@ qml.Hamiltonian([1, 1], [qml.PauliX("b"), qml.PauliZ("a")]), False, ), - (qml.Hamiltonian([1], [qml.PauliZ(0) @ qml.PauliX(1)]), qml.PauliZ(0) @ qml.PauliX(1), True), - (qml.Hamiltonian([1], [qml.PauliZ(0)]), qml.PauliZ(0), True), - ( - qml.Hamiltonian( - [1, 1, 1], - [ - qml.Hermitian(np.array([[1, 0], [0, -1]]), "b") @ qml.Identity(7), - qml.PauliZ(3), - qml.Identity(1.2), - ], - ), - qml.Hamiltonian( - [1, 1, 1], - [qml.Hermitian(np.array([[1, 0], [0, -1]]), "b"), qml.PauliZ(3), qml.Identity(1.2)], - ), - True, - ), ( qml.Hamiltonian([1, 1], [qml.PauliZ(3) @ qml.Identity(1.2), qml.PauliZ(3)]), qml.Hamiltonian([2], [qml.PauliZ(3)]), @@ -260,53 +243,6 @@ ), ] -equal_tensors = [ - (qml.PauliX(0) @ qml.PauliY(1), qml.PauliY(1) @ qml.PauliX(0), True), - (qml.PauliX(0) @ qml.Identity(1) @ qml.PauliZ(2), qml.PauliX(0) @ qml.PauliZ(2), True), - (qml.PauliX(0) @ qml.Identity(2) @ qml.PauliZ(1), qml.PauliX(0) @ qml.PauliZ(2), False), - (qml.PauliX(0) @ qml.PauliZ(1), qml.PauliX(0) @ qml.PauliZ(2), False), - (qml.PauliX("a") @ qml.PauliZ("b"), qml.PauliX("a") @ qml.PauliZ("b"), True), - (qml.PauliX("a") @ qml.PauliZ("b"), qml.PauliX("c") @ qml.PauliZ("d"), False), - (qml.PauliX("a") @ qml.PauliZ("b"), qml.PauliX("b") @ qml.PauliZ("a"), False), - (qml.PauliX(1.1) @ qml.PauliZ(1.2), qml.PauliX(1.1) @ qml.PauliZ(1.2), True), - (qml.PauliX(1.1) @ qml.PauliZ(1.2), qml.PauliX(1.2) @ qml.PauliZ(0.9), False), -] - -equal_hamiltonians_and_tensors = [ - (qml.Hamiltonian([1], [qml.PauliX(0) @ qml.PauliY(1)]), qml.PauliY(1) @ qml.PauliX(0), True), - ( - qml.Hamiltonian( - [0.5, 0.5], - [qml.PauliZ(0) @ qml.PauliY(1), qml.PauliY(1) @ qml.PauliZ(0) @ qml.Identity("a")], - ), - qml.PauliZ(0) @ qml.PauliY(1), - True, - ), - (qml.Hamiltonian([1], [qml.PauliX(0) @ qml.PauliY(1)]), qml.PauliX(0) @ qml.PauliY(1), True), - (qml.Hamiltonian([2], [qml.PauliX(0) @ qml.PauliY(1)]), qml.PauliX(0) @ qml.PauliY(1), False), - (qml.Hamiltonian([1], [qml.PauliX(0) @ qml.PauliY(1)]), qml.PauliX(4) @ qml.PauliY(1), False), - ( - qml.Hamiltonian([1], [qml.PauliX("a") @ qml.PauliZ("b")]), - qml.PauliX("a") @ qml.PauliZ("b"), - True, - ), - ( - qml.Hamiltonian([1], [qml.PauliX("a") @ qml.PauliZ("b")]), - qml.PauliX("b") @ qml.PauliZ("a"), - False, - ), - ( - qml.Hamiltonian([1], [qml.PauliX(1.2) @ qml.PauliZ(0.2)]), - qml.PauliX(1.2) @ qml.PauliZ(0.2), - True, - ), - ( - qml.Hamiltonian([1], [qml.PauliX(1.2) @ qml.PauliZ(0.2)]), - qml.PauliX(1.3) @ qml.PauliZ(2), - False, - ), -] - equal_pauli_operators = [ (qml.PauliX(0), qml.PauliX(0), True), (qml.PauliY("a"), qml.PauliY("a"), True), @@ -318,10 +254,6 @@ (qml.PauliY("a"), qml.PauliX("a"), False), (qml.PauliZ(0.3), qml.PauliY(0.3), False), (qml.PauliZ(0), qml.RX(1.23, 0), False), - (qml.Hamiltonian([1], [qml.PauliX("a")]), qml.PauliX("a"), True), - (qml.Hamiltonian([1], [qml.PauliX("a")]), qml.PauliX("b"), False), - (qml.Hamiltonian([1], [qml.PauliX(1.2)]), qml.PauliX(1.2), True), - (qml.Hamiltonian([1], [qml.PauliX(1.2)]), qml.PauliX(1.3), False), ] @@ -354,6 +286,29 @@ def _(op1: RandomType, op2, **_): class TestEqual: + + def test_identity_equal(self): + """Test that comparing two Identities always returns True regardless of wires""" + I1 = qml.Identity() + I2 = qml.Identity(wires=[-1]) + I3 = qml.Identity(wires=[0, 1, 2, 3]) + I4 = qml.Identity(wires=["a", "b"]) + + assert qml.equal(I1, I2) + assert qml.equal(I1, I3) + assert qml.equal(I1, I4) + assert qml.equal(I2, I3) + assert qml.equal(I2, I4) + assert qml.equal(I3, I4) + + @pytest.mark.parametrize(("op1", "op2", "res"), equal_pauli_operators) + def test_pauli_operator_equals(self, op1, op2, res): + """Tests that equality can be checked between PauliX/Y/Z operators, and between Pauli operators + and Hamiltonians""" + + assert qml.equal(op1, op2) == qml.equal(op2, op1) + assert qml.equal(op1, op2) == res + @pytest.mark.parametrize("ops", PARAMETRIZED_OPERATIONS_COMBINATIONS) def test_equal_simple_diff_op(self, ops): """Test different operators return False""" @@ -1464,113 +1419,11 @@ def test_shadow_expval_list_versus_operator(self): assert not qml.equal(m1, m2) -@pytest.mark.usefixtures("legacy_opmath_only") # TODO update qml.equal with new opmath -class TestObservablesComparisons: - """Tests comparisons between Hamiltonians, Tensors and PauliX/Y/Z operators""" +def test_unsupported_object_type_not_implemented(): + dev = qml.device("default.qubit", wires=1) - def test_identity_equal(self): - """Test that comparing two Identities always returns True regardless of wires""" - I1 = qml.Identity() - I2 = qml.Identity(wires=[-1]) - I3 = qml.Identity(wires=[0, 1, 2, 3]) - I4 = qml.Identity(wires=["a", "b"]) - - assert qml.equal(I1, I2) - assert qml.equal(I1, I3) - assert qml.equal(I1, I4) - assert qml.equal(I2, I3) - assert qml.equal(I2, I4) - assert qml.equal(I3, I4) - - @pytest.mark.parametrize(("H1", "H2", "res"), equal_hamiltonians) - def test_hamiltonian_equal(self, H1, H2, res): - """Tests that equality can be checked between Hamiltonians""" - if not qml.operation.active_new_opmath(): - H1 = qml.operation.convert_to_legacy_H(H1) - H2 = qml.operation.convert_to_legacy_H(H2) - - assert qml.equal(H1, H2) == qml.equal(H2, H1) - assert qml.equal(H1, H2) == res - if not res: - error_message_pattern = re.compile(r"'([^']+)' and '([^']+)' are not the same") - with pytest.raises(AssertionError, match=error_message_pattern): - assert_equal(H1, H2) - - @pytest.mark.parametrize(("T1", "T2", "res"), equal_tensors) - def test_tensors_equal(self, T1, T2, res): - """Tests that equality can be checked between Tensors""" - assert qml.equal(T1, T2) == qml.equal(T2, T1) - assert qml.equal(T1, T2) == res - - def test_tensors_not_equal(self): - """Tensors are not equal because of different observable data""" - op1 = qml.operation.Tensor(qml.X(0), qml.Y(1)) - op2 = qml.operation.Tensor(qml.Y(0), qml.X(1)) - with pytest.raises(AssertionError, match="have different _obs_data outputs"): - assert_equal(op1, op2) - - @pytest.mark.parametrize(("H", "T", "res"), equal_hamiltonians_and_tensors) - def test_hamiltonians_and_tensors_equal(self, H, T, res): - """Tests that equality can be checked between a Hamiltonian and a Tensor""" - if not qml.operation.active_new_opmath(): - H = qml.operation.convert_to_legacy_H(H) - T = qml.operation.Tensor(*T.operands) - - assert qml.equal(H, T) == qml.equal(T, H) - assert qml.equal(H, T) == res - - @pytest.mark.parametrize(("op1", "op2", "res"), equal_pauli_operators) - def test_pauli_operator_equals(self, op1, op2, res): - """Tests that equality can be checked between PauliX/Y/Z operators, and between Pauli operators and Hamiltonians""" - if not qml.operation.active_new_opmath(): - op1 = qml.operation.convert_to_legacy_H(op1) - op2 = qml.operation.convert_to_legacy_H(op2) - - assert qml.equal(op1, op2) == qml.equal(op2, op1) - assert qml.equal(op1, op2) == res - - def test_hamiltonian_and_operation_not_equal(self): - """Tests that comparing a Hamiltonian with an Operator that is not an Observable returns False""" - op1 = qml.Hamiltonian([1, 1], [qml.PauliX(0), qml.PauliY(0)]) - op2 = qml.RX(1.2, 0) - assert qml.equal(op1, op2) is False - assert qml.equal(op2, op1) is False - with pytest.raises(AssertionError, match="is not of type Observable"): - assert_equal(op1, op2) - - def test_tensor_and_operation_not_equal(self): - """Tests that comparing a Tensor with an Operator that is not an Observable returns False""" - op1 = qml.PauliX(0) @ qml.PauliY(1) - op2 = qml.RX(1.2, 0) - assert qml.equal(op1, op2) is False - assert qml.equal(op2, op1) is False - with pytest.raises(AssertionError, match="is not of type Observable"): - assert_equal(op1, op2) - - def test_tensor_and_observable_not_equal(self): - """Tests that comparing a Tensor with an Observable that is not a Tensor returns False""" - op1 = qml.PauliX(0) @ qml.PauliY(1) - op2 = qml.Z(0) - assert qml.equal(op1, op2) is False - assert qml.equal(op2, op1) is False - with pytest.raises(AssertionError, match="is of type "): - assert_equal(op1, op2) - - def test_tensor_and_unsupported_observable_returns_false(self): - """Tests that trying to compare a Tensor to something other than another Tensor or a Hamiltonian returns False""" - op1 = qml.PauliX(0) @ qml.PauliY(1) - op2 = qml.Hermitian([[0, 1], [1, 0]], 0) - - assert not qml.equal(op1, op2) - error_message_pattern = re.compile(r"'([^']+)' and '([^']+)' are not the same") - with pytest.raises(AssertionError, match=error_message_pattern): - assert_equal(op1, op2) - - def test_unsupported_object_type_not_implemented(self): - dev = qml.device("default.qubit", wires=1) - - with pytest.raises(NotImplementedError, match="Comparison of"): - qml.equal(dev, dev) + with pytest.raises(NotImplementedError, match="Comparison of"): + qml.equal(dev, dev) class TestSymbolicOpComparison: @@ -2141,7 +1994,6 @@ def test_s_prod_base_op_comparison_with_trainability(self): assert not qml.equal(op1, op2, check_interface=False, check_trainability=True) -@pytest.mark.usefixtures("new_opmath_only") class TestProdComparisons: """Tests comparisons between Prod operators""" @@ -2189,6 +2041,25 @@ class TestProdComparisons: ), ] + @pytest.mark.parametrize( + ("T1", "T2", "res"), + [ + (qml.PauliX(0) @ qml.PauliY(1), qml.PauliY(1) @ qml.PauliX(0), True), + (qml.PauliX(0) @ qml.Identity(1) @ qml.PauliZ(2), qml.PauliX(0) @ qml.PauliZ(2), True), + (qml.PauliX(0) @ qml.Identity(2) @ qml.PauliZ(1), qml.PauliX(0) @ qml.PauliZ(2), False), + (qml.PauliX(0) @ qml.PauliZ(1), qml.PauliX(0) @ qml.PauliZ(2), False), + (qml.PauliX("a") @ qml.PauliZ("b"), qml.PauliX("a") @ qml.PauliZ("b"), True), + (qml.PauliX("a") @ qml.PauliZ("b"), qml.PauliX("c") @ qml.PauliZ("d"), False), + (qml.PauliX("a") @ qml.PauliZ("b"), qml.PauliX("b") @ qml.PauliZ("a"), False), + (qml.PauliX(1.1) @ qml.PauliZ(1.2), qml.PauliX(1.1) @ qml.PauliZ(1.2), True), + (qml.PauliX(1.1) @ qml.PauliZ(1.2), qml.PauliX(1.2) @ qml.PauliZ(0.9), False), + ], + ) + def test_prods_equal(self, T1, T2, res): + """Tests that equality can be checked between Prods""" + assert qml.equal(T1, T2) == qml.equal(T2, T1) + assert qml.equal(T1, T2) == res + def test_non_commuting_order_swap_not_equal(self): """Test that changing the order of non-commuting operators is not equal""" op1 = qml.prod(qml.PauliX(0), qml.PauliY(0)) @@ -2247,7 +2118,6 @@ def test_prod_global_phase(self): assert qml.equal(p1, p2) -@pytest.mark.usefixtures("new_opmath_only") class TestSumComparisons: """Tests comparisons between Sum operators""" @@ -2386,6 +2256,20 @@ def test_sum_global_phase(self): op2 = qml.sum(qml.GlobalPhase(np.pi), qml.X(0)) assert qml.equal(op1, op2) + @pytest.mark.parametrize(("H1", "H2", "res"), equal_hamiltonians) + def test_hamiltonian_equal(self, H1, H2, res): + """Tests that equality can be checked between LinearCombinations""" + + assert qml.equal(H1, H2) == qml.equal(H2, H1) + assert qml.equal(H1, H2) == res + if not res: + if len(H1) != len(H2): + error_message = "op1 and op2 have different number of operands" + else: + error_message = re.compile(r"op1 and op2 have different operands") + with pytest.raises(AssertionError, match=error_message): + assert_equal(H1, H2) + def f1(p, t): return np.polyval(p, t) diff --git a/tests/ops/functions/test_generator.py b/tests/ops/functions/test_generator.py index aa91323fc3e..9c997204632 100644 --- a/tests/ops/functions/test_generator.py +++ b/tests/ops/functions/test_generator.py @@ -341,28 +341,12 @@ class TestObservableReturn: """Tests for format="observable". This format preserves the initial generator encoded in the operator.""" - @pytest.mark.usefixtures("legacy_opmath_only") - def test_observable_legacy_opmath(self): - """Test a generator that returns a single observable is correct with opmath disabled""" - gen = qml.generator(ObservableOp, format="observable")(0.5, wires=0) - assert gen.name == "Hamiltonian" - assert gen.compare(ObservableOp(0.5, wires=0).generator()) - - @pytest.mark.usefixtures("new_opmath_only") def test_observable(self): """Test a generator that returns a single observable is correct""" gen = qml.generator(ObservableOp, format="observable")(0.5, wires=0) assert gen.name == "SProd" qml.assert_equal(gen, ObservableOp(0.5, wires=0).generator()) - @pytest.mark.usefixtures("legacy_opmath_only") - def test_tensor_observable_legacy_opmath(self): - """Test a generator that returns a tensor observable is correct with opmath disabled""" - gen = qml.generator(TensorOp, format="observable")(0.5, wires=[0, 1]) - assert gen.name == "Hamiltonian" - assert gen.compare(TensorOp(0.5, wires=[0, 1]).generator()) - - @pytest.mark.usefixtures("new_opmath_only") def test_tensor_observable(self): """Test a generator that returns a tensor observable is correct""" gen = qml.generator(TensorOp, format="observable")(0.5, wires=[0, 1]) @@ -390,11 +374,9 @@ def test_sparse_hamiltonian(self): assert np.all(gen.parameters[0].toarray() == SparseOp.H.toarray()) -@pytest.mark.usefixtures("use_legacy_and_new_opmath") class TestHamiltonianReturn: """Tests for format="hamiltonian". This format always returns the generator - as a Hamiltonian (either a qml.ops.Hamiltonian or a qml.ops.LinearCombination - depending on whether new_opmath is enabled.)""" + as a qml.ops.LinearCombination.""" def test_observable_no_coeff(self): """Test a generator that returns an observable with no coefficient is correct""" @@ -408,7 +390,8 @@ def test_observable(self): assert isinstance(gen, qml.Hamiltonian) assert gen.compare(ObservableOp(0.5, wires=0).generator()) - @pytest.mark.usefixtures("legacy_opmath_only") + # removed fixture to only run with legacy opmath - not clear why it was added, + # but we'll see once things are tidied up enough for tests to run 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]) diff --git a/tests/ops/functions/test_is_commuting.py b/tests/ops/functions/test_is_commuting.py index 7e1ecaef810..a1171f295e6 100644 --- a/tests/ops/functions/test_is_commuting.py +++ b/tests/ops/functions/test_is_commuting.py @@ -791,11 +791,6 @@ def test_barrier_u3_identity(self, wires, res): qml.prod(qml.PauliX("a"), qml.PauliZ("c"), qml.PauliY("d")), False, ), - ( - qml.operation.Tensor(qml.PauliX("a"), qml.PauliY("b"), qml.PauliZ("d")), - qml.operation.Tensor(qml.PauliX("a"), qml.PauliZ("c"), qml.PauliY("d")), - False, - ), ( qml.sum(qml.PauliZ("a"), qml.PauliY("b"), qml.PauliZ("d")), qml.sum(qml.PauliX("a"), qml.PauliZ("c"), qml.PauliY("d")), @@ -825,10 +820,6 @@ def test_pauli_words(self, pauli_word_1, pauli_word_2, commute_status): qml.prod(qml.PauliX(0), qml.Hadamard(1), qml.Identity(2)), qml.sum(qml.PauliX(0), qml.PauliY(2)), ), - ( - qml.sum(qml.PauliX(0), qml.PauliY(2)), - qml.operation.Tensor(qml.PauliX(0), qml.Hadamard(1), qml.Identity(2)), - ), (qml.PauliX(2), qml.sum(qml.Hadamard(1), qml.prod(qml.PauliX(1), qml.Identity(2)))), (qml.prod(qml.PauliX(1), qml.PauliY(2)), qml.s_prod(0.5, qml.Hadamard(1))), ], diff --git a/tests/ops/op_math/test_adjoint.py b/tests/ops/op_math/test_adjoint.py index 6fba91f1537..ac7b508d2c5 100644 --- a/tests/ops/op_math/test_adjoint.py +++ b/tests/ops/op_math/test_adjoint.py @@ -76,7 +76,6 @@ class CustomOp(qml.operation.Operation): assert "grad_recipe" in dir(op) assert "control_wires" in dir(op) - @pytest.mark.usefixtures("legacy_opmath_only") def test_observable(self): """Test that when the base is an Observable, Adjoint will also inherit from Observable.""" @@ -96,8 +95,7 @@ class CustomObs(qml.operation.Observable): # Check some basic observable functionality assert ob.compare(ob) - with pytest.warns(UserWarning, match="Tensor object acts on overlapping"): - assert isinstance(1.0 * ob @ ob, qml.Hamiltonian) + assert isinstance(1.0 * ob @ ob, qml.ops.Prod) # check the dir assert "grad_recipe" not in dir(ob) @@ -107,7 +105,7 @@ class CustomObs(qml.operation.Observable): ( PlainOperator(1.2, wires=0), qml.RX(1.2, wires=0), - qml.operation.Tensor(qml.PauliX(0), qml.PauliX(1)), + qml.Hermitian([[1, 0], [0, 1]], wires=0), qml.PauliX(0), ), ) @@ -180,24 +178,6 @@ def test_template_base(self, seed): assert op.wires == qml.wires.Wires((0, 1)) - @pytest.mark.usefixtures("legacy_opmath_only") - def test_hamiltonian_base(self): - """Test adjoint initialization for a hamiltonian.""" - with pytest.warns(UserWarning, match="Tensor object acts on overlapping"): - base = 2.0 * qml.PauliX(0) @ qml.PauliY(0) + qml.PauliZ("b") - - op = Adjoint(base) - - assert op.base is base - assert op.hyperparameters["base"] is base - assert op.name == "Adjoint(Hamiltonian)" - - assert op.num_params == 2 - assert qml.math.allclose(op.parameters, [2.0, 1.0]) - assert qml.math.allclose(op.data, [2.0, 1.0]) - - assert op.wires == qml.wires.Wires([0, "b"]) - class TestProperties: """Test Adjoint properties.""" @@ -320,12 +300,6 @@ def test_queue_category(self): op = Adjoint(qml.PauliX(0)) assert op._queue_category == "_ops" # pylint: disable=protected-access - @pytest.mark.usefixtures("legacy_opmath_only") - def test_queue_category_None(self): - """Test that the queue category `None` for some observables carries over.""" - op = Adjoint(qml.PauliX(0) @ qml.PauliY(1)) - assert op._queue_category is None # pylint: disable=protected-access - @pytest.mark.parametrize("value", (True, False)) def test_is_hermitian(self, value): """Test `is_hermitian` property mirrors that of the base.""" @@ -492,7 +466,6 @@ def test_has_generator_false(self): assert op.has_generator is False - @pytest.mark.usefixtures("use_legacy_and_new_opmath") def test_generator(self): """Assert that the generator of an Adjoint is -1.0 times the base generator.""" base = qml.RX(1.23, wires=0) @@ -653,7 +626,6 @@ def test_no_matrix_defined(self, seed): with pytest.raises(qml.operation.MatrixUndefinedError): Adjoint(base).matrix() - @pytest.mark.usefixtures("use_legacy_and_new_opmath") def test_adj_hamiltonian(self): """Test that a we can take the adjoint of a hamiltonian.""" U = qml.Hamiltonian([1.0], [qml.PauliX(wires=0) @ qml.PauliZ(wires=1)]) @@ -868,22 +840,6 @@ def test_single_op_defined_outside_queue_eager(self): assert len(q) == 1 assert q.queue[0] is out - @pytest.mark.usefixtures("legacy_opmath_only") - def test_single_observable(self): - """Test passing a single preconstructed observable in a queuing context.""" - - with qml.queuing.AnnotatedQueue() as q: - base = qml.PauliX(0) @ qml.PauliY(1) - out = adjoint(base) - - assert len(q) == 1 - assert q.queue[0] is out - assert out.base is base - assert isinstance(out, Adjoint) - - qs = qml.tape.QuantumScript.from_queue(q) - assert len(qs) == 0 - def test_correct_queued_operators(self): """Test that args and kwargs do not add operators to the queue.""" diff --git a/tests/ops/op_math/test_composite.py b/tests/ops/op_math/test_composite.py index d6cfdc3f07a..b0f52a84526 100644 --- a/tests/ops/op_math/test_composite.py +++ b/tests/ops/op_math/test_composite.py @@ -16,7 +16,7 @@ """ import inspect -# pylint:disable=protected-access +# pylint:disable=protected-access, use-implicit-booleaness-not-comparison from copy import copy import numpy as np @@ -24,7 +24,7 @@ import pennylane as qml from pennylane.operation import DecompositionUndefinedError -from pennylane.ops.op_math import CompositeOp, Prod, SProd, Sum +from pennylane.ops.op_math import CompositeOp from pennylane.wires import Wires ops = ( @@ -213,27 +213,6 @@ def test_build_pauli_rep(self): op = ValidOp(*self.simple_operands) assert op._build_pauli_rep() == qml.pauli.PauliSentence({}) - def test_tensor_and_hamiltonian_converted(self): - """Test that Tensor and Hamiltonian instances get converted to Prod and Sum.""" - operands = [ - qml.Hamiltonian( - [1.1, 2.2], [qml.PauliZ(0), qml.operation.Tensor(qml.PauliX(0), qml.PauliZ(1))] - ), - qml.prod(qml.PauliX(0), qml.PauliZ(1)), - qml.operation.Tensor(qml.PauliX(2), qml.PauliZ(3)), - ] - op = qml.sum(*operands) - assert isinstance(op[0], Sum) - assert isinstance(op[0][1], SProd) - assert isinstance(op[0][1].base, Prod) - assert op[1] is operands[1] - assert isinstance(op[2], Prod) - assert op.operands == ( - qml.dot([1.1, 2.2], [qml.PauliZ(0), qml.prod(qml.PauliX(0), qml.PauliZ(1))]), - operands[1], - qml.prod(qml.PauliX(2), qml.PauliZ(3)), - ) - @pytest.mark.parametrize("math_op", [qml.prod, qml.sum]) def test_no_recursion_error_raised(math_op): diff --git a/tests/ops/op_math/test_evolution.py b/tests/ops/op_math/test_evolution.py index 1c3c8b1e0ad..2838a9f5f69 100644 --- a/tests/ops/op_math/test_evolution.py +++ b/tests/ops/op_math/test_evolution.py @@ -71,15 +71,6 @@ def test_generator(self): U = Evolution(qml.PauliX(0), 3) assert U.generator() == -1 * U.base - @pytest.mark.usefixtures("legacy_opmath_only") - def test_num_params_for_parametric_base_legacy_opmath(self): - base_op = 0.5 * qml.PauliY(0) + qml.PauliZ(0) @ qml.PauliX(1) - op = Evolution(base_op, 1.23) - - assert base_op.num_params == 2 - assert op.num_params == 1 - - @pytest.mark.usefixtures("new_opmath_only") def test_num_params_for_parametric_base(self): base_op = 0.5 * qml.PauliY(0) + qml.PauliZ(0) @ qml.PauliX(1) op = Evolution(base_op, 1.23) diff --git a/tests/ops/op_math/test_exp.py b/tests/ops/op_math/test_exp.py index 76da143d621..e73ec05d703 100644 --- a/tests/ops/op_math/test_exp.py +++ b/tests/ops/op_math/test_exp.py @@ -432,16 +432,6 @@ def test_non_pauli_word_base_no_decomposition(self): ): op.decomposition() - @pytest.mark.usefixtures("legacy_opmath_only") - def test_nontensor_tensor_no_decomposition(self): - """Checks that accessing the decomposition throws an error if the base is a Tensor - object that is not a mathematical tensor""" - base_op = qml.PauliX(0) @ qml.PauliZ(0) - op = Exp(base_op, 1j) - assert not op.has_decomposition - with pytest.raises(DecompositionUndefinedError): - _ = op.decomposition() - @pytest.mark.parametrize( "base, base_string", ( @@ -458,22 +448,6 @@ def test_decomposition_into_pauli_rot(self, base, base_string): pr = op.decomposition()[0] qml.assert_equal(pr, qml.PauliRot(3.21, base_string, base.wires)) - @pytest.mark.parametrize( - "base, base_string", - ( - (qml.operation.Tensor(qml.PauliZ(0), qml.PauliY(1)), "ZY"), - (qml.operation.Tensor(qml.PauliY(0), qml.Identity(1), qml.PauliZ(2)), "YIZ"), - ), - ) - def test_decomposition_tensor_into_pauli_rot(self, base, base_string): - """Check that Exp decomposes into PauliRot if base is a pauli word with more than one term.""" - theta = 3.21 - op = Exp(base, -0.5j * theta) - - assert op.has_decomposition - pr = op.decomposition()[0] - qml.assert_equal(pr, qml.PauliRot(3.21, base_string, base.wires)) - @pytest.mark.parametrize("op_name", all_qubit_operators) @pytest.mark.parametrize("str_wires", (True, False)) def test_generator_decomposition(self, op_name, str_wires): diff --git a/tests/ops/op_math/test_linear_combination.py b/tests/ops/op_math/test_linear_combination.py index b75b7d01fd9..728ec636966 100644 --- a/tests/ops/op_math/test_linear_combination.py +++ b/tests/ops/op_math/test_linear_combination.py @@ -25,54 +25,10 @@ import pennylane as qml from pennylane import X, Y, Z from pennylane import numpy as pnp -from pennylane.operation import enable_new_opmath_cm from pennylane.ops import LinearCombination from pennylane.pauli import PauliSentence, PauliWord from pennylane.wires import Wires - -@pytest.mark.usefixtures("legacy_opmath_only") -def test_switching(): - """Test that switching to new from old opmath changes the dispatch of qml.Hamiltonian""" - Ham = qml.Hamiltonian([1.0, 2.0, 3.0], [X(0), X(0) @ X(1), X(2)]) - assert isinstance(Ham, qml.Hamiltonian) - assert not isinstance(Ham, qml.ops.LinearCombination) - - with enable_new_opmath_cm(warn=False): - LC = qml.Hamiltonian([1.0, 2.0, 3.0], [X(0), X(0) @ X(1), X(2)]) - assert isinstance(LC, qml.Hamiltonian) - assert isinstance(LC, qml.ops.LinearCombination) - - -def test_isinstance_Hamiltonian(): - """Test that Hamiltonian and LinearCombination can be used interchangeably when new opmath is disabled or enabled""" - H = qml.Hamiltonian([1.0, 2.0, 3.0], [X(0), X(0) @ X(1), X(2)]) - assert isinstance(H, qml.Hamiltonian) - - -def test_mixed_legacy_warning_Hamiltonian_legacy(): - """Test that mixing legacy ops and LinearCombination.compare raises a warning in legacy opmath""" - - op1 = qml.ops.LinearCombination([0.5, 0.5], [X(0) @ X(1), qml.Hadamard(0)]) - op2 = qml.ops.Hamiltonian([0.5, 0.5], [qml.operation.Tensor(X(0), X(1)), qml.Hadamard(0)]) - - with pytest.warns(UserWarning, match="Attempting to compare a legacy operator class instance"): - res = op1.compare(op2) - - assert res - - -def test_mixed_legacy_warning_Tensor(): - """Test that mixing legacy ops and LinearCombination.compare raises a warning""" - op1 = qml.ops.LinearCombination([1.0], [X(0) @ qml.Hadamard(1)]) - op2 = qml.operation.Tensor(X(0), qml.Hadamard(1)) - - with pytest.warns(UserWarning, match="Attempting to compare a legacy operator class instance"): - res = op1.compare(op2) - - assert res - - # Make test data in different interfaces, if installed COEFFS_PARAM_INTERFACE = [ ([-0.05, 0.17], 1.7, "autograd"), @@ -555,7 +511,6 @@ def circuit2(param): dev = qml.device("default.qubit", wires=2) -@pytest.mark.usefixtures("new_opmath_only") class TestLinearCombination: """Test the LinearCombination class""" diff --git a/tests/ops/op_math/test_pow_op.py b/tests/ops/op_math/test_pow_op.py index 22b0ab4d83a..e667f77e465 100644 --- a/tests/ops/op_math/test_pow_op.py +++ b/tests/ops/op_math/test_pow_op.py @@ -164,30 +164,6 @@ class CustomObs(qml.operation.Observable): # check the dir assert "grad_recipe" not in dir(ob) - @pytest.mark.usefixtures("legacy_opmath_only") - def test_observable_legacy_opmath(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 - - base = CustomObs(wires=0) - ob: Pow = power_method(base=base, z=-1.2) - - assert isinstance(ob, Pow) - assert isinstance(ob, qml.operation.Operator) - assert not isinstance(ob, qml.operation.Operation) - assert isinstance(ob, qml.operation.Observable) - assert not isinstance(ob, PowOperation) - - # Check some basic observable functionality - assert ob.compare(ob) - assert isinstance(1.0 * ob @ ob, qml.Hamiltonian) - - # check the dir - assert "grad_recipe" not in dir(ob) - @pytest.mark.parametrize("power_method", [Pow, pow_using_dunder_method, qml.pow]) class TestInitialization: @@ -258,26 +234,6 @@ def test_template_base(self, power_method, seed): assert op.wires == qml.wires.Wires((0, 1)) assert op.num_wires == 2 - @pytest.mark.usefixtures("legacy_opmath_only") - def test_hamiltonian_base(self, power_method): - """Test pow initialization for a hamiltonian.""" - base = qml.Hamiltonian([2.0, 1.0], [qml.PauliX(0) @ qml.PauliY(0), qml.PauliZ("b")]) - - op: Pow = power_method(base=base, z=3.4) - - assert op.base is base - assert op.z == 3.4 - assert op.hyperparameters["base"] is base - assert op.hyperparameters["z"] == 3.4 - assert op.name == "Hamiltonian**3.4" - - assert op.num_params == 2 - assert qml.math.allclose(op.parameters, [2.0, 1.0]) - assert qml.math.allclose(op.data, [2.0, 1.0]) - - assert op.wires == qml.wires.Wires([0, "b"]) - assert op.num_wires == 2 - # pylint: disable=too-many-public-methods @pytest.mark.parametrize("power_method", [Pow, pow_using_dunder_method, qml.pow]) @@ -423,12 +379,6 @@ def test_queue_category(self, power_method): op: Pow = power_method(base=qml.PauliX(0), z=3.5) assert op._queue_category == "_ops" # pylint: disable=protected-access - @pytest.mark.usefixtures("legacy_opmath_only") - def test_queue_category_None(self, power_method): - """Test that the queue category `None` for some observables carries over.""" - op: Pow = power_method(base=qml.PauliX(0) @ qml.PauliY(1), z=-1.1) - assert op._queue_category is None # pylint: disable=protected-access - def test_batching_properties(self, power_method): """Test the batching properties and methods.""" @@ -876,7 +826,6 @@ def test_matrix_wire_order(self): assert qml.math.allclose(op_mat, compare_mat) - @pytest.mark.usefixtures("use_legacy_and_new_opmath") def test_pow_hamiltonian(self): """Test that a hamiltonian object can be exponentiated.""" U = qml.Hamiltonian([1.0], [qml.PauliX(wires=0)]) diff --git a/tests/ops/op_math/test_sum.py b/tests/ops/op_math/test_sum.py index d6bc1668f9f..a9d1233ce64 100644 --- a/tests/ops/op_math/test_sum.py +++ b/tests/ops/op_math/test_sum.py @@ -344,7 +344,6 @@ def test_repr(self, op, repr_true): # qml.sum(*[0.5 * X(i) for i in range(10)]) # multiline output needs fixing of https://github.com/PennyLaneAI/pennylane/issues/5162 before working ) - @pytest.mark.usefixtures("new_opmath_only") @pytest.mark.parametrize("op", SUM_REPR_EVAL) def test_eval_sum(self, op): """Test that string representations of Sum can be evaluated and yield the same operator""" @@ -681,7 +680,6 @@ def test_queue_category(self, ops_lst, sum_method): sum_op = sum_method(*ops_lst) assert sum_op._queue_category is None # pylint: disable=protected-access - @pytest.mark.usefixtures("new_opmath_only") def test_eigvals_Identity_no_wires(self): """Test that eigenvalues can be computed for a sum containing identity with no wires.""" @@ -710,7 +708,6 @@ def test_eigendecomposition(self): assert np.allclose(eig_vals, true_eigvals) assert np.allclose(eig_vecs, true_eigvecs) - @pytest.mark.usefixtures("new_opmath_only") def test_eigendecomposition_repeat_operations(self): """Test that the eigendecomposition works with a repeated operation.""" op1 = qml.X(0) + qml.X(0) + qml.X(0) diff --git a/tests/ops/qubit/test_attributes.py b/tests/ops/qubit/test_attributes.py index 9808c07d71d..a9bf5d3d4e2 100644 --- a/tests/ops/qubit/test_attributes.py +++ b/tests/ops/qubit/test_attributes.py @@ -83,10 +83,6 @@ def test_inclusion_after_addition(self): assert "RY" in new_attribute assert len(new_attribute) == 8 - def test_tensor_check(self): - """Test that we can ask if a tensor is in the attribute.""" - assert qml.operation.Tensor(qml.PauliX(wires=0), qml.PauliZ(wires=1)) not in new_attribute - single_scalar_single_wire_ops = [ "RX", diff --git a/tests/ops/qubit/test_hamiltonian.py b/tests/ops/qubit/test_hamiltonian.py deleted file mode 100644 index 8b27b2cb7b4..00000000000 --- a/tests/ops/qubit/test_hamiltonian.py +++ /dev/null @@ -1,2195 +0,0 @@ -# Copyright 2018-2021 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 Hamiltonian class. -""" -import warnings - -# pylint: disable=too-many-public-methods, superfluous-parens, unnecessary-dunder-call -from collections.abc import Iterable -from unittest.mock import patch - -import numpy as np -import pytest -import scipy - -import pennylane as qml -from pennylane import numpy as pnp -from pennylane.wires import Wires - -pytestmark = pytest.mark.filterwarnings( - "ignore:qml.ops.Hamiltonian uses:pennylane.PennyLaneDeprecationWarning" -) - - -# Make test data in different interfaces, if installed -COEFFS_PARAM_INTERFACE = [ - ([-0.05, 0.17], 1.7, "autograd"), - (np.array([-0.05, 0.17]), np.array(1.7), "autograd"), - (pnp.array([-0.05, 0.17], requires_grad=True), pnp.array(1.7, requires_grad=True), "autograd"), -] - -try: - import jax - from jax import numpy as jnp - - COEFFS_PARAM_INTERFACE.append((jnp.array([-0.05, 0.17]), jnp.array(1.7), "jax")) -except ImportError: - pass - -try: - import tensorflow as tf - - COEFFS_PARAM_INTERFACE.append( - (tf.Variable([-0.05, 0.17], dtype=tf.double), tf.Variable(1.7, dtype=tf.double), "tf") - ) -except ImportError: - pass - -try: - import torch - - COEFFS_PARAM_INTERFACE.append((torch.tensor([-0.05, 0.17]), torch.tensor(1.7), "torch")) -except ImportError: - pass - -H_ONE_QUBIT = np.array([[1.0, 0.5j], [-0.5j, 2.5]]) - -H_TWO_QUBITS = np.array( - [[0.5, 1.0j, 0.0, -3j], [-1.0j, -1.1, 0.0, -0.1], [0.0, 0.0, -0.9, 12.0], [3j, -0.1, 12.0, 0.0]] -) - -COEFFS = [(0.5, 1.2, -0.7), (2.2, -0.2, 0.0), (0.33,)] - -with qml.operation.disable_new_opmath_cm(warn=False): - - OBSERVABLES = [ - (qml.PauliZ(0), qml.PauliY(0), qml.PauliZ(1)), - (qml.PauliX(0) @ qml.PauliZ(1), qml.PauliY(0) @ qml.PauliZ(1), qml.PauliZ(1)), - (qml.Hermitian(H_TWO_QUBITS, [0, 1]),), - ] - - valid_hamiltonians = [ - ((1.0,), (qml.Hermitian(H_TWO_QUBITS, [0, 1]),)), - ((-0.8,), (qml.PauliZ(0),)), - ((0.6,), (qml.PauliX(0) @ qml.PauliX(1),)), - ((0.5, -1.6), (qml.PauliX(0), qml.PauliY(1))), - ((0.5, -1.6), (qml.PauliX(1), qml.PauliY(1))), - ((0.5, -1.6), (qml.PauliX("a"), qml.PauliY("b"))), - ((1.1, -0.4, 0.333), (qml.PauliX(0), qml.Hermitian(H_ONE_QUBIT, 2), qml.PauliZ(2))), - ((-0.4, 0.15), (qml.Hermitian(H_TWO_QUBITS, [0, 2]), qml.PauliZ(1))), - ([1.5, 2.0], [qml.PauliZ(0), qml.PauliY(2)]), - (np.array([-0.1, 0.5]), [qml.Hermitian(H_TWO_QUBITS, [0, 1]), qml.PauliY(0)]), - ((0.5, 1.2), (qml.PauliX(0), qml.PauliX(0) @ qml.PauliX(1))), - ((0.5 + 1.2j, 1.2 + 0.5j), (qml.PauliX(0), qml.PauliY(1))), - ((0.7 + 0j, 0 + 1.3j), (qml.PauliX(0), qml.PauliY(1))), - ] - - valid_hamiltonians_str = [ - " (1.0) [Hermitian0,1]", - " (-0.8) [Z0]", - " (0.6) [X0 X1]", - " (-1.6) [Y1]\n+ (0.5) [X0]", - " (-1.6) [Y1]\n+ (0.5) [X1]", - " (-1.6) [Yb]\n+ (0.5) [Xa]", - " (-0.4) [Hermitian2]\n+ (0.333) [Z2]\n+ (1.1) [X0]", - " (0.15) [Z1]\n+ (-0.4) [Hermitian0,2]", - " (1.5) [Z0]\n+ (2.0) [Y2]", - " (0.5) [Y0]\n+ (-0.1) [Hermitian0,1]", - " (0.5) [X0]\n+ (1.2) [X0 X1]", - " ((0.5+1.2j)) [X0]\n+ ((1.2+0.5j)) [Y1]", - " (1.3j) [Y1]\n+ ((0.7+0j)) [X0]", - ] - - valid_hamiltonians_repr = [ - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - ] - - invalid_hamiltonians = [ - ((), (qml.PauliZ(0),)), - ((), (qml.PauliZ(0), qml.PauliY(1))), - ((3.5,), ()), - ((1.2, -0.4), ()), - ((0.5, 1.2), (qml.PauliZ(0),)), - ((1.0,), (qml.PauliZ(0), qml.PauliY(0))), - ] - - with warnings.catch_warnings(): - warnings.filterwarnings( - "ignore", "qml.ops.Hamiltonian uses", qml.PennyLaneDeprecationWarning - ) - - simplify_hamiltonians = [ - ( - qml.Hamiltonian( - [1, 1, 1], [qml.PauliX(0) @ qml.Identity(1), qml.PauliX(0), qml.PauliX(1)] - ), - qml.Hamiltonian([2, 1], [qml.PauliX(0), qml.PauliX(1)]), - ), - ( - qml.Hamiltonian( - [-1, 1, 1], [qml.PauliX(0) @ qml.Identity(1), qml.PauliX(0), qml.PauliX(1)] - ), - qml.Hamiltonian([1], [qml.PauliX(1)]), - ), - ( - qml.Hamiltonian( - [1, 0.5], - [ - qml.PauliX(0) @ qml.PauliY(1), - qml.PauliY(1) @ qml.Identity(2) @ qml.PauliX(0), - ], - ), - qml.Hamiltonian([1.5], [qml.PauliX(0) @ qml.PauliY(1)]), - ), - ( - qml.Hamiltonian( - [1, 1, 0.5], - [ - qml.Hermitian(np.array([[1, 0], [0, -1]]), "a"), - qml.PauliX("b") @ qml.PauliY(1.3), - qml.PauliY(1.3) @ qml.Identity(-0.9) @ qml.PauliX("b"), - ], - ), - qml.Hamiltonian( - [1, 1.5], - [ - qml.Hermitian(np.array([[1, 0], [0, -1]]), "a"), - qml.PauliX("b") @ qml.PauliY(1.3), - ], - ), - ), - # Simplifies to zero Hamiltonian - ( - qml.Hamiltonian( - [1, -0.5, -0.5], [qml.PauliX(0) @ qml.Identity(1), qml.PauliX(0), qml.PauliX(0)] - ), - qml.Hamiltonian([], []), - ), - ( - qml.Hamiltonian( - [1, -1], - [ - qml.PauliX(4) @ qml.Identity(0) @ qml.PauliX(1), - qml.PauliX(4) @ qml.PauliX(1), - ], - ), - qml.Hamiltonian([], []), - ), - ( - qml.Hamiltonian([0], [qml.Identity(0)]), - qml.Hamiltonian([0], [qml.Identity(0)]), - ), - ] - - equal_hamiltonians = [ - ( - qml.Hamiltonian([1, 1], [qml.PauliX(0) @ qml.Identity(1), qml.PauliZ(0)]), - qml.Hamiltonian([1, 1], [qml.PauliX(0), qml.PauliZ(0)]), - True, - ), - ( - qml.Hamiltonian( - [1, 1], [qml.PauliX(0) @ qml.Identity(1), qml.PauliY(2) @ qml.PauliZ(0)] - ), - qml.Hamiltonian( - [1, 1], [qml.PauliX(0), qml.PauliZ(0) @ qml.PauliY(2) @ qml.Identity(1)] - ), - True, - ), - ( - qml.Hamiltonian( - [1, 1, 1], [qml.PauliX(0) @ qml.Identity(1), qml.PauliZ(0), qml.Identity(1)] - ), - qml.Hamiltonian([1, 1], [qml.PauliX(0), qml.PauliZ(0)]), - False, - ), - ( - qml.Hamiltonian([1], [qml.PauliZ(0) @ qml.PauliX(1)]), - qml.PauliZ(0) @ qml.PauliX(1), - True, - ), - (qml.Hamiltonian([1], [qml.PauliZ(0)]), qml.PauliZ(0), True), - ( - qml.Hamiltonian( - [1, 1, 1], - [ - qml.Hermitian(np.array([[1, 0], [0, -1]]), "b") @ qml.Identity(7), - qml.PauliZ(3), - qml.Identity(1.2), - ], - ), - qml.Hamiltonian( - [1, 1, 1], - [ - qml.Hermitian(np.array([[1, 0], [0, -1]]), "b"), - qml.PauliZ(3), - qml.Identity(1.2), - ], - ), - True, - ), - ( - qml.Hamiltonian([1, 1], [qml.PauliZ(3) @ qml.Identity(1.2), qml.PauliZ(3)]), - qml.Hamiltonian([2], [qml.PauliZ(3)]), - True, - ), - ] - - add_hamiltonians = [ - ( - qml.Hamiltonian([1, 1.2, 0.1], [qml.PauliX(0), qml.PauliZ(1), qml.PauliX(2)]), - qml.Hamiltonian([0.5, 0.3, 1], [qml.PauliX(0), qml.PauliX(1), qml.PauliX(2)]), - qml.Hamiltonian( - [1.5, 1.2, 1.1, 0.3], - [qml.PauliX(0), qml.PauliZ(1), qml.PauliX(2), qml.PauliX(1)], - ), - ), - ( - qml.Hamiltonian( - [1.3, 0.2, 0.7], [qml.PauliX(0) @ qml.PauliX(1), qml.Hadamard(1), qml.PauliX(2)] - ), - qml.Hamiltonian( - [0.5, 0.3, 1.6], [qml.PauliX(0), qml.PauliX(1) @ qml.PauliX(0), qml.PauliX(2)] - ), - qml.Hamiltonian( - [1.6, 0.2, 2.3, 0.5], - [qml.PauliX(0) @ qml.PauliX(1), qml.Hadamard(1), qml.PauliX(2), qml.PauliX(0)], - ), - ), - ( - qml.Hamiltonian( - [1, 1], [qml.PauliX(0), qml.Hermitian(np.array([[1, 0], [0, -1]]), 0)] - ), - qml.Hamiltonian( - [0.5, 0.5], [qml.PauliX(0), qml.Hermitian(np.array([[1, 0], [0, -1]]), 0)] - ), - qml.Hamiltonian( - [1.5, 1.5], [qml.PauliX(0), qml.Hermitian(np.array([[1, 0], [0, -1]]), 0)] - ), - ), - ( - qml.Hamiltonian([1, 1.2, 0.1], [qml.PauliX(0), qml.PauliZ(1), qml.PauliX(2)]), - qml.PauliX(0) @ qml.Identity(1), - qml.Hamiltonian([2, 1.2, 0.1], [qml.PauliX(0), qml.PauliZ(1), qml.PauliX(2)]), - ), - ( - qml.Hamiltonian( - [1.3, 0.2, 0.7], [qml.PauliX(0) @ qml.PauliX(1), qml.Hadamard(1), qml.PauliX(2)] - ), - qml.Hadamard(1), - qml.Hamiltonian( - [1.3, 1.2, 0.7], [qml.PauliX(0) @ qml.PauliX(1), qml.Hadamard(1), qml.PauliX(2)] - ), - ), - ( - qml.Hamiltonian([1, 1.2, 0.1], [qml.PauliX("b"), qml.PauliZ(3.1), qml.PauliX(1.6)]), - qml.PauliX("b") @ qml.Identity(5), - qml.Hamiltonian([2, 1.2, 0.1], [qml.PauliX("b"), qml.PauliZ(3.1), qml.PauliX(1.6)]), - ), - # Case where arguments coeffs and ops to the Hamiltonian are iterables other than lists - ( - qml.Hamiltonian((1, 1.2, 0.1), (qml.PauliX(0), qml.PauliZ(1), qml.PauliX(2))), - qml.Hamiltonian( - np.array([0.5, 0.3, 1]), np.array([qml.PauliX(0), qml.PauliX(1), qml.PauliX(2)]) - ), - qml.Hamiltonian( - (1.5, 1.2, 1.1, 0.3), - np.array([qml.PauliX(0), qml.PauliZ(1), qml.PauliX(2), qml.PauliX(1)]), - ), - ), - # Case where the 1st hamiltonian doesn't contain all wires - ( - qml.Hamiltonian([1.23, -3.45], [qml.PauliX(0), qml.PauliY(1)]), - qml.Hamiltonian([6.78], [qml.PauliZ(2)]), - qml.Hamiltonian([1.23, -3.45, 6.78], [qml.PauliX(0), qml.PauliY(1), qml.PauliZ(2)]), - ), - ] - - add_zero_hamiltonians = [ - qml.Hamiltonian([1, 1.2, 0.1], [qml.PauliX(0), qml.PauliZ(1), qml.PauliX(2)]), - qml.Hamiltonian([1, 1], [qml.PauliX(0), qml.Hermitian(np.array([[1, 0], [0, -1]]), 0)]), - qml.Hamiltonian( - [1.5, 1.2, 1.1, 0.3], [qml.PauliX(0), qml.PauliZ(1), qml.PauliX(2), qml.PauliX(1)] - ), - ] - - iadd_zero_hamiltonians = [ - # identical hamiltonians - ( - qml.Hamiltonian([1, 1.2, 0.1], [qml.PauliX(0), qml.PauliZ(1), qml.PauliX(2)]), - qml.Hamiltonian([1, 1.2, 0.1], [qml.PauliX(0), qml.PauliZ(1), qml.PauliX(2)]), - ), - ( - qml.Hamiltonian( - [1, 1], [qml.PauliX(0), qml.Hermitian(np.array([[1, 0], [0, -1]]), 0)] - ), - qml.Hamiltonian( - [1, 1], [qml.PauliX(0), qml.Hermitian(np.array([[1, 0], [0, -1]]), 0)] - ), - ), - ( - qml.Hamiltonian( - [1.5, 1.2, 1.1, 0.3], - [qml.PauliX(0), qml.PauliZ(1), qml.PauliX(2), qml.PauliX(1)], - ), - qml.Hamiltonian( - [1.5, 1.2, 1.1, 0.3], - [qml.PauliX(0), qml.PauliZ(1), qml.PauliX(2), qml.PauliX(1)], - ), - ), - ] - - sub_hamiltonians = [ - ( - qml.Hamiltonian([1, 1.2, 0.1], [qml.PauliX(0), qml.PauliZ(1), qml.PauliX(2)]), - qml.Hamiltonian([0.5, 0.3, 1.6], [qml.PauliX(0), qml.PauliX(1), qml.PauliX(2)]), - qml.Hamiltonian( - [0.5, 1.2, -1.5, -0.3], - [qml.PauliX(0), qml.PauliZ(1), qml.PauliX(2), qml.PauliX(1)], - ), - ), - ( - qml.Hamiltonian( - [1.3, 0.2, 1], [qml.PauliX(0) @ qml.PauliX(1), qml.Hadamard(1), qml.PauliX(2)] - ), - qml.Hamiltonian( - [0.5, 0.3, 1], [qml.PauliX(0), qml.PauliX(1) @ qml.PauliX(0), qml.PauliX(2)] - ), - qml.Hamiltonian( - [1, 0.2, -0.5], [qml.PauliX(0) @ qml.PauliX(1), qml.Hadamard(1), qml.PauliX(0)] - ), - ), - ( - qml.Hamiltonian( - [1, 1], [qml.PauliX(0), qml.Hermitian(np.array([[1, 0], [0, -1]]), 0)] - ), - qml.Hamiltonian( - [0.5, 0.5], [qml.PauliX(0), qml.Hermitian(np.array([[1, 0], [0, -1]]), 0)] - ), - qml.Hamiltonian( - [0.5, 0.5], [qml.PauliX(0), qml.Hermitian(np.array([[1, 0], [0, -1]]), 0)] - ), - ), - ( - qml.Hamiltonian([1, 1.2, 0.1], [qml.PauliX(0), qml.PauliZ(1), qml.PauliX(2)]), - qml.PauliX(0) @ qml.Identity(1), - qml.Hamiltonian([1.2, 0.1], [qml.PauliZ(1), qml.PauliX(2)]), - ), - ( - qml.Hamiltonian( - [1.3, 0.2, 0.7], [qml.PauliX(0) @ qml.PauliX(1), qml.Hadamard(1), qml.PauliX(2)] - ), - qml.Hadamard(1), - qml.Hamiltonian( - [1.3, -0.8, 0.7], - [qml.PauliX(0) @ qml.PauliX(1), qml.Hadamard(1), qml.PauliX(2)], - ), - ), - ( - qml.Hamiltonian([1, 1.2, 0.1], [qml.PauliX("b"), qml.PauliZ(3.1), qml.PauliX(1.6)]), - qml.PauliX("b") @ qml.Identity(1), - qml.Hamiltonian([1.2, 0.1], [qml.PauliZ(3.1), qml.PauliX(1.6)]), - ), - # The result is the zero Hamiltonian - ( - qml.Hamiltonian([1, 1.2, 0.1], [qml.PauliX(0), qml.PauliZ(1), qml.PauliX(2)]), - qml.Hamiltonian([1, 1.2, 0.1], [qml.PauliX(0), qml.PauliZ(1), qml.PauliX(2)]), - qml.Hamiltonian([], []), - ), - ( - qml.Hamiltonian([1, 2], [qml.PauliX(4), qml.PauliZ(2)]), - qml.Hamiltonian([1, 2], [qml.PauliX(4), qml.PauliZ(2)]), - qml.Hamiltonian([], []), - ), - # Case where arguments coeffs and ops to the Hamiltonian are iterables other than lists - ( - qml.Hamiltonian((1, 1.2, 0.1), (qml.PauliX(0), qml.PauliZ(1), qml.PauliX(2))), - qml.Hamiltonian( - np.array([0.5, 0.3, 1.6]), - np.array([qml.PauliX(0), qml.PauliX(1), qml.PauliX(2)]), - ), - qml.Hamiltonian( - (0.5, 1.2, -1.5, -0.3), - np.array([qml.PauliX(0), qml.PauliZ(1), qml.PauliX(2), qml.PauliX(1)]), - ), - ), - # Case where the 1st hamiltonian doesn't contain all wires - ( - qml.Hamiltonian([1.23, -3.45], [qml.PauliX(0), qml.PauliY(1)]), - qml.Hamiltonian([6.78], [qml.PauliZ(2)]), - qml.Hamiltonian( - [1.23, -3.45, -6.78], [qml.PauliX(0), qml.PauliY(1), qml.PauliZ(2)] - ), - ), - ] - - mul_hamiltonians = [ - ( - 0.5, - qml.Hamiltonian( - [1, 2], [qml.PauliX(0), qml.PauliZ(1)] - ), # Case where the types of the coefficient and the scalar differ - qml.Hamiltonian([0.5, 1.0], [qml.PauliX(0), qml.PauliZ(1)]), - ), - ( - 3, - qml.Hamiltonian([1.5, 0.5], [qml.PauliX(0), qml.PauliZ(1)]), - qml.Hamiltonian([4.5, 1.5], [qml.PauliX(0), qml.PauliZ(1)]), - ), - ( - -1.3, - qml.Hamiltonian([1, -0.3], [qml.PauliX(0), qml.PauliZ(1) @ qml.PauliZ(2)]), - qml.Hamiltonian([-1.3, 0.39], [qml.PauliX(0), qml.PauliZ(1) @ qml.PauliZ(2)]), - ), - ( - -1.3, - qml.Hamiltonian( - [1, -0.3], - [ - qml.Hermitian(np.array([[1, 0], [0, -1]]), "b"), - qml.PauliZ(23) @ qml.PauliZ(0), - ], - ), - qml.Hamiltonian( - [-1.3, 0.39], - [ - qml.Hermitian(np.array([[1, 0], [0, -1]]), "b"), - qml.PauliZ(23) @ qml.PauliZ(0), - ], - ), - ), - # The result is the zero Hamiltonian - ( - 0, - qml.Hamiltonian([1], [qml.PauliX(0)]), - qml.Hamiltonian([0], [qml.PauliX(0)]), - ), - ( - 0, - qml.Hamiltonian([1, 1.2, 0.1], [qml.PauliX(0), qml.PauliZ(1), qml.PauliX(2)]), - qml.Hamiltonian([0, 0, 0], [qml.PauliX(0), qml.PauliZ(1), qml.PauliX(2)]), - ), - # Case where arguments coeffs and ops to the Hamiltonian are iterables other than lists - ( - 3, - qml.Hamiltonian((1.5, 0.5), (qml.PauliX(0), qml.PauliZ(1))), - qml.Hamiltonian(np.array([4.5, 1.5]), np.array([qml.PauliX(0), qml.PauliZ(1)])), - ), - ] - - matmul_hamiltonians = [ - ( - qml.Hamiltonian([1, 1], [qml.PauliX(0), qml.PauliZ(1)]), - qml.Hamiltonian([0.5, 0.5], [qml.PauliZ(2), qml.PauliZ(3)]), - qml.Hamiltonian( - [0.5, 0.5, 0.5, 0.5], - [ - qml.PauliX(0) @ qml.PauliZ(2), - qml.PauliX(0) @ qml.PauliZ(3), - qml.PauliZ(1) @ qml.PauliZ(2), - qml.PauliZ(1) @ qml.PauliZ(3), - ], - ), - ), - ( - qml.Hamiltonian([0.5, 0.25], [qml.PauliX(0) @ qml.PauliX(1), qml.PauliZ(0)]), - qml.Hamiltonian([1, 1], [qml.PauliX(3) @ qml.PauliZ(2), qml.PauliZ(2)]), - qml.Hamiltonian( - [0.5, 0.5, 0.25, 0.25], - [ - qml.PauliX(0) @ qml.PauliX(1) @ qml.PauliX(3) @ qml.PauliZ(2), - qml.PauliX(0) @ qml.PauliX(1) @ qml.PauliZ(2), - qml.PauliZ(0) @ qml.PauliX(3) @ qml.PauliZ(2), - qml.PauliZ(0) @ qml.PauliZ(2), - ], - ), - ), - ( - qml.Hamiltonian( - [1, 1], [qml.PauliX("b"), qml.Hermitian(np.array([[1, 0], [0, -1]]), 0)] - ), - qml.Hamiltonian([2, 2], [qml.PauliZ(1.2), qml.PauliY("c")]), - qml.Hamiltonian( - [2, 2, 2, 2], - [ - qml.PauliX("b") @ qml.PauliZ(1.2), - qml.PauliX("b") @ qml.PauliY("c"), - qml.Hermitian(np.array([[1, 0], [0, -1]]), 0) @ qml.PauliZ(1.2), - qml.Hermitian(np.array([[1, 0], [0, -1]]), 0) @ qml.PauliY("c"), - ], - ), - ), - ( - qml.Hamiltonian([1, 1], [qml.PauliX(0), qml.PauliZ(1)]), - qml.PauliX(2), - qml.Hamiltonian( - [1, 1], [qml.PauliX(0) @ qml.PauliX(2), qml.PauliZ(1) @ qml.PauliX(2)] - ), - ), - # Case where arguments coeffs and ops to the Hamiltonian are iterables other than lists - ( - qml.Hamiltonian((1, 1), (qml.PauliX(0), qml.PauliZ(1))), - qml.Hamiltonian(np.array([0.5, 0.5]), np.array([qml.PauliZ(2), qml.PauliZ(3)])), - qml.Hamiltonian( - (0.5, 0.5, 0.5, 0.5), - np.array( - [ - qml.PauliX(0) @ qml.PauliZ(2), - qml.PauliX(0) @ qml.PauliZ(3), - qml.PauliZ(1) @ qml.PauliZ(2), - qml.PauliZ(1) @ qml.PauliZ(3), - ] - ), - ), - ), - ] - - rmatmul_hamiltonians = [ - ( - qml.Hamiltonian([0.5, 0.5], [qml.PauliZ(2), qml.PauliZ(3)]), - qml.Hamiltonian([1, 1], [qml.PauliX(0), qml.PauliZ(1)]), - qml.Hamiltonian( - [0.5, 0.5, 0.5, 0.5], - [ - qml.PauliX(0) @ qml.PauliZ(2), - qml.PauliX(0) @ qml.PauliZ(3), - qml.PauliZ(1) @ qml.PauliZ(2), - qml.PauliZ(1) @ qml.PauliZ(3), - ], - ), - ), - ( - qml.Hamiltonian([1, 1], [qml.PauliX(3) @ qml.PauliZ(2), qml.PauliZ(2)]), - qml.Hamiltonian([0.5, 0.25], [qml.PauliX(0) @ qml.PauliX(1), qml.PauliZ(0)]), - qml.Hamiltonian( - [0.5, 0.5, 0.25, 0.25], - [ - qml.PauliX(0) @ qml.PauliX(1) @ qml.PauliX(3) @ qml.PauliZ(2), - qml.PauliX(0) @ qml.PauliX(1) @ qml.PauliZ(2), - qml.PauliZ(0) @ qml.PauliX(3) @ qml.PauliZ(2), - qml.PauliZ(0) @ qml.PauliZ(2), - ], - ), - ), - ( - qml.Hamiltonian([2, 2], [qml.PauliZ(1.2), qml.PauliY("c")]), - qml.Hamiltonian( - [1, 1], [qml.PauliX("b"), qml.Hermitian(np.array([[1, 0], [0, -1]]), 0)] - ), - qml.Hamiltonian( - [2, 2, 2, 2], - [ - qml.PauliX("b") @ qml.PauliZ(1.2), - qml.PauliX("b") @ qml.PauliY("c"), - qml.Hermitian(np.array([[1, 0], [0, -1]]), 0) @ qml.PauliZ(1.2), - qml.Hermitian(np.array([[1, 0], [0, -1]]), 0) @ qml.PauliY("c"), - ], - ), - ), - ( - qml.Hamiltonian([1, 1], [qml.PauliX(0), qml.PauliZ(1)]), - qml.PauliX(2), - qml.Hamiltonian( - [1, 1], [qml.PauliX(2) @ qml.PauliX(0), qml.PauliX(2) @ qml.PauliZ(1)] - ), - ), - # Case where arguments coeffs and ops to the Hamiltonian are iterables other than lists - ( - qml.Hamiltonian(np.array([0.5, 0.5]), np.array([qml.PauliZ(2), qml.PauliZ(3)])), - qml.Hamiltonian((1, 1), (qml.PauliX(0), qml.PauliZ(1))), - qml.Hamiltonian( - (0.5, 0.5, 0.5, 0.5), - np.array( - [ - qml.PauliX(0) @ qml.PauliZ(2), - qml.PauliX(0) @ qml.PauliZ(3), - qml.PauliZ(1) @ qml.PauliZ(2), - qml.PauliZ(1) @ qml.PauliZ(3), - ] - ), - ), - ), - ] - - big_hamiltonian_coeffs = np.array( - [ - -0.04207898, - 0.17771287, - 0.17771287, - -0.24274281, - -0.24274281, - 0.17059738, - 0.04475014, - -0.04475014, - -0.04475014, - 0.04475014, - 0.12293305, - 0.16768319, - 0.16768319, - 0.12293305, - 0.17627641, - ] - ) - - big_hamiltonian_ops = [ - qml.Identity(wires=[0]), - qml.PauliZ(wires=[0]), - qml.PauliZ(wires=[1]), - qml.PauliZ(wires=[2]), - qml.PauliZ(wires=[3]), - qml.PauliZ(wires=[0]) @ qml.PauliZ(wires=[1]), - qml.PauliY(wires=[0]) - @ qml.PauliX(wires=[1]) - @ qml.PauliX(wires=[2]) - @ qml.PauliY(wires=[3]), - qml.PauliY(wires=[0]) - @ qml.PauliY(wires=[1]) - @ qml.PauliX(wires=[2]) - @ qml.PauliX(wires=[3]), - qml.PauliX(wires=[0]) - @ qml.PauliX(wires=[1]) - @ qml.PauliY(wires=[2]) - @ qml.PauliY(wires=[3]), - qml.PauliX(wires=[0]) - @ qml.PauliY(wires=[1]) - @ qml.PauliY(wires=[2]) - @ qml.PauliX(wires=[3]), - qml.PauliZ(wires=[0]) @ qml.PauliZ(wires=[2]), - qml.PauliZ(wires=[0]) @ qml.PauliZ(wires=[3]), - qml.PauliZ(wires=[1]) @ qml.PauliZ(wires=[2]), - qml.PauliZ(wires=[1]) @ qml.PauliZ(wires=[3]), - qml.PauliZ(wires=[2]) @ qml.PauliZ(wires=[3]), - ] - - big_hamiltonian = qml.Hamiltonian(big_hamiltonian_coeffs, big_hamiltonian_ops) - - big_hamiltonian_grad = ( - np.array( - [ - [ - [6.52084595e-18, -2.11464420e-02, -1.16576858e-02], - [-8.22589330e-18, -5.20597922e-02, -1.85365365e-02], - [-2.73850768e-17, 1.14202988e-01, -5.45041403e-03], - [-1.27514307e-17, -1.10465531e-01, 5.19489457e-02], - ], - [ - [-2.45428288e-02, 8.38921555e-02, -2.00641818e-17], - [-2.21085973e-02, 7.39332741e-04, -1.25580654e-17], - [9.62058625e-03, -1.51398765e-01, 2.02129847e-03], - [1.10020832e-03, -3.49066271e-01, 2.13669117e-03], - ], - ] - ), - ) - - -def circuit1(param): - """First Pauli subcircuit""" - qml.RX(param, wires=0) - qml.RY(param, wires=0) - return qml.expval(qml.PauliX(0)) - - -def circuit2(param): - """Second Pauli subcircuit""" - qml.RX(param, wires=0) - qml.RY(param, wires=0) - return qml.expval(qml.PauliZ(0)) - - -dev = qml.device("default.qubit", wires=2) - - -@pytest.mark.usefixtures("use_legacy_and_new_opmath") -def test_matmul_queuing(): - """Test that the other and self are removed during Hamiltonian.__matmul__ .""" - - with qml.queuing.AnnotatedQueue() as q: - H = 0.5 * qml.X(0) @ qml.Y(1) - - assert len(q) == 1 - assert q.queue[0] is H - - -@pytest.mark.usefixtures("use_legacy_and_new_opmath") -def test_deprecation(): - """Test that a warning is raised if attempting to create a legacy Hamiltonian.""" - with pytest.warns( - qml.PennyLaneDeprecationWarning, - match="qml.ops.Hamiltonian uses the old approach", - ): - _ = qml.ops.Hamiltonian([1.0], [qml.X(0)]) - - -@pytest.mark.usefixtures("legacy_opmath_only") -class TestHamiltonian: - """Test the Hamiltonian class""" - - @pytest.mark.parametrize("coeffs, ops", valid_hamiltonians) - def test_hamiltonian_valid_init(self, coeffs, ops): - """Tests that the Hamiltonian object is created with - the correct attributes""" - - H = qml.Hamiltonian(coeffs, ops) - assert np.allclose(H.terms()[0], coeffs) - assert H.terms()[1] == list(ops) - - @pytest.mark.parametrize("coeffs, ops", invalid_hamiltonians) - def test_hamiltonian_invalid_init_exception(self, coeffs, ops): - """Tests that an exception is raised when giving an invalid - combination of coefficients and ops""" - with pytest.raises(ValueError, match="number of coefficients and operators does not match"): - qml.Hamiltonian(coeffs, ops) - - @pytest.mark.parametrize( - "obs", [[qml.PauliX(0), qml.CNOT(wires=[0, 1])], [qml.PauliZ, qml.PauliZ(0)]] - ) - def test_hamiltonian_invalid_observables(self, obs): - """Tests that an exception is raised when - a complex Hamiltonian is given""" - coeffs = [0.1, 0.2] - - with pytest.raises(ValueError, match="observables are not valid"): - qml.Hamiltonian(coeffs, obs) - - # pylint: disable=protected-access - @pytest.mark.parametrize("coeffs, ops", valid_hamiltonians) - @pytest.mark.parametrize("grouping_type", (None, "qwc")) - def test_flatten_unflatten(self, coeffs, ops, grouping_type): - """Test the flatten and unflatten methods for hamiltonians""" - assert not qml.operation.active_new_opmath() - if any(not qml.pauli.is_pauli_word(t) for t in ops) and grouping_type: - pytest.skip("grouping type must be none if a term is not a pauli word.") - - H = qml.Hamiltonian(coeffs, ops, grouping_type=grouping_type) - data, metadata = H._flatten() - assert metadata[0] == H.grouping_indices - assert hash(metadata) - assert len(data) == 2 - assert data[0] is H.data - assert data[1] is H._ops - - new_H = qml.Hamiltonian._unflatten(*H._flatten()) - qml.assert_equal(H, new_H) - assert new_H.grouping_indices == H.grouping_indices - - @pytest.mark.parametrize("coeffs, ops", valid_hamiltonians) - def test_hamiltonian_wires(self, coeffs, ops): - """Tests that the Hamiltonian object has correct wires.""" - H = qml.Hamiltonian(coeffs, ops) - assert set(H.wires) == set(w for op in H.ops for w in op.wires) - - def test_label(self): - """Tests the label method of Hamiltonian when <=3 coefficients.""" - H = qml.Hamiltonian((-0.8,), (qml.PauliZ(0),)) - assert H.label() == "𝓗" - assert H.label(decimals=2) == "𝓗\n(-0.80)" - - def test_label_many_coefficients(self): - """Tests the label method of Hamiltonian when >3 coefficients.""" - H = ( - 0.1 * qml.PauliX(0) - + 0.1 * qml.PauliY(1) - + 0.3 * qml.PauliZ(0) @ qml.PauliX(1) - + 0.4 * qml.PauliX(3) - ) - assert H.label() == "𝓗" - assert H.label(decimals=2) == "𝓗" - - @pytest.mark.parametrize("terms, string", zip(valid_hamiltonians, valid_hamiltonians_str)) - def test_hamiltonian_str(self, terms, string): - """Tests that the __str__ function for printing is correct""" - H = qml.Hamiltonian(*terms) - assert H.__str__() == string - - @patch("builtins.print") - def test_small_hamiltonian_ipython_display(self, mock_print): - """Test that the ipython_dipslay method prints __str__.""" - # pylint: disable=protected-access - H = 1.0 * qml.PauliX(0) - H._ipython_display_() - mock_print.assert_called_with(str(H)) - - @patch("builtins.print") - def test_big_hamiltonian_ipython_display(self, mock_print): - """Test that the ipython_display method prints __repr__ when H has more than 15 terms.""" - # pylint: disable=protected-access - H = qml.Hamiltonian([1] * 16, [qml.PauliX(i) for i in range(16)]) - H._ipython_display_() - mock_print.assert_called_with(repr(H)) - - @pytest.mark.parametrize("terms, string", zip(valid_hamiltonians, valid_hamiltonians_repr)) - def test_hamiltonian_repr(self, terms, string): - """Tests that the __repr__ function for printing is correct""" - H = qml.Hamiltonian(*terms) - assert H.__repr__() == string - - def test_hamiltonian_name(self): - """Tests the name property of the Hamiltonian class""" - H = qml.Hamiltonian([], []) - assert H.name == "Hamiltonian" - - @pytest.mark.parametrize(("old_H", "new_H"), simplify_hamiltonians) - def test_simplify(self, old_H, new_H): - """Tests the simplify method""" - old_H.simplify() - assert old_H.compare(new_H) - - def test_simplify_while_queueing(self): - """Tests that simplifying a Hamiltonian in a tape context - queues the simplified Hamiltonian.""" - - with qml.queuing.AnnotatedQueue() as q: - a = qml.PauliX(wires=0) - b = qml.PauliY(wires=1) - c = qml.Identity(wires=2) - d = b @ c - H = qml.Hamiltonian([1.0, 2.0], [a, d]) - H.simplify() - - # check that H is simplified - assert H.ops == [a, b] - # check that the simplified Hamiltonian is in the queue - assert q.get_info(H) is not None - - def test_data(self): - """Tests the obs_data method""" - # pylint: disable=protected-access - - H = qml.Hamiltonian( - [1, 1, 0.5], - [qml.PauliZ(0), qml.PauliZ(0) @ qml.PauliX(1), qml.PauliX(2) @ qml.Identity(1)], - ) - data = H._obs_data() - - assert data == { - (1, frozenset([("PauliZ", qml.wires.Wires(0), ())])), - ( - 1, - frozenset([("PauliZ", qml.wires.Wires(0), ()), ("PauliX", qml.wires.Wires(1), ())]), - ), - (0.5, frozenset([("PauliX", qml.wires.Wires(2), ())])), - } - - def test_data_gell_mann(self): - """Tests that the obs_data method for Hamiltonians with qml.GellMann - observables includes the Gell-Mann index.""" - H = qml.Hamiltonian( - [1, -1, 0.5], - [ - qml.GellMann(wires=0, index=3), - qml.GellMann(wires=0, index=3) @ qml.GellMann(wires=1, index=1), - qml.GellMann(wires=2, index=2), - ], - ) - data = H._obs_data() - - assert data == { - (1, frozenset([("GellMann", qml.wires.Wires(0), (3,))])), - ( - -1, - frozenset( - [("GellMann", qml.wires.Wires(0), (3,)), ("GellMann", qml.wires.Wires(1), (1,))] - ), - ), - (0.5, frozenset([("GellMann", qml.wires.Wires(2), (2,))])), - } - - def test_compare_gell_mann(self): - """Tests that the compare method returns the correct result for Hamiltonians - with qml.GellMann present.""" - H1 = qml.Hamiltonian([1], [qml.GellMann(wires=2, index=2)]) - H2 = qml.Hamiltonian([1], [qml.GellMann(wires=2, index=1) @ qml.GellMann(wires=1, index=2)]) - H3 = qml.Hamiltonian([1], [qml.GellMann(wires=2, index=1)]) - H4 = qml.Hamiltonian([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 - - def test_hamiltonian_equal_error(self): - """Tests that the correct error is raised when compare() is called on invalid type""" - - H = qml.Hamiltonian([1], [qml.PauliZ(0)]) - with pytest.raises( - ValueError, - match=r"Can only compare a Hamiltonian, and a Hamiltonian/Observable/Tensor.", - ): - H.compare([[1, 0], [0, -1]]) - - @pytest.mark.parametrize(("H1", "H2", "res"), equal_hamiltonians) - def test_hamiltonian_equal(self, H1, H2, res): - """Tests that equality can be checked between Hamiltonians""" - assert H1.compare(H2) == res - - @pytest.mark.parametrize(("H1", "H2", "H"), add_hamiltonians) - def test_hamiltonian_add(self, H1, H2, H): - """Tests that Hamiltonians are added correctly""" - assert H.compare(H1 + H2) - - @pytest.mark.parametrize("H", add_zero_hamiltonians) - def test_hamiltonian_add_zero(self, H): - """Tests that Hamiltonians 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) - - @pytest.mark.parametrize(("coeff", "H", "res"), mul_hamiltonians) - def test_hamiltonian_mul(self, coeff, H, res): - """Tests that scalars and Hamiltonians are multiplied correctly""" - assert res.compare(coeff * H) - assert res.compare(H * coeff) - - def test_hamiltonian_mul_coeff_cast(self): - """Test that the coefficients are correct when the type of the existing - and the new coefficients differ.""" - h = 0.5 * (qml.PauliX(0) @ qml.PauliX(0) + qml.PauliY(0) @ qml.PauliY(1)) - assert np.all(h.coeffs == np.array([0.5, 0.5])) - - @pytest.mark.parametrize(("H1", "H2", "H"), sub_hamiltonians) - def test_hamiltonian_sub(self, H1, H2, H): - """Tests that Hamiltonians are subtracted correctly""" - assert H.compare(H1 - H2) - - def test_hamiltonian_tensor_matmul(self): - """Tests that a hamiltonian can be multiplied by a tensor.""" - H = qml.PauliX(0) + qml.PauliY(0) - t = qml.PauliZ(1) @ qml.PauliZ(2) - out = H @ t - - expected = qml.Hamiltonian( - [1, 1], - [ - qml.PauliX(0) @ qml.PauliZ(1) @ qml.PauliZ(2), - qml.PauliY(0) @ qml.PauliZ(1) @ qml.PauliZ(2), - ], - ) - qml.assert_equal(out, expected) - - @pytest.mark.parametrize(("H1", "H2", "H"), matmul_hamiltonians) - def test_hamiltonian_matmul(self, H1, H2, H): - """Tests that Hamiltonians are tensored correctly""" - assert H.compare(H1 @ H2) - - @pytest.mark.parametrize(("H1", "H2", "H"), rmatmul_hamiltonians) - def test_hamiltonian_rmatmul(self, H1, H2, H): - """Tests that Hamiltonians are tensored correctly when using __rmatmul__""" - assert H.compare(H1.__rmatmul__(H2)) - - def test_hamiltonian_same_wires(self): - """Test if a ValueError is raised when multiplication between Hamiltonians acting on the - same wires is attempted""" - h1 = qml.Hamiltonian([1, 1], [qml.PauliZ(0), qml.PauliZ(1)]) - - with pytest.raises(ValueError, match="Hamiltonians can only be multiplied together if"): - _ = h1 @ h1 - - @pytest.mark.parametrize(("H1", "H2", "H"), add_hamiltonians) - def test_hamiltonian_iadd(self, H1, H2, H): - """Tests that Hamiltonians are added inline correctly""" - H1 += H2 - assert H.compare(H1) - assert H.wires == H1.wires - - @pytest.mark.parametrize(("H1", "H2"), iadd_zero_hamiltonians) - def test_hamiltonian_iadd_zero(self, H1, H2): - """Tests in-place addition between Hamiltonians and zero""" - H1 += 0 - assert H1.compare(H2) - H1 += 0.0 - assert H1.compare(H2) - H1 += 0e1 - assert H1.compare(H2) - - @pytest.mark.parametrize(("coeff", "H", "res"), mul_hamiltonians) - def test_hamiltonian_imul(self, coeff, H, res): - """Tests that scalars and Hamiltonians are multiplied inline correctly""" - H *= coeff - assert res.compare(H) - - @pytest.mark.parametrize(("H1", "H2", "H"), sub_hamiltonians) - def test_hamiltonian_isub(self, H1, H2, H): - """Tests that Hamiltonians are subtracted inline correctly""" - H1 -= H2 - assert H.compare(H1) - assert H.wires == H1.wires - - def test_arithmetic_errors(self): - """Tests that the arithmetic operations thrown the correct errors""" - H = qml.Hamiltonian([1], [qml.PauliZ(0)]) - A = [[1, 0], [0, -1]] - with pytest.raises(TypeError, match="unsupported operand type"): - _ = H @ A - with pytest.raises(TypeError, match="unsupported operand type"): - _ = A @ H - with pytest.raises(TypeError, match="unsupported operand type"): - _ = H + A - with pytest.raises(TypeError, match="can't multiply sequence by non-int"): - _ = H * A - with pytest.raises(TypeError, match="unsupported operand type"): - _ = H - A - with pytest.raises(TypeError, match="unsupported operand type"): - H += A - with pytest.raises(TypeError, match="unsupported operand type"): - H *= A - with pytest.raises(TypeError, match="unsupported operand type"): - H -= A - - def test_hamiltonian_queue_outside(self): - """Tests that Hamiltonian are queued correctly when components are defined outside the recording context.""" - - H = qml.PauliX(1) + 3 * qml.PauliZ(0) @ qml.PauliZ(2) + qml.PauliZ(1) - - with qml.queuing.AnnotatedQueue() as q: - qml.Hadamard(wires=1) - qml.PauliX(wires=0) - qml.expval(H) - - assert len(q.queue) == 3 - assert isinstance(q.queue[0], qml.Hadamard) - assert isinstance(q.queue[1], qml.PauliX) - assert isinstance(q.queue[2], qml.measurements.MeasurementProcess) - assert H.compare(q.queue[2].obs) - - def test_hamiltonian_queue_inside(self): - """Tests that Hamiltonian are queued correctly when components are instantiated inside the recording context.""" - - with qml.queuing.AnnotatedQueue() as q: - m = qml.expval( - qml.Hamiltonian( - [1, 3, 1], [qml.PauliX(1), qml.PauliZ(0) @ qml.PauliZ(2), qml.PauliZ(1)] - ) - ) - - assert len(q.queue) == 1 - assert q.queue[0] is m - - def test_terms(self): - """Tests that the terms representation is returned correctly.""" - coeffs = pnp.array([1.0, 2.0], requires_grad=True) - ops = [qml.PauliX(0), qml.PauliZ(1)] - h = qml.Hamiltonian(coeffs, ops) - c, o = h.terms() - assert isinstance(c, Iterable) - assert isinstance(o, list) - assert all(isinstance(item, np.ndarray) for item in c) - assert all(item.requires_grad for item in c) - assert all(isinstance(item, qml.operation.Operator) for item in o) - - def test_hamiltonian_no_empty_wire_list_error(self): - """Test that empty Hamiltonian does not raise an empty wire error.""" - hamiltonian = qml.Hamiltonian([], []) - assert isinstance(hamiltonian, qml.Hamiltonian) - - def test_map_wires(self): - """Test the map_wires method.""" - coeffs = pnp.array([1.0, 2.0, -3.0], requires_grad=True) - ops = [qml.PauliX(0), qml.PauliZ(1), qml.PauliY(2)] - h = qml.Hamiltonian(coeffs, ops) - wire_map = {0: 10, 1: 11, 2: 12} - mapped_h = h.map_wires(wire_map=wire_map) - final_obs = [qml.PauliX(10), qml.PauliZ(11), qml.PauliY(12)] - assert h is not mapped_h - assert h.wires == Wires([0, 1, 2]) - assert mapped_h.wires == Wires([10, 11, 12]) - for obs1, obs2 in zip(mapped_h.ops, final_obs): - qml.assert_equal(obs1, obs2) - for coeff1, coeff2 in zip(mapped_h.coeffs, h.coeffs): - assert coeff1 == coeff2 - - def test_hermitian_tensor_prod(self): - """Test that the tensor product of a Hamiltonian with Hermitian observable works.""" - tensor = qml.PauliX(0) @ qml.PauliX(1) - herm = qml.Hermitian([[1, 0], [0, 1]], wires=4) - - ham = qml.Hamiltonian([1.0, 1.0], [tensor, qml.PauliX(2)]) @ qml.Hamiltonian([1.0], [herm]) - assert isinstance(ham, qml.Hamiltonian) - - def test_hamiltonian_pauli_rep(self): - """Test that the pauli rep is set for a hamiltonian that is a linear combination of paulis.""" - h = qml.Hamiltonian([1.0, 2.0], [qml.X(0) @ qml.Y(1), qml.Z(0) @ qml.Z(2)]) - - pw1 = qml.pauli.PauliWord({0: "X", 1: "Y"}) - pw2 = qml.pauli.PauliWord({0: "Z", 2: "Z"}) - ps = 1.0 * pw1 + 2.0 * pw2 - assert h.pauli_rep == ps - - def test_hamiltonian_no_pauli_rep(self): - """Test that the pauli_rep for a hamiltonian is None if it is not a linear combination of paulis.""" - h = qml.Hamiltonian([1.0, 2.0], [qml.X(0), qml.Hermitian(np.eye(2), 2)]) - assert h.pauli_rep is None - - -@pytest.mark.usefixtures("legacy_opmath_only") -class TestHamiltonianCoefficients: - """Test the creation of a Hamiltonian""" - - @pytest.mark.parametrize("coeffs", [el[0] for el in COEFFS_PARAM_INTERFACE]) - def test_creation_different_coeff_types(self, coeffs): - """Check that Hamiltonian's coefficients and data attributes are set correctly.""" - H = qml.Hamiltonian(coeffs, [qml.PauliX(0), qml.PauliZ(0)]) - assert np.allclose(coeffs, H.coeffs) - assert np.allclose([coeffs[i] for i in range(qml.math.shape(coeffs)[0])], H.data) - - @pytest.mark.parametrize("coeffs", [el[0] for el in COEFFS_PARAM_INTERFACE]) - def test_simplify(self, coeffs): - """Test that simplify works with different coefficient types.""" - H1 = qml.Hamiltonian(coeffs, [qml.PauliX(0), qml.PauliZ(1)]) - H2 = qml.Hamiltonian(coeffs, [qml.PauliX(0), qml.Identity(0) @ qml.PauliZ(1)]) - H2.simplify() - assert H1.compare(H2) - assert H1.data == H2.data - - -@pytest.mark.tf -@pytest.mark.usefixtures("legacy_opmath_only") -class TestHamiltonianArithmeticTF: - """Tests creation of Hamiltonians using arithmetic - operations with TensorFlow tensor coefficients.""" - - def test_hamiltonian_equal(self): - """Tests equality""" - coeffs = tf.Variable([0.5, -1.6]) - obs = [qml.PauliX(0), qml.PauliY(1)] - H1 = qml.Hamiltonian(coeffs, obs) - - coeffs2 = tf.Variable([-1.6, 0.5]) - obs2 = [qml.PauliY(1), qml.PauliX(0)] - H2 = qml.Hamiltonian(coeffs2, obs2) - - assert H1.compare(H2) - - def test_hamiltonian_add(self): - """Tests that Hamiltonians are added correctly""" - coeffs = tf.Variable([0.5, -1.6]) - obs = [qml.PauliX(0), qml.PauliY(1)] - H1 = qml.Hamiltonian(coeffs, obs) - - coeffs2 = tf.Variable([0.5, -0.4]) - H2 = qml.Hamiltonian(coeffs2, obs) - - coeffs_expected = tf.Variable([1.0, -2.0]) - H = qml.Hamiltonian(coeffs_expected, obs) - - assert H.compare(H1 + H2) - - H1 += H2 - assert H.compare(H1) - - def test_hamiltonian_sub(self): - """Tests that Hamiltonians are subtracted correctly""" - coeffs = tf.Variable([1.0, -2.0]) - obs = [qml.PauliX(0), qml.PauliY(1)] - H1 = qml.Hamiltonian(coeffs, obs) - - coeffs2 = tf.Variable([0.5, -0.4]) - H2 = qml.Hamiltonian(coeffs2, obs) - - coeffs_expected = tf.Variable([0.5, -1.6]) - H = qml.Hamiltonian(coeffs_expected, obs) - - assert H.compare(H1 - H2) - - H1 -= H2 - assert H.compare(H1) - - def test_hamiltonian_matmul(self): - """Tests that Hamiltonians are tensored correctly""" - coeffs = tf.Variable([1.0, 2.0]) - obs = [qml.PauliX(0), qml.PauliY(1)] - H1 = qml.Hamiltonian(coeffs, obs) - - coeffs2 = tf.Variable([-1.0, -2.0]) - obs2 = [qml.PauliX(2), qml.PauliY(3)] - H2 = qml.Hamiltonian(coeffs2, obs2) - - coeffs_expected = tf.Variable([-4.0, -2.0, -2.0, -1.0]) - obs_expected = [ - qml.PauliY(1) @ qml.PauliY(3), - qml.PauliX(0) @ qml.PauliY(3), - qml.PauliX(2) @ qml.PauliY(1), - qml.PauliX(0) @ qml.PauliX(2), - ] - H = qml.Hamiltonian(coeffs_expected, obs_expected) - - assert H.compare(H1 @ H2) - - -@pytest.mark.usefixtures("legacy_opmath_only") -class TestHamiltonianArithmeticTorch: - """Tests creation of Hamiltonians using arithmetic - operations with torch tensor coefficients.""" - - @pytest.mark.torch - def test_hamiltonian_equal(self): - """Tests equality""" - coeffs = torch.tensor([0.5, -1.6]) - obs = [qml.PauliX(0), qml.PauliY(1)] - H1 = qml.Hamiltonian(coeffs, obs) - - coeffs2 = torch.tensor([-1.6, 0.5]) - obs2 = [qml.PauliY(1), qml.PauliX(0)] - H2 = qml.Hamiltonian(coeffs2, obs2) - - assert H1.compare(H2) - - @pytest.mark.torch - def test_hamiltonian_add(self): - """Tests that Hamiltonians are added correctly""" - coeffs = torch.tensor([0.5, -1.6]) - obs = [qml.PauliX(0), qml.PauliY(1)] - H1 = qml.Hamiltonian(coeffs, obs) - - coeffs2 = torch.tensor([0.5, -0.4]) - H2 = qml.Hamiltonian(coeffs2, obs) - - coeffs_expected = torch.tensor([1.0, -2.0]) - H = qml.Hamiltonian(coeffs_expected, obs) - - assert H.compare(H1 + H2) - - H1 += H2 - assert H.compare(H1) - - @pytest.mark.torch - def test_hamiltonian_sub(self): - """Tests that Hamiltonians are subtracted correctly""" - coeffs = torch.tensor([1.0, -2.0]) - obs = [qml.PauliX(0), qml.PauliY(1)] - H1 = qml.Hamiltonian(coeffs, obs) - - coeffs2 = torch.tensor([0.5, -0.4]) - H2 = qml.Hamiltonian(coeffs2, obs) - - coeffs_expected = torch.tensor([0.5, -1.6]) - H = qml.Hamiltonian(coeffs_expected, obs) - - assert H.compare(H1 - H2) - - H1 -= H2 - assert H.compare(H1) - - @pytest.mark.torch - def test_hamiltonian_matmul(self): - """Tests that Hamiltonians are tensored correctly""" - coeffs = torch.tensor([1.0, 2.0]) - obs = [qml.PauliX(0), qml.PauliY(1)] - H1 = qml.Hamiltonian(coeffs, obs) - - coeffs2 = torch.tensor([-1.0, -2.0]) - obs2 = [qml.PauliX(2), qml.PauliY(3)] - H2 = qml.Hamiltonian(coeffs2, obs2) - - coeffs_expected = torch.tensor([-4.0, -2.0, -2.0, -1.0]) - obs_expected = [ - qml.PauliY(1) @ qml.PauliY(3), - qml.PauliX(0) @ qml.PauliY(3), - qml.PauliX(2) @ qml.PauliY(1), - qml.PauliX(0) @ qml.PauliX(2), - ] - H = qml.Hamiltonian(coeffs_expected, obs_expected) - - assert H.compare(H1 @ H2) - - -@pytest.mark.usefixtures("legacy_opmath_only") -class TestHamiltonianArithmeticAutograd: - """Tests creation of Hamiltonians using arithmetic - operations with autograd tensor coefficients.""" - - @pytest.mark.autograd - def test_hamiltonian_equal(self): - """Tests equality""" - coeffs = pnp.array([0.5, -1.6]) - obs = [qml.PauliX(0), qml.PauliY(1)] - H1 = qml.Hamiltonian(coeffs, obs) - - coeffs2 = pnp.array([-1.6, 0.5]) - obs2 = [qml.PauliY(1), qml.PauliX(0)] - H2 = qml.Hamiltonian(coeffs2, obs2) - - assert H1.compare(H2) - - @pytest.mark.autograd - def test_hamiltonian_add(self): - """Tests that Hamiltonians are added correctly""" - coeffs = pnp.array([0.5, -1.6]) - obs = [qml.PauliX(0), qml.PauliY(1)] - H1 = qml.Hamiltonian(coeffs, obs) - - coeffs2 = pnp.array([0.5, -0.4]) - H2 = qml.Hamiltonian(coeffs2, obs) - - coeffs_expected = pnp.array([1.0, -2.0]) - H = qml.Hamiltonian(coeffs_expected, obs) - - assert H.compare(H1 + H2) - - H1 += H2 - assert H.compare(H1) - - @pytest.mark.autograd - def test_hamiltonian_sub(self): - """Tests that Hamiltonians are subtracted correctly""" - coeffs = pnp.array([1.0, -2.0]) - obs = [qml.PauliX(0), qml.PauliY(1)] - H1 = qml.Hamiltonian(coeffs, obs) - - coeffs2 = pnp.array([0.5, -0.4]) - H2 = qml.Hamiltonian(coeffs2, obs) - - coeffs_expected = pnp.array([0.5, -1.6]) - H = qml.Hamiltonian(coeffs_expected, obs) - - assert H.compare(H1 - H2) - - H1 -= H2 - assert H.compare(H1) - - @pytest.mark.autograd - def test_hamiltonian_matmul(self): - """Tests that Hamiltonians are tensored correctly""" - coeffs = pnp.array([1.0, 2.0]) - obs = [qml.PauliX(0), qml.PauliY(1)] - H1 = qml.Hamiltonian(coeffs, obs) - - coeffs2 = pnp.array([-1.0, -2.0]) - obs2 = [qml.PauliX(2), qml.PauliY(3)] - H2 = qml.Hamiltonian(coeffs2, obs2) - - coeffs_expected = pnp.array([-4.0, -2.0, -2.0, -1.0]) - obs_expected = [ - qml.PauliY(1) @ qml.PauliY(3), - qml.PauliX(0) @ qml.PauliY(3), - qml.PauliX(2) @ qml.PauliY(1), - qml.PauliX(0) @ qml.PauliX(2), - ] - H = qml.Hamiltonian(coeffs_expected, obs_expected) - - assert H.compare(H1 @ H2) - - -with qml.operation.disable_new_opmath_cm(warn=False): - TEST_SPARSE_MATRIX = [ - ( - [1, -0.45], - [qml.PauliZ(0) @ qml.PauliZ(1), qml.PauliY(0) @ qml.PauliZ(1)], - None, - np.array( - [ - [1.0 + 0.0j, 0.0 + 0.0j, 0.0 + 0.45j, 0.0 + 0.0j], - [0.0 + 0.0j, -1.0 + 0.0j, 0.0 + 0.0j, 0.0 - 0.45j], - [0.0 - 0.45j, 0.0 + 0.0j, -1.0 + 0.0j, 0.0 + 0.0j], - [0.0 + 0.0j, 0.0 + 0.45j, 0.0 + 0.0j, 1.0 + 0.0j], - ] - ), - ), - ( - [0.1], - [qml.PauliZ("b") @ qml.PauliX("a")], - ["a", "c", "b"], - np.array( - [ - [ - 0.0 + 0.0j, - 0.0 + 0.0j, - 0.0 + 0.0j, - 0.0 + 0.0j, - 0.1 + 0.0j, - 0.0 + 0.0j, - 0.0 + 0.0j, - 0.0 + 0.0j, - ], - [ - 0.0 + 0.0j, - 0.0 + 0.0j, - 0.0 + 0.0j, - 0.0 + 0.0j, - 0.0 + 0.0j, - -0.1 + 0.0j, - 0.0 + 0.0j, - 0.0 + 0.0j, - ], - [ - 0.0 + 0.0j, - 0.0 + 0.0j, - 0.0 + 0.0j, - 0.0 + 0.0j, - 0.0 + 0.0j, - 0.0 + 0.0j, - 0.1 + 0.0j, - 0.0 + 0.0j, - ], - [ - 0.0 + 0.0j, - 0.0 + 0.0j, - 0.0 + 0.0j, - 0.0 + 0.0j, - 0.0 + 0.0j, - 0.0 + 0.0j, - 0.0 + 0.0j, - -0.1 + 0.0j, - ], - [ - 0.1 + 0.0j, - 0.0 + 0.0j, - 0.0 + 0.0j, - 0.0 + 0.0j, - 0.0 + 0.0j, - 0.0 + 0.0j, - 0.0 + 0.0j, - 0.0 + 0.0j, - ], - [ - 0.0 + 0.0j, - -0.1 + 0.0j, - 0.0 + 0.0j, - 0.0 + 0.0j, - 0.0 + 0.0j, - 0.0 + 0.0j, - 0.0 + 0.0j, - 0.0 + 0.0j, - ], - [ - 0.0 + 0.0j, - 0.0 + 0.0j, - 0.1 + 0.0j, - 0.0 + 0.0j, - 0.0 + 0.0j, - 0.0 + 0.0j, - 0.0 + 0.0j, - 0.0 + 0.0j, - ], - [ - 0.0 + 0.0j, - 0.0 + 0.0j, - 0.0 + 0.0j, - -0.1 + 0.0j, - 0.0 + 0.0j, - 0.0 + 0.0j, - 0.0 + 0.0j, - 0.0 + 0.0j, - ], - ] - ), - ), - ( - [0.21, -0.78, 0.52], - [ - qml.PauliZ(0) @ qml.PauliZ(1), - qml.PauliX(0) @ qml.PauliZ(1), - qml.PauliY(0) @ qml.PauliZ(1), - ], - None, - np.array( - [ - [0.21 + 0.0j, 0.0 + 0.0j, -0.78 - 0.52j, 0.0 + 0.0j], - [0.0 + 0.0j, -0.21 + 0.0j, 0.0 + 0.0j, 0.78 + 0.52j], - [-0.78 + 0.52j, 0.0 + 0.0j, -0.21 + 0.0j, 0.0 + 0.0j], - [0.0 + 0.0j, 0.78 - 0.52j, 0.0 + 0.0j, 0.21 + 0.0j], - ] - ), - ), - ] - - -@pytest.mark.usefixtures("legacy_opmath_only") -class TestHamiltonianSparseMatrix: - """Tests for sparse matrix representation.""" - - @pytest.mark.parametrize(["coeffs", "obs", "wires", "ref_matrix"], TEST_SPARSE_MATRIX) - def test_sparse_matrix(self, coeffs, obs, wires, ref_matrix): - """Tests that sparse_hamiltonian returns a correct sparse matrix""" - H = qml.Hamiltonian(coeffs, obs) - - sparse_matrix = H.sparse_matrix(wire_order=wires) - - assert np.allclose(sparse_matrix.toarray(), ref_matrix) - - def test_sparse_format(self): - """Tests that sparse_hamiltonian returns a scipy.sparse.csr_matrix object""" - - coeffs = [-0.25, 0.75] - obs = [ - qml.PauliX(wires=[0]) @ qml.PauliZ(wires=[1]), - qml.PauliY(wires=[0]) @ qml.PauliZ(wires=[1]), - ] - H = qml.Hamiltonian(coeffs, obs) - - sparse_matrix = H.sparse_matrix() - - assert isinstance(sparse_matrix, scipy.sparse.csr_matrix) - - def test_observable_error(self): - """Tests that an error is thrown if the observables are themselves constructed from multi-qubit - operations.""" - with pytest.raises(ValueError, match="Can only sparsify Hamiltonians"): - H = qml.Hamiltonian( - [0.1], [qml.PauliZ("c") @ qml.Hermitian(np.eye(4), wires=["a", "b"])] - ) - H.sparse_matrix(wire_order=["a", "c", "b"]) - - -@pytest.mark.jax -@pytest.mark.usefixtures("legacy_opmath_only") -class TestHamiltonianArithmeticJax: - """Tests creation of Hamiltonians using arithmetic - operations with jax tensor coefficients.""" - - def test_hamiltonian_equal(self): - """Tests equality""" - coeffs = jnp.array([0.5, -1.6]) - obs = [qml.PauliX(0), qml.PauliY(1)] - H1 = qml.Hamiltonian(coeffs, obs) - - coeffs2 = jnp.array([-1.6, 0.5]) - obs2 = [qml.PauliY(1), qml.PauliX(0)] - H2 = qml.Hamiltonian(coeffs2, obs2) - - assert H1.compare(H2) - - def test_hamiltonian_add(self): - """Tests that Hamiltonians are added correctly""" - coeffs = jnp.array([0.5, -1.6]) - obs = [qml.PauliX(0), qml.PauliY(1)] - H1 = qml.Hamiltonian(coeffs, obs) - - coeffs2 = jnp.array([0.5, -0.4]) - H2 = qml.Hamiltonian(coeffs2, obs) - - coeffs_expected = jnp.array([1.0, -2.0]) - H = qml.Hamiltonian(coeffs_expected, obs) - - assert H.compare(H1 + H2) - - H1 += H2 - assert H.compare(H1) - - def test_hamiltonian_sub(self): - """Tests that Hamiltonians are subtracted correctly""" - - coeffs = jnp.array([1.0, -2.0]) - obs = [qml.PauliX(0), qml.PauliY(1)] - H1 = qml.Hamiltonian(coeffs, obs) - - coeffs2 = jnp.array([0.5, -0.4]) - H2 = qml.Hamiltonian(coeffs2, obs) - - coeffs_expected = jnp.array([0.5, -1.6]) - H = qml.Hamiltonian(coeffs_expected, obs) - - assert H.compare(H1 - H2) - - H1 -= H2 - assert H.compare(H1) - - def test_hamiltonian_matmul(self): - """Tests that Hamiltonians are tensored correctly""" - coeffs = jnp.array([1.0, 2.0]) - obs = [qml.PauliX(0), qml.PauliY(1)] - H1 = qml.Hamiltonian(coeffs, obs) - - coeffs2 = jnp.array([-1.0, -2.0]) - obs2 = [qml.PauliX(2), qml.PauliY(3)] - H2 = qml.Hamiltonian(coeffs2, obs2) - - coeffs_expected = jnp.array([-4.0, -2.0, -2.0, -1.0]) - obs_expected = [ - qml.PauliY(1) @ qml.PauliY(3), - qml.PauliX(0) @ qml.PauliY(3), - qml.PauliX(2) @ qml.PauliY(1), - qml.PauliX(0) @ qml.PauliX(2), - ] - H = qml.Hamiltonian(coeffs_expected, obs_expected) - - assert H.compare(H1 @ H2) - - -@pytest.mark.usefixtures("legacy_opmath_only") -class TestGrouping: - """Tests for the grouping functionality""" - - def test_indentities_preserved(self): - """Tests that the grouping indices do not drop identity terms when the wire order is nonstandard.""" - - obs = [qml.PauliZ(1), qml.PauliZ(0), qml.Identity(0)] - - H = qml.Hamiltonian([1.0, 1.0, 1.0], obs, grouping_type="qwc") - assert H.grouping_indices == ((0, 1, 2),) - - def test_grouping_is_correct_kwarg(self): - """Basic test checking that grouping with a kwarg works as expected""" - a = qml.PauliX(0) - b = qml.PauliX(1) - c = qml.PauliZ(0) - obs = [a, b, c] - coeffs = [1.0, 2.0, 3.0] - - H = qml.Hamiltonian(coeffs, obs, grouping_type="qwc") - assert H.grouping_indices == ((0, 1), (2,)) - - def test_grouping_is_correct_compute_grouping(self): - """Basic test checking that grouping with compute_grouping works as expected""" - a = qml.PauliX(0) - b = qml.PauliX(1) - c = qml.PauliZ(0) - obs = [a, b, c] - coeffs = [1.0, 2.0, 3.0] - - H = qml.Hamiltonian(coeffs, obs, grouping_type="qwc") - H.compute_grouping() - assert H.grouping_indices == ((0, 1), (2,)) - - def test_set_grouping(self): - """Test that we can set grouping indices.""" - H = qml.Hamiltonian([1.0, 2.0, 3.0], [qml.PauliX(0), qml.PauliX(1), qml.PauliZ(0)]) - H.grouping_indices = [[0, 1], [2]] - - assert H.grouping_indices == ((0, 1), (2,)) - - def test_set_grouping_error(self): - """Test that grouping indices are validated.""" - H = qml.Hamiltonian([1.0, 2.0, 3.0], [qml.PauliX(0), qml.PauliX(1), qml.PauliZ(0)]) - - with pytest.raises(ValueError, match="The grouped index value"): - H.grouping_indices = [[3, 1], [2]] - - with pytest.raises(ValueError, match="The grouped index value"): - H.grouping_indices = "a" - - def test_grouping_for_non_groupable_hamiltonians(self): - """Test that grouping is computed correctly, even if no observables commute""" - a = qml.PauliX(0) - b = qml.PauliY(0) - c = qml.PauliZ(0) - obs = [a, b, c] - coeffs = [1.0, 2.0, 3.0] - - H = qml.Hamiltonian(coeffs, obs, grouping_type="qwc") - assert H.grouping_indices == ((0,), (1,), (2,)) - - def test_grouping_is_reset_when_simplifying(self): - """Tests that calling simplify() resets the grouping""" - obs = [qml.PauliX(0), qml.PauliX(1), qml.PauliZ(0)] - coeffs = [1.0, 2.0, 3.0] - - H = qml.Hamiltonian(coeffs, obs, grouping_type="qwc") - assert H.grouping_indices is not None - - H.simplify() - assert H.grouping_indices is None - - def test_grouping_does_not_alter_queue(self): - """Tests that grouping is invisible to the queue.""" - a = qml.PauliX(0) - b = qml.PauliX(1) - c = qml.PauliZ(0) - obs = [a, b, c] - coeffs = [1.0, 2.0, 3.0] - - with qml.queuing.AnnotatedQueue() as q: - H = qml.Hamiltonian(coeffs, obs, grouping_type="qwc") - - assert q.queue == [H] - - def test_grouping_method_can_be_set(self): - r"""Tests that the grouping method can be controlled by kwargs. - This is done by changing from default to 'lf' and checking the result.""" - # Create a graph with unique solution so that test does not depend on solver/implementation - a = qml.PauliX(0) - b = qml.PauliX(0) - c = qml.PauliZ(0) - obs = [a, b, c] - coeffs = [1.0, 2.0, 3.0] - - # compute grouping during construction - H2 = qml.Hamiltonian(coeffs, obs, grouping_type="qwc", method="lf") - assert set(H2.grouping_indices) == set(((0, 1), (2,))) - - # compute grouping separately - H3 = qml.Hamiltonian(coeffs, obs, grouping_type=None) - H3.compute_grouping(method="lf") - assert set(H3.grouping_indices) == set(((0, 1), (2,))) - - -@pytest.mark.usefixtures("legacy_opmath_only") -class TestHamiltonianEvaluation: - """Test the usage of a Hamiltonian as an observable""" - - @pytest.mark.parametrize("coeffs, param, interface", COEFFS_PARAM_INTERFACE) - def test_vqe_forward_different_coeff_types(self, coeffs, param, interface): - """Check that manually splitting a Hamiltonian expectation has the same - result as passing the Hamiltonian as an observable""" - device = qml.device("default.qubit", wires=2) - H = qml.Hamiltonian(coeffs, [qml.PauliX(0), qml.PauliZ(0)]) - - @qml.qnode(device, interface=interface) - def circuit(): - qml.RX(param, wires=0) - qml.RY(param, wires=0) - return qml.expval(H) - - @qml.qnode(device, interface=interface) - def node1(): - qml.RX(param, wires=0) - qml.RY(param, wires=0) - return qml.expval(qml.PauliX(0)) - - @qml.qnode(device, interface=interface) - def node2(): - qml.RX(param, wires=0) - qml.RY(param, wires=0) - return qml.expval(qml.PauliZ(0)) - - res = circuit() - res_expected = coeffs[0] * node1() + coeffs[1] * node2() - assert np.isclose(res, res_expected) - - def test_simplify_reduces_tape_parameters(self): - """Test that simplifying a Hamiltonian reduces the number of parameters on a tape""" - device = qml.device("default.qubit", wires=2) - - @qml.qnode(device) - def circuit(): - qml.RY(0.1, wires=0) - return qml.expval( - qml.simplify(qml.Hamiltonian([1.0, 2.0], [qml.PauliX(1), qml.PauliX(1)])) - ) - - circuit() - pars = circuit.qtape.get_parameters(trainable_only=False) - # simplify worked and added 1. and 2. - assert pars == [0.1, 3.0] - - -@pytest.mark.usefixtures("legacy_opmath_only") -class TestHamiltonianDifferentiation: - """Test that the Hamiltonian coefficients are differentiable""" - - @pytest.mark.parametrize("simplify", [True, False]) - @pytest.mark.parametrize("group", [None, "qwc"]) - def test_trainable_coeffs_paramshift(self, simplify, group): - """Test the parameter-shift method by comparing the differentiation of linearly combined subcircuits - with the differentiation of a Hamiltonian expectation""" - coeffs = pnp.array([-0.05, 0.17], requires_grad=True) - param = pnp.array(1.7, requires_grad=True) - - # differentiating a circuit with measurement expval(H) - @qml.qnode(dev, diff_method="parameter-shift") - def circuit(coeffs, param): - qml.RX(param, wires=0) - qml.RY(param, wires=0) - return qml.expval( - qml.simplify(qml.Hamiltonian(coeffs, [qml.X(0), qml.Z(0)], grouping_type=group)) - if simplify - else qml.Hamiltonian(coeffs, [qml.X(0), qml.Z(0)], grouping_type=group) - ) - - grad_fn = qml.grad(circuit) - grad = grad_fn(coeffs, param) - - # differentiating a cost that combines circuits with - # measurements expval(Pauli) - half1 = qml.QNode(circuit1, dev, diff_method="parameter-shift") - half2 = qml.QNode(circuit2, dev, diff_method="parameter-shift") - - def combine(coeffs, param): - return coeffs[0] * half1(param) + coeffs[1] * half2(param) - - grad_fn_expected = qml.grad(combine) - grad_expected = grad_fn_expected(coeffs, param) - - assert np.allclose(grad[0], grad_expected[0]) - assert np.allclose(grad[1], grad_expected[1]) - - def test_nontrainable_coeffs_paramshift(self): - """Test the parameter-shift method if the coefficients are explicitly set non-trainable - by not passing them to the qnode.""" - coeffs = np.array([-0.05, 0.17]) - param = pnp.array(1.7, requires_grad=True) - - # differentiating a circuit with measurement expval(H) - @qml.qnode(dev, diff_method="parameter-shift") - def circuit(param): - qml.RX(param, wires=0) - qml.RY(param, wires=0) - return qml.expval( - qml.Hamiltonian( - coeffs, - [qml.PauliX(0), qml.PauliZ(0)], - ) - ) - - grad_fn = qml.grad(circuit) - grad = grad_fn(param) - - # differentiating a cost that combines circuits with - # measurements expval(Pauli) - half1 = qml.QNode(circuit1, dev, diff_method="parameter-shift") - half2 = qml.QNode(circuit2, dev, diff_method="parameter-shift") - - def combine(param): - return coeffs[0] * half1(param) + coeffs[1] * half2(param) - - grad_fn_expected = qml.grad(combine) - grad_expected = grad_fn_expected(param) - - assert np.allclose(grad, grad_expected) - - @pytest.mark.autograd - @pytest.mark.parametrize("simplify", [True, False]) - @pytest.mark.parametrize("group", [None, "qwc"]) - def test_trainable_coeffs_autograd(self, simplify, group): - """Test the autograd interface by comparing the differentiation of linearly combined subcircuits - with the differentiation of a Hamiltonian expectation""" - coeffs = pnp.array([-0.05, 0.17], requires_grad=True) - param = pnp.array(1.7, requires_grad=True) - - # differentiating a circuit with measurement expval(H) - @qml.qnode(dev, interface="autograd") - def circuit(coeffs, param): - qml.RX(param, wires=0) - qml.RY(param, wires=0) - return qml.expval( - qml.simplify(qml.Hamiltonian(coeffs, [qml.X(0), qml.Z(0)], grouping_type=group)) - if simplify - else qml.ops.Hamiltonian(coeffs, [qml.X(0), qml.Z(0)], grouping_type=group) - ) - - grad_fn = qml.grad(circuit) - grad = grad_fn(coeffs, param) - - # differentiating a cost that combines circuits with - # measurements expval(Pauli) - half1 = qml.QNode(circuit1, dev, interface="autograd") - half2 = qml.QNode(circuit2, dev, interface="autograd") - - def combine(coeffs, param): - return coeffs[0] * half1(param) + coeffs[1] * half2(param) - - grad_fn_expected = qml.grad(combine) - grad_expected = grad_fn_expected(coeffs, param) - - assert np.allclose(grad[0], grad_expected[0]) - assert np.allclose(grad[1], grad_expected[1]) - - @pytest.mark.autograd - def test_nontrainable_coeffs_autograd(self): - """Test the autograd interface if the coefficients are explicitly set non-trainable""" - coeffs = pnp.array([-0.05, 0.17], requires_grad=False) - param = pnp.array(1.7, requires_grad=True) - - # differentiating a circuit with measurement expval(H) - @qml.qnode(dev, interface="autograd") - def circuit(coeffs, param): - qml.RX(param, wires=0) - qml.RY(param, wires=0) - return qml.expval(qml.Hamiltonian(coeffs, [qml.PauliX(0), qml.PauliZ(0)])) - - grad_fn = qml.grad(circuit) - grad = grad_fn(coeffs, param) - - # differentiating a cost that combines circuits with - # measurements expval(Pauli) - half1 = qml.QNode(circuit1, dev, interface="autograd") - half2 = qml.QNode(circuit2, dev, interface="autograd") - - def combine(coeffs, param): - return coeffs[0] * half1(param) + coeffs[1] * half2(param) - - grad_fn_expected = qml.grad(combine) - grad_expected = grad_fn_expected(coeffs, param) - - assert np.allclose(grad, grad_expected) - - @pytest.mark.jax - @pytest.mark.parametrize("simplify", [True, False]) - @pytest.mark.parametrize("group", [None, "qwc"]) - def test_trainable_coeffs_jax(self, simplify, group): - """Test the jax interface by comparing the differentiation of linearly - combined subcircuits with the differentiation of a Hamiltonian expectation""" - - coeffs = jnp.array([-0.05, 0.17]) - param = jnp.array(1.7) - - # differentiating a circuit with measurement expval(H) - @qml.qnode(dev, interface="jax", diff_method="backprop") - def circuit(coeffs, param): - qml.RX(param, wires=0) - qml.RY(param, wires=0) - return qml.expval( - qml.simplify(qml.Hamiltonian(coeffs, [qml.X(0), qml.Z(0)], grouping_type=group)) - if simplify - else qml.ops.Hamiltonian(coeffs, [qml.X(0), qml.Z(0)], grouping_type=group) - ) - - grad_fn = jax.grad(circuit) - grad = grad_fn(coeffs, param) - - # differentiating a cost that combines circuits with - # measurements expval(Pauli) - half1 = qml.QNode(circuit1, dev, interface="jax", diff_method="backprop") - half2 = qml.QNode(circuit2, dev, interface="jax", diff_method="backprop") - - def combine(coeffs, param): - return coeffs[0] * half1(param) + coeffs[1] * half2(param) - - grad_fn_expected = jax.grad(combine) - grad_expected = grad_fn_expected(coeffs, param) - - assert np.allclose(grad[0], grad_expected[0]) - assert np.allclose(grad[1], grad_expected[1]) - - @pytest.mark.jax - def test_nontrainable_coeffs_jax(self): - """Test the jax interface if the coefficients are explicitly set non-trainable""" - coeffs = np.array([-0.05, 0.17]) - param = jnp.array(1.7) - - # differentiating a circuit with measurement expval(H) - @qml.qnode(dev, interface="jax", diff_method="backprop") - def circuit(coeffs, param): - qml.RX(param, wires=0) - qml.RY(param, wires=0) - return qml.expval(qml.Hamiltonian(coeffs, [qml.PauliX(0), qml.PauliZ(0)])) - - grad_fn = jax.grad(circuit, argnums=(1)) - grad = grad_fn(coeffs, param) - - # differentiating a cost that combines circuits with - # measurements expval(Pauli) - half1 = qml.QNode(circuit1, dev, interface="jax", diff_method="backprop") - half2 = qml.QNode(circuit2, dev, interface="jax", diff_method="backprop") - - def combine(coeffs, param): - return coeffs[0] * half1(param) + coeffs[1] * half2(param) - - grad_fn_expected = jax.grad(combine, argnums=(1)) - grad_expected = grad_fn_expected(coeffs, param) - - assert np.allclose(grad, grad_expected) - - @pytest.mark.torch - @pytest.mark.parametrize("simplify", [True, False]) - @pytest.mark.parametrize("group", [None, "qwc"]) - def test_trainable_coeffs_torch(self, simplify, group): - """Test the torch interface by comparing the differentiation of linearly combined subcircuits - with the differentiation of a Hamiltonian expectation""" - coeffs = torch.tensor([-0.05, 0.17], requires_grad=True) - param = torch.tensor(1.7, requires_grad=True) - - # differentiating a circuit with measurement expval(H) - @qml.qnode(dev, interface="torch", diff_method="backprop") - def circuit(coeffs, param): - qml.RX(param, wires=0) - qml.RY(param, wires=0) - return qml.expval( - qml.simplify(qml.Hamiltonian(coeffs, [qml.X(0), qml.Z(0)], grouping_type=group)) - if simplify - else qml.ops.Hamiltonian(coeffs, [qml.X(0), qml.Z(0)], grouping_type=group) - ) - - res = circuit(coeffs, param) - res.backward() # pylint:disable=no-member - grad = (coeffs.grad, param.grad) - - # differentiating a cost that combines circuits with - # measurements expval(Pauli) - - # we need to create new tensors here - coeffs2 = torch.tensor([-0.05, 0.17], requires_grad=True) - param2 = torch.tensor(1.7, requires_grad=True) - - half1 = qml.QNode(circuit1, dev, interface="torch", diff_method="backprop") - half2 = qml.QNode(circuit2, dev, interface="torch", diff_method="backprop") - - def combine(coeffs, param): - return coeffs[0] * half1(param) + coeffs[1] * half2(param) - - res_expected = combine(coeffs2, param2) - res_expected.backward() - grad_expected = (coeffs2.grad, param2.grad) - - assert np.allclose(grad[0], grad_expected[0]) - assert np.allclose(grad[1], grad_expected[1]) - - @pytest.mark.torch - def test_nontrainable_coeffs_torch(self): - """Test the torch interface if the coefficients are explicitly set non-trainable""" - coeffs = torch.tensor([-0.05, 0.17], requires_grad=False) - param = torch.tensor(1.7, requires_grad=True) - - # differentiating a circuit with measurement expval(H) - @qml.qnode(dev, interface="torch", diff_method="backprop") - def circuit(coeffs, param): - qml.RX(param, wires=0) - qml.RY(param, wires=0) - return qml.expval( - qml.Hamiltonian( - coeffs, - [qml.PauliX(0), qml.PauliZ(0)], - ) - ) - - res = circuit(coeffs, param) - res.backward() # pylint:disable=no-member - - # differentiating a cost that combines circuits with - # measurements expval(Pauli) - - # we need to create new tensors here - coeffs2 = torch.tensor([-0.05, 0.17], requires_grad=False) - param2 = torch.tensor(1.7, requires_grad=True) - - half1 = qml.QNode(circuit1, dev, interface="torch", diff_method="backprop") - half2 = qml.QNode(circuit2, dev, interface="torch", diff_method="backprop") - - def combine(coeffs, param): - return coeffs[0] * half1(param) + coeffs[1] * half2(param) - - res_expected = combine(coeffs2, param2) - res_expected.backward() - - assert coeffs.grad is None - assert np.allclose(param.grad, param2.grad) - - @pytest.mark.tf - @pytest.mark.parametrize("simplify", [True, False]) - @pytest.mark.parametrize("group", [None, "qwc"]) - def test_trainable_coeffs_tf(self, simplify, group): - """Test the tf interface by comparing the differentiation of linearly combined subcircuits - with the differentiation of a Hamiltonian expectation""" - coeffs = tf.Variable([-0.05, 0.17], dtype=tf.double) - param = tf.Variable(1.7, dtype=tf.double) - - # differentiating a circuit with measurement expval(H) - @qml.qnode(dev, interface="tf", diff_method="backprop") - def circuit(coeffs, param): - qml.RX(param, wires=0) - qml.RY(param, wires=0) - return qml.expval( - qml.simplify(qml.Hamiltonian(coeffs, [qml.X(0), qml.Z(0)], grouping_type=group)) - if simplify - else qml.ops.Hamiltonian(coeffs, [qml.X(0), qml.Z(0)], grouping_type=group) - ) - - with tf.GradientTape() as tape: - res = circuit(coeffs, param) - - grad = tape.gradient(res, [coeffs, param]) - - # differentiating a cost that combines circuits with - # measurements expval(Pauli) - - # we need to create new tensors here - coeffs2 = tf.Variable([-0.05, 0.17], dtype=tf.double) - param2 = tf.Variable(1.7, dtype=tf.double) - half1 = qml.QNode(circuit1, dev, interface="tf", diff_method="backprop") - half2 = qml.QNode(circuit2, dev, interface="tf", diff_method="backprop") - - def combine(coeffs, param): - return coeffs[0] * half1(param) + coeffs[1] * half2(param) - - with tf.GradientTape() as tape2: - res_expected = combine(coeffs2, param2) - grad_expected = tape2.gradient(res_expected, [coeffs2, param2]) - - assert np.allclose(grad[0], grad_expected[0]) - assert np.allclose(grad[1], grad_expected[1]) - - @pytest.mark.tf - def test_nontrainable_coeffs_tf(self): - """Test the tf interface if the coefficients are explicitly set non-trainable""" - - coeffs = tf.constant([-0.05, 0.17], dtype=tf.double) - param = tf.Variable(1.7, dtype=tf.double) - - # differentiating a circuit with measurement expval(H) - @qml.qnode(dev, interface="tf", diff_method="backprop") - def circuit(coeffs, param): - qml.RX(param, wires=0) - qml.RY(param, wires=0) - return qml.expval( - qml.Hamiltonian( - coeffs, - [qml.PauliX(0), qml.PauliZ(0)], - ) - ) - - with tf.GradientTape() as tape: - res = circuit(coeffs, param) - grad = tape.gradient(res, [coeffs, param]) - - # differentiating a cost that combines circuits with - # measurements expval(Pauli) - - # we need to create new tensors here - coeffs2 = tf.constant([-0.05, 0.17], dtype=tf.double) - param2 = tf.Variable(1.7, dtype=tf.double) - half1 = qml.QNode(circuit1, dev, interface="tf", diff_method="backprop") - half2 = qml.QNode(circuit2, dev, interface="tf", diff_method="backprop") - - def combine(coeffs, param): - return coeffs[0] * half1(param) + coeffs[1] * half2(param) - - with tf.GradientTape() as tape2: - res_expected = combine(coeffs2, param2) - grad_expected = tape2.gradient(res_expected, [coeffs2, param2]) - - assert grad[0] is None - assert np.allclose(grad[1], grad_expected[1]) - - def test_not_supported_by_adjoint_differentiation(self): - """Test that error is raised when attempting the adjoint differentiation method.""" - device = qml.device("default.qubit", wires=2) - - coeffs = pnp.array([-0.05, 0.17], requires_grad=True) - param = pnp.array(1.7, requires_grad=True) - - @qml.qnode(device, diff_method="adjoint") - def circuit(coeffs, param): - qml.RX(param, wires=0) - qml.RY(param, wires=0) - return qml.expval( - qml.Hamiltonian( - coeffs, - [qml.PauliX(0), qml.PauliZ(0)], - ) - ) - - grad_fn = qml.grad(circuit) - with pytest.raises( - qml.QuantumFunctionError, - match="does not support adjoint with requested circuit.", - ): - grad_fn(coeffs, param) diff --git a/tests/ops/qubit/test_parametric_ops.py b/tests/ops/qubit/test_parametric_ops.py index 66a44b98e9b..f5c29ea4d32 100644 --- a/tests/ops/qubit/test_parametric_ops.py +++ b/tests/ops/qubit/test_parametric_ops.py @@ -231,7 +231,7 @@ def test_pcphase_raises_error(self): class TestParameterFrequencies: - @pytest.mark.usefixtures("use_legacy_and_new_opmath") + @pytest.mark.parametrize("op", PARAMETRIZED_OPERATIONS) def test_parameter_frequencies_match_generator(self, op, tol): if not qml.operation.has_gen(op): @@ -3011,7 +3011,6 @@ def test_init_incorrect_pauli_word_length_error(self, pauli_word, wires): ("IIIXYZ"), ], ) - @pytest.mark.usefixtures("use_legacy_and_new_opmath") def test_multirz_generator(self, pauli_word): """Test that the generator of the MultiRZ gate is correct.""" op = qml.PauliRot(0.3, pauli_word, wires=range(len(pauli_word))) @@ -3054,19 +3053,6 @@ def test_pauli_rot_identity_torch(self, torch_device, theta): exp = torch.tensor(np.diag([val, val]), device=torch_device) assert qml.math.allclose(mat, exp) - @pytest.mark.usefixtures("legacy_opmath_only") - def test_pauli_rot_generator_legacy_opmath(self): - """Test that the generator of the PauliRot operation - is correctly returned.""" - op = qml.PauliRot(0.65, "ZY", wires=["a", 7]) - gen, coeff = qml.generator(op) - expected = qml.PauliZ("a") @ qml.PauliY(7) - - assert coeff == -0.5 - assert gen.operands[0].name == expected.obs[0].name - assert gen.operands[1].wires == expected.obs[1].wires - - @pytest.mark.usefixtures("new_opmath_only") def test_pauli_rot_generator(self): """Test that the generator of the PauliRot operation is correctly returned.""" @@ -3246,7 +3232,6 @@ def decomp_circuit(theta): assert np.allclose(qml.jacobian(circuit)(angle), qml.jacobian(decomp_circuit)(angle)) @pytest.mark.parametrize("qubits", range(3, 6)) - @pytest.mark.usefixtures("use_legacy_and_new_opmath") def test_multirz_generator(self, qubits, mocker): """Test that the generator of the MultiRZ gate is correct.""" op = qml.MultiRZ(0.3, wires=range(qubits)) diff --git a/tests/ops/qubit/test_qchem_ops.py b/tests/ops/qubit/test_qchem_ops.py index f652da4c5a0..8b11969ffda 100644 --- a/tests/ops/qubit/test_qchem_ops.py +++ b/tests/ops/qubit/test_qchem_ops.py @@ -46,7 +46,7 @@ class TestParameterFrequencies: - @pytest.mark.usefixtures("use_legacy_and_new_opmath") + @pytest.mark.parametrize("op", PARAMETRIZED_QCHEM_OPERATIONS) def test_parameter_frequencies_match_generator(self, op, tol): if not qml.operation.has_gen(op): @@ -1234,7 +1234,6 @@ def test_label_method(op, label1, label2, label3): assert op.label(decimals=0) == label3 -@pytest.mark.usefixtures("use_legacy_and_new_opmath") @pytest.mark.parametrize("op", PARAMETRIZED_QCHEM_OPERATIONS) def test_generators(op): """Check that the type of the generator returned by the qchem ops is diff --git a/tests/ops/qutrit/test_qutrit_observables.py b/tests/ops/qutrit/test_qutrit_observables.py index be2ac895869..5ab0111b385 100644 --- a/tests/ops/qutrit/test_qutrit_observables.py +++ b/tests/ops/qutrit/test_qutrit_observables.py @@ -372,19 +372,6 @@ def test_matrix(self, index, mat, eigs, tol): assert np.allclose(res_static, mat) assert np.allclose(res_dynamic, mat) - @pytest.mark.usefixtures("legacy_opmath_only") - def test_obs_data(self): - """Test that the _obs_data() method of qml.GellMann returns the correct - observable data.""" - ob1 = qml.GellMann(wires=0, index=2) - ob2 = qml.GellMann(wires=0, index=2) @ qml.GellMann(wires=1, index=1) - - assert ob1._obs_data() == {("GellMann", qml.wires.Wires(0), (2,))} - assert ob2._obs_data() == { - ("GellMann", qml.wires.Wires(0), (2,)), - ("GellMann", qml.wires.Wires(1), (1,)), - } - @pytest.mark.parametrize("index, mat, eigs", GM_OBSERVABLES) def test_eigvals(self, index, mat, eigs, tol): """Test that the Gell-Mann eigenvalues are correct""" diff --git a/tests/optimize/test_momentum_qng.py b/tests/optimize/test_momentum_qng.py index e3153cadf3a..42e7ddd6fad 100644 --- a/tests/optimize/test_momentum_qng.py +++ b/tests/optimize/test_momentum_qng.py @@ -81,7 +81,6 @@ def circuit(params): var -= accum assert np.allclose([var1, var2], var) - @pytest.mark.usefixtures("use_legacy_and_new_opmath") def test_step_and_cost_autograd_with_gen_hamiltonian(self): """Test that the correct cost and step is returned after 8 optimization steps via the step_and_cost method for the MomentumQNG optimizer when the generator @@ -304,7 +303,7 @@ def gradient(params): # check final cost assert np.allclose(circuit(theta), -1, atol=1e-4) - def test_single_qubit_vqe_using_expval_h_multiple_input_params(self, tol, recwarn): + def test_single_qubit_vqe_using_expval_h_multiple_input_params(self, tol): """Test single-qubit VQE by returning qml.expval(H) in the QNode and check for the correct MomentumQNG value every step, the correct parameter updates, and correct cost after a few steps""" @@ -356,5 +355,3 @@ def gradient(params): # check final cost assert np.allclose(circuit(x, y), qml.eigvals(H).min(), atol=tol, rtol=0) - if qml.operation.active_new_opmath(): - assert len(recwarn) == 0 diff --git a/tests/optimize/test_qng.py b/tests/optimize/test_qng.py index a382a73aef3..21bf3d8ab0d 100644 --- a/tests/optimize/test_qng.py +++ b/tests/optimize/test_qng.py @@ -162,7 +162,6 @@ def circuit(params): assert np.allclose(step1, expected_step) assert np.allclose(step2, expected_step) - @pytest.mark.usefixtures("use_legacy_and_new_opmath") def test_step_and_cost_autograd_with_gen_hamiltonian(self): """Test that the correct cost and step is returned via the step_and_cost method for the QNG optimizer when the generator @@ -393,8 +392,7 @@ def gradient(params): # check final cost assert np.allclose(circuit(x, y), qml.eigvals(H).min(), atol=tol, rtol=0) - if qml.operation.active_new_opmath(): - assert len(recwarn) == 0 + assert len(recwarn) == 0 flat_dummy_array = np.linspace(-1, 1, 64) diff --git a/tests/pauli/grouping/test_pauli_group_observables.py b/tests/pauli/grouping/test_pauli_group_observables.py index c36dd7e94ff..a2256b8a0e0 100644 --- a/tests/pauli/grouping/test_pauli_group_observables.py +++ b/tests/pauli/grouping/test_pauli_group_observables.py @@ -22,7 +22,6 @@ import pennylane as qml from pennylane import Identity, PauliX, PauliY, PauliZ from pennylane import numpy as pnp -from pennylane.operation import Tensor from pennylane.pauli import are_identical_pauli_words, are_pauli_words_qwc from pennylane.pauli.grouping.group_observables import ( PauliGroupingStrategy, @@ -459,34 +458,6 @@ def test_return_list_coefficients(self): _, grouped_coeffs = group_observables(obs, coeffs) assert isinstance(grouped_coeffs[0], list) - @pytest.mark.usefixtures("legacy_opmath_only") - def test_return_new_opmath_legacy_opmath(self): - """Test that using new opmath causes grouped observables to have Prods instead of - Tensors""" - old_observables = [ - Tensor(PauliX(0), PauliZ(1)), - Tensor(PauliY(2), PauliZ(1)), - Tensor(PauliZ(1), PauliZ(2)), - ] - - old_groups = group_observables(old_observables) - - assert all(isinstance(o, Tensor) for g in old_groups for o in g) - - @pytest.mark.usefixtures("legacy_opmath_only") - def test_return_deactive_opmath_prod(self): - """Test that using new opmath causes grouped observables to have Prods instead of - Tensors""" - observables = [ - qml.prod(PauliX(0), PauliZ(1)), - qml.prod(PauliY(2), PauliZ(1)), - qml.prod(PauliZ(1), PauliZ(2)), - ] - - old_groups = group_observables(observables) - - assert all(isinstance(o, qml.ops.Prod) for g in old_groups for o in g) - def test_observables_on_no_wires(self): """Test that observables on no wires are stuck in the first group.""" @@ -511,7 +482,6 @@ def test_no_observables_with_wires(self): assert groups == [observables] assert coeffs == [[1, 2]] - @pytest.mark.usefixtures("new_opmath_only") def test_observables_on_no_wires_coeffs(self): """Test that observables on no wires are stuck in the first group and coefficients are tracked when provided.""" diff --git a/tests/pauli/test_conversion.py b/tests/pauli/test_conversion.py index 5e8267104b8..30ec54390b4 100644 --- a/tests/pauli/test_conversion.py +++ b/tests/pauli/test_conversion.py @@ -18,7 +18,6 @@ import pytest import pennylane as qml -from pennylane.operation import Tensor from pennylane.ops import Identity, PauliX, PauliY, PauliZ from pennylane.pauli import PauliSentence, PauliWord, pauli_sentence from pennylane.pauli.conversion import _generalized_pauli_decompose @@ -45,51 +44,49 @@ test_diff_matrix1 = [[[-2, -2 + 1j]], [[-2, -2 + 1j], [-1, -1j]]] test_diff_matrix2 = [[[-2, -2 + 1j], [-2 - 1j, 0]], [[2.5, -0.5], [-0.5, 2.5]]] -with warnings.catch_warnings(): - warnings.filterwarnings("ignore", "qml.ops.Hamiltonian uses", qml.PennyLaneDeprecationWarning) - hamiltonian_ps = ( - ( - qml.ops.Hamiltonian([], []), - PauliSentence(), - ), - ( - qml.ops.Hamiltonian([2], [qml.PauliZ(wires=0)]), - PauliSentence({PauliWord({0: "Z"}): 2}), +hamiltonian_ps = ( + ( + qml.Hamiltonian([], []), + PauliSentence(), + ), + ( + qml.Hamiltonian([2], [qml.PauliZ(wires=0)]), + PauliSentence({PauliWord({0: "Z"}): 2}), + ), + ( + qml.Hamiltonian([2], [qml.PauliZ(wires=0)]), + PauliSentence({PauliWord({0: "Z"}): 2}), + ), + ( + qml.Hamiltonian( + [2, -0.5], + [qml.PauliZ(wires=0), qml.prod(qml.X(wires=0), qml.Z(wires=1))], ), - ( - qml.Hamiltonian([2], [qml.PauliZ(wires=0)]), - PauliSentence({PauliWord({0: "Z"}): 2}), + PauliSentence( + { + PauliWord({0: "Z"}): 2, + PauliWord({0: "X", 1: "Z"}): -0.5, + } ), - ( - qml.ops.Hamiltonian( - [2, -0.5], - [qml.PauliZ(wires=0), qml.operation.Tensor(qml.X(wires=0), qml.Z(wires=1))], - ), - PauliSentence( - { - PauliWord({0: "Z"}): 2, - PauliWord({0: "X", 1: "Z"}): -0.5, - } - ), + ), + ( + qml.Hamiltonian( + [2, -0.5, 3.14], + [ + qml.PauliZ(wires=0), + qml.prod(qml.X(wires=0), qml.Z(wires="a")), + qml.Identity(wires="b"), + ], ), - ( - qml.ops.Hamiltonian( - [2, -0.5, 3.14], - [ - qml.PauliZ(wires=0), - qml.operation.Tensor(qml.X(wires=0), qml.Z(wires="a")), - qml.Identity(wires="b"), - ], - ), - PauliSentence( - { - PauliWord({0: "Z"}): 2, - PauliWord({0: "X", "a": "Z"}): -0.5, - PauliWord({}): 3.14, - } - ), + PauliSentence( + { + PauliWord({0: "Z"}): 2, + PauliWord({0: "X", "a": "Z"}): -0.5, + PauliWord({}): 3.14, + } ), - ) + ), +) class TestDecomposition: @@ -116,33 +113,22 @@ def test_hide_identity_true(self): when hide_identity=True""" H = np.array(np.diag([0, 0, 0, 1])) _, obs_list = qml.pauli_decompose(H, hide_identity=True).terms() - tensors = filter(lambda obs: isinstance(obs, Tensor), obs_list) + tensors = filter(lambda obs: isinstance(obs, qml.ops.Prod), obs_list) for tensor in tensors: - all_identities = all(isinstance(o, Identity) for o in tensor.obs) - no_identities = not any(isinstance(o, Identity) for o in tensor.obs) + all_identities = all(isinstance(o, Identity) for o in tensor.operands) + no_identities = not any(isinstance(o, Identity) for o in tensor.operands) assert all_identities or no_identities def test_hide_identity_true_all_identities(self): """Tests that the all identity operator remains even with hide_identity = True.""" H = np.eye(4) _, obs_list = qml.pauli_decompose(H, hide_identity=True).terms() - tensors = filter(lambda obs: isinstance(obs, Tensor), obs_list) + tensors = filter(lambda obs: isinstance(obs, qml.ops.Prod), obs_list) for tensor in tensors: - assert all(isinstance(o, Identity) for o in tensor.obs) + assert all(isinstance(o, Identity) for o in tensor.operands) - @pytest.mark.usefixtures("legacy_opmath_only") - @pytest.mark.parametrize("hide_identity", [True, False]) - @pytest.mark.parametrize("hamiltonian", test_hamiltonians) - def test_observable_types_legacy_opmath(self, hamiltonian, hide_identity): - """Tests that the Hamiltonian decomposes into a linear combination of Pauli words.""" - allowed_obs = (Tensor, Identity, PauliX, PauliY, PauliZ) - - _, decomposed_obs = qml.pauli_decompose(hamiltonian, hide_identity).terms() - assert all((isinstance(o, allowed_obs) for o in decomposed_obs)) - - @pytest.mark.usefixtures("new_opmath_only") @pytest.mark.parametrize("hide_identity", [True, False]) @pytest.mark.parametrize("hamiltonian", test_hamiltonians) def test_observable_types(self, hamiltonian, hide_identity): @@ -159,8 +145,8 @@ def test_result_length(self, hamiltonian): _, decomposed_obs = qml.pauli_decompose(hamiltonian).terms() n = int(np.log2(len(hamiltonian))) - tensors = filter(lambda obs: isinstance(obs, Tensor), decomposed_obs) - assert all(len(tensor.obs) == n for tensor in tensors) + tensors = filter(lambda obs: isinstance(obs, qml.ops.Prod), decomposed_obs) + assert all(len(tensor.operands) == n for tensor in tensors) # pylint: disable = consider-using-generator @pytest.mark.parametrize("hamiltonian", test_hamiltonians) @@ -259,36 +245,22 @@ def test_hide_identity_true(self): when hide_identity=True""" H = np.array(np.diag([0, 0, 0, 1])) _, obs_list = qml.pauli_decompose(H, hide_identity=True, check_hermitian=False).terms() - tensors = filter(lambda obs: isinstance(obs, Tensor), obs_list) + tensors = filter(lambda obs: isinstance(obs, qml.ops.Prod), obs_list) for tensor in tensors: - all_identities = all(isinstance(o, Identity) for o in tensor.obs) - no_identities = not any(isinstance(o, Identity) for o in tensor.obs) + all_identities = all(isinstance(o, Identity) for o in tensor.operands) + no_identities = not any(isinstance(o, Identity) for o in tensor.operands) assert all_identities or no_identities def test_hide_identity_true_all_identities(self): """Tests that the all identity operator remains even with hide_identity = True.""" H = np.eye(4) _, obs_list = qml.pauli_decompose(H, hide_identity=True, check_hermitian=False).terms() - tensors = filter(lambda obs: isinstance(obs, Tensor), obs_list) + tensors = filter(lambda obs: isinstance(obs, qml.ops.Prod), obs_list) for tensor in tensors: - assert all(isinstance(o, Identity) for o in tensor.obs) + assert all(isinstance(o, Identity) for o in tensor.operands) - @pytest.mark.usefixtures("legacy_opmath_only") - @pytest.mark.parametrize("hide_identity", [True, False]) - @pytest.mark.parametrize("hamiltonian", test_hamiltonians) - def test_observable_types_legacy_opmath(self, hamiltonian, hide_identity): - """Tests that the Hamiltonian decomposes into a linear combination of tensors, - the identity matrix, and Pauli matrices.""" - allowed_obs = (Tensor, Identity, PauliX, PauliY, PauliZ) - - _, decomposed_obs = qml.pauli_decompose( - hamiltonian, hide_identity, check_hermitian=False - ).terms() - assert all((isinstance(o, allowed_obs) for o in decomposed_obs)) - - @pytest.mark.usefixtures("new_opmath_only") @pytest.mark.parametrize("hide_identity", [True, False]) @pytest.mark.parametrize("hamiltonian", test_hamiltonians) def test_observable_types(self, hamiltonian, hide_identity): @@ -308,8 +280,8 @@ def test_result_length(self, hamiltonian): _, decomposed_obs = qml.pauli_decompose(hamiltonian, check_hermitian=False).terms() n = int(np.log2(len(hamiltonian))) - tensors = filter(lambda obs: isinstance(obs, Tensor), decomposed_obs) - assert all(len(tensor.obs) == n for tensor in tensors) + tensors = filter(lambda obs: isinstance(obs, qml.ops.Prod), decomposed_obs) + assert all(len(tensor.operands) == n for tensor in tensors) # pylint: disable = consider-using-generator @pytest.mark.parametrize("hamiltonian", test_hamiltonians) @@ -339,36 +311,7 @@ def test_to_paulisentence(self, hamiltonian): assert np.allclose(hamiltonian, ps.to_mat(range(num_qubits))) # pylint: disable = consider-using-generator - @pytest.mark.usefixtures("legacy_opmath_only") - @pytest.mark.parametrize("hide_identity", [True, False]) - @pytest.mark.parametrize("matrix", test_general_matrix) - def test_observable_types_general_legacy_opmath(self, matrix, hide_identity): - """Tests that the matrix decomposes into a linear combination of tensors, - the identity matrix, and Pauli matrices.""" - shape = matrix.shape - num_qubits = int(np.ceil(np.log2(max(shape)))) - allowed_obs = (Tensor, Identity, PauliX, PauliY, PauliZ) - - decomposed_coeff, decomposed_obs = qml.pauli_decompose( - matrix, hide_identity, check_hermitian=False - ).terms() - - assert all((isinstance(o, allowed_obs) for o in decomposed_obs)) - linear_comb = sum( - [ - decomposed_coeff[i] * qml.matrix(o, wire_order=range(num_qubits)) - for i, o in enumerate(decomposed_obs) - ] - ) - assert np.allclose(matrix, linear_comb[: shape[0], : shape[1]]) - - if not hide_identity: - tensors = filter(lambda obs: isinstance(obs, Tensor), decomposed_obs) - assert all(len(tensor.obs) == num_qubits for tensor in tensors) - - # pylint: disable = consider-using-generator - @pytest.mark.usefixtures("new_opmath_only") @pytest.mark.parametrize("hide_identity", [True, False]) @pytest.mark.parametrize("matrix", test_general_matrix) def test_observable_types_general(self, matrix, hide_identity): @@ -515,7 +458,6 @@ class TestPauliSentence: (qml.Identity(wires=0), PauliSentence({PauliWord({}): 1})), ) - @pytest.mark.usefixtures("use_legacy_and_new_opmath") @pytest.mark.parametrize("op, ps", pauli_op_ps) def test_pauli_ops(self, op, ps): """Test that PL Pauli ops are properly cast to a PauliSentence.""" @@ -537,13 +479,11 @@ def test_pauli_ops(self, op, ps): (qml.PauliX(wires=0) @ qml.PauliY(wires=0), PauliSentence({PauliWord({0: "Z"}): 1j})), ) - @pytest.mark.usefixtures("use_legacy_and_new_opmath") @pytest.mark.parametrize("op, ps", tensor_ps) def test_tensor(self, op, ps): """Test that Tensors of Pauli ops are properly cast to a PauliSentence.""" assert pauli_sentence(op) == ps - @pytest.mark.usefixtures("use_legacy_and_new_opmath") def test_tensor_raises_error(self): """Test that Tensors of non-Pauli ops raise error when cast to a PauliSentence.""" h_mat = np.array([[1, 1], [1, -1]]) @@ -553,15 +493,9 @@ def test_tensor_raises_error(self): with pytest.raises(ValueError, match="Op must be a linear combination of"): pauli_sentence(op) - @pytest.mark.filterwarnings( - "ignore:qml.ops.Hamiltonian uses:pennylane.PennyLaneDeprecationWarning" - ) - @pytest.mark.usefixtures("use_legacy_and_new_opmath") @pytest.mark.parametrize("op, ps", hamiltonian_ps) def test_hamiltonian(self, op, ps): """Test that a Hamiltonian is properly cast to a PauliSentence.""" - if qml.operation.active_new_opmath(): - op = qml.operation.convert_to_legacy_H(op) assert pauli_sentence(op) == ps operator_ps = ( @@ -585,14 +519,6 @@ def test_hamiltonian(self, op, ps): } ), ), - ( - qml.operation.Tensor(qml.PauliX(wires=0), qml.PauliZ(wires=1)), - PauliSentence( - { - PauliWord({0: "X", 1: "Z"}): 1, - } - ), - ), ( qml.sum( qml.s_prod(2, qml.PauliZ(wires=0)), @@ -627,7 +553,6 @@ def test_operator_private_ps(self, op, ps): qml.Hadamard(wires=0), qml.Hamiltonian([1, 2], [qml.Projector([0], wires=0), qml.PauliZ(wires=1)]), qml.RX(1.23, wires="a") + qml.PauliZ(wires=0), - qml.ops.Hamiltonian([1, 2], [qml.Projector([0], wires=0), qml.Z(1)]), ) @pytest.mark.parametrize("op", error_ps) diff --git a/tests/pauli/test_pauli_arithmetic.py b/tests/pauli/test_pauli_arithmetic.py index 6aee89c5dc9..0a26db4b9aa 100644 --- a/tests/pauli/test_pauli_arithmetic.py +++ b/tests/pauli/test_pauli_arithmetic.py @@ -22,7 +22,6 @@ import pennylane as qml from pennylane import numpy as np -from pennylane.operation import Tensor from pennylane.pauli.pauli_arithmetic import I, PauliSentence, PauliWord, X, Y, Z matI = np.eye(2) @@ -403,11 +402,6 @@ def test_operation(self, pw, op): assert pw_op.name == op.name assert pw_op.wires == op.wires - if isinstance(op, qml.ops.Prod): # pylint: disable=no-member - pw_tensor_op = pw.operation(get_as_tensor=True) - expected_tensor_op = qml.operation.Tensor(*op.operands) - qml.assert_equal(pw_tensor_op, expected_tensor_op) - def test_operation_empty(self): """Test that an empty PauliWord with wire_order returns Identity.""" op = PauliWord({}).operation(wire_order=[0, 1]) @@ -420,63 +414,6 @@ def test_operation_empty_nowires(self): res = pw4.operation() assert res == qml.Identity() - tup_pw_hamiltonian = ( - (PauliWord({0: X}), qml.Hamiltonian([1], [qml.PauliX(wires=0)])), - ( - pw1, - qml.Hamiltonian([1], [qml.operation.Tensor(qml.PauliX(wires=1), qml.PauliY(wires=2))]), - ), - ( - pw2, - qml.Hamiltonian( - [1], - [ - qml.operation.Tensor( - qml.PauliX(wires="a"), qml.PauliX(wires="b"), qml.PauliZ(wires="c") - ) - ], - ), - ), - ( - pw3, - qml.Hamiltonian( - [1], - [ - qml.operation.Tensor( - qml.PauliZ(wires=0), qml.PauliZ(wires="b"), qml.PauliZ(wires="c") - ) - ], - ), - ), - ) - - @pytest.mark.usefixtures("legacy_opmath_only") - @pytest.mark.parametrize("pw, h", tup_pw_hamiltonian) - def test_hamiltonian(self, pw, h): - """Test that a PauliWord can be cast to a Hamiltonian.""" - pw_h = pw.hamiltonian() - h = qml.operation.convert_to_legacy_H(h) - assert pw_h.compare(h) - - @pytest.mark.usefixtures("legacy_opmath_only") - def test_hamiltonian_empty(self): - """Test that an empty PauliWord with wire_order returns Identity Hamiltonian.""" - op = PauliWord({}).hamiltonian(wire_order=[0, 1]) - id = qml.Hamiltonian([1], [qml.Identity(wires=[0, 1])]) - assert op.compare(id) - - @pytest.mark.usefixtures("legacy_opmath_only") - def test_hamiltonian_empty_error(self): - """Test that a ValueError is raised if an empty PauliWord is - cast to a Hamiltonian.""" - with pytest.raises(ValueError, match="Can't get the Hamiltonian for an empty PauliWord."): - pw4.hamiltonian() - - def test_hamiltonian_deprecation(self): - """Test that the correct deprecation warning is raised when calling hamiltonian()""" - with pytest.warns(qml.PennyLaneDeprecationWarning, match="PauliWord.hamiltonian"): - _ = pw1.hamiltonian() - def test_pickling(self): """Check that pauliwords can be pickled and unpickled.""" pw = PauliWord({2: "X", 3: "Y", 4: "Z"}) @@ -1008,79 +945,6 @@ def test_operation_wire_order(self): qml.assert_equal(op, id) - tup_ps_hamiltonian = ( - (PauliSentence({PauliWord({0: X}): 1}), qml.Hamiltonian([1], [qml.PauliX(wires=0)])), - ( - ps1_hamiltonian, - qml.Hamiltonian( - [1.23, 4.0, -0.5], - [ - Tensor(qml.PauliX(wires=1), qml.PauliY(wires=2)), - Tensor(qml.PauliX(wires="a"), qml.PauliX(wires="b"), qml.PauliZ(wires="c")), - Tensor(qml.PauliZ(wires=0), qml.PauliZ(wires="b"), qml.PauliZ(wires="c")), - ], - ), - ), - ( - ps2_hamiltonian, - qml.Hamiltonian( - [-1.23, -4.0, 0.5], - [ - Tensor(qml.PauliX(wires=1), qml.PauliY(wires=2)), - Tensor(qml.PauliX(wires="a"), qml.PauliX(wires="b"), qml.PauliZ(wires="c")), - Tensor(qml.PauliZ(wires=0), qml.PauliZ(wires="b"), qml.PauliZ(wires="c")), - ], - ), - ), - ( - ps3, - qml.Hamiltonian( - [-0.5, 1.0], - [ - Tensor(qml.PauliZ(wires=0), qml.PauliZ(wires="b"), qml.PauliZ(wires="c")), - qml.Identity(wires=[0, "b", "c"]), - ], - ), - ), - ) - - @pytest.mark.usefixtures("legacy_opmath_only") - @pytest.mark.parametrize("ps, h", tup_ps_hamiltonian) - def test_hamiltonian(self, ps, h): - """Test that a PauliSentence can be cast to a Hamiltonian.""" - ps_h = ps.hamiltonian() - h = qml.operation.convert_to_legacy_H(h) - assert ps_h.compare(h) - - @pytest.mark.usefixtures("legacy_opmath_only") - def test_hamiltonian_empty(self): - """Test that an empty PauliSentence with wire_order returns Identity.""" - op = ps5.hamiltonian(wire_order=[0, 1]) - id = qml.Hamiltonian([], []) - assert op.compare(id) - - @pytest.mark.usefixtures("legacy_opmath_only") - def test_hamiltonian_empty_error(self): - """Test that a ValueError is raised if an empty PauliSentence is - cast to a Hamiltonian.""" - with pytest.raises( - ValueError, match="Can't get the Hamiltonian for an empty PauliSentence." - ): - ps5.hamiltonian() - - @pytest.mark.usefixtures("legacy_opmath_only") - def test_hamiltonian_wire_order(self): - """Test that the wire_order parameter is used when the pauli representation is empty""" - op = ps5.hamiltonian(wire_order=["a", "b"]) - id = qml.Hamiltonian([], []) - - qml.assert_equal(op, id) - - def test_hamiltonian_deprecation(self): - """Test that the correct deprecation warning is raised when calling hamiltonian()""" - with pytest.warns(qml.PennyLaneDeprecationWarning, match="PauliSentence.hamiltonian"): - _ = ps1.hamiltonian() - def test_pickling(self): """Check that paulisentences can be pickled and unpickled.""" word1 = PauliWord({2: "X", 3: "Y", 4: "Z"}) diff --git a/tests/pauli/test_pauli_interface.py b/tests/pauli/test_pauli_interface.py index 7bb31caa86c..42ffa93dde5 100644 --- a/tests/pauli/test_pauli_interface.py +++ b/tests/pauli/test_pauli_interface.py @@ -14,7 +14,6 @@ """ Unit tests for the :mod:`pauli` interface functions in ``pauli/pauli_interface.py``. """ -import numpy as np import pytest import pennylane as qml @@ -27,10 +26,9 @@ (qml.Identity(0), 1), (qml.PauliX(0) @ qml.PauliY(1), 1), (qml.PauliX(0) @ qml.PauliY(0), 1j), - (qml.operation.Tensor(qml.X(0), qml.Y(1)), 1), - (qml.operation.Tensor(qml.X(0), qml.Y(0)), 1j), (qml.Hamiltonian([-1.23], [qml.PauliZ(0)]), -1.23), (qml.prod(qml.PauliX(0), qml.PauliY(1)), 1), + (qml.prod(qml.X(0), qml.Y(0)), 1j), (qml.s_prod(1.23, qml.s_prod(-1j, qml.PauliZ(0))), -1.23j), ) @@ -41,13 +39,6 @@ def test_pauli_word_prefactor(op, true_prefactor): assert pauli_word_prefactor(op) == true_prefactor -def test_pauli_word_prefactor_tensor_error(): - """Test that an error is raised is the tensor is not a pauli sentence.""" - op = qml.operation.Tensor(qml.Hermitian(np.eye(2), wires=0), qml.Hadamard(wires=1)) - with pytest.raises(ValueError, match="Expected a valid Pauli word"): - pauli_word_prefactor(op) - - ops = ( qml.Hadamard(0), qml.Hadamard(0) @ qml.PauliZ(1), @@ -59,7 +50,6 @@ def test_pauli_word_prefactor_tensor_error(): ) -@pytest.mark.usefixtures("use_legacy_and_new_opmath") @pytest.mark.parametrize("op", ops) def test_pauli_word_prefactor_raises_error(op): """Test that an error is raised when the operator provided is not a valid PauliWord.""" diff --git a/tests/pauli/test_pauli_utils.py b/tests/pauli/test_pauli_utils.py index a8c7e3a060a..d493ab1c736 100644 --- a/tests/pauli/test_pauli_utils.py +++ b/tests/pauli/test_pauli_utils.py @@ -17,7 +17,6 @@ # pylint: disable=too-few-public-methods,too-many-public-methods import functools import itertools -import warnings import numpy as np import pytest @@ -28,7 +27,6 @@ RY, U3, Hadamard, - Hamiltonian, Hermitian, Identity, PauliX, @@ -36,7 +34,6 @@ PauliZ, is_commuting, ) -from pennylane.operation import Tensor from pennylane.pauli import ( are_identical_pauli_words, are_pauli_words_qwc, @@ -54,7 +51,6 @@ pauli_word_to_string, qwc_complement_adj_matrix, qwc_rotation, - simplify, string_to_pauli_word, ) @@ -229,7 +225,8 @@ def test_observables_to_binary_matrix_n_qubits_arg(self): ValueError, observables_to_binary_matrix, observables, n_qubits_invalid ) - @pytest.mark.usefixtures("legacy_opmath_only") + # removed a fixture to only use legacy_opmath because its not clear why it there + # we'll see what happens when we are ready to run the tests def test_is_qwc(self): """Determining if two Pauli words are qubit-wise commuting.""" @@ -320,11 +317,11 @@ def test_is_qwc_not_binary_vectors(self): (PauliZ(1) @ PauliX(2) @ PauliZ(4), True), (PauliX(1) @ Hadamard(4), False), (Hadamard(0), False), - (Hamiltonian([], []), False), - (Hamiltonian([0.5], [PauliZ(1) @ PauliX(2)]), True), - (Hamiltonian([0.5], [PauliZ(1) @ PauliX(1)]), True), - (Hamiltonian([1.0], [Hadamard(0)]), False), - (Hamiltonian([1.0, 0.5], [PauliX(0), PauliZ(1) @ PauliX(2)]), False), + (qml.Hamiltonian([], []), False), + (qml.Hamiltonian([0.5], [PauliZ(1) @ PauliX(2)]), True), + (qml.Hamiltonian([0.5], [PauliZ(1) @ PauliX(1)]), True), + (qml.Hamiltonian([1.0], [Hadamard(0)]), False), + (qml.Hamiltonian([1.0, 0.5], [PauliX(0), PauliZ(1) @ PauliX(2)]), False), (qml.prod(qml.PauliX(0), qml.PauliY(0)), True), (qml.prod(qml.PauliX(0), qml.PauliY(1)), True), (qml.prod(qml.PauliX(0), qml.Hadamard(1)), False), @@ -352,7 +349,7 @@ class DummyOp(qml.operation.Operator): def test_are_identical_pauli_words(self): """Tests for determining if two Pauli words have the same ``wires`` and ``name`` attributes.""" - pauli_word_1 = Tensor(PauliX(0)) + pauli_word_1 = qml.ops.Prod(PauliX(0)) pauli_word_2 = PauliX(0) assert are_identical_pauli_words(pauli_word_1, pauli_word_2) @@ -360,7 +357,7 @@ def test_are_identical_pauli_words(self): pauli_word_1 = PauliX(0) @ PauliY(1) pauli_word_2 = PauliY(1) @ PauliX(0) - pauli_word_3 = Tensor(PauliX(0), PauliY(1)) + pauli_word_3 = qml.ops.Prod(PauliX(0), PauliY(1)) pauli_word_4 = PauliX(1) @ PauliZ(2) pauli_word_5 = qml.s_prod(1.5, qml.PauliX(0)) pauli_word_6 = qml.sum(qml.s_prod(0.5, qml.PauliX(0)), qml.s_prod(1.0, qml.PauliX(0))) @@ -375,14 +372,6 @@ def test_are_identical_pauli_words(self): assert not are_identical_pauli_words(pauli_word_7, pauli_word_4) assert not are_identical_pauli_words(pauli_word_6, pauli_word_4) - @pytest.mark.usefixtures("legacy_opmath_only") - def test_are_identical_pauli_words_hamiltonian_unsupported(self): - """Test that using Hamiltonians that are valid Pauli words with are_identical_pauli_words - always returns False""" - pauli_word_1 = qml.Hamiltonian([1.0], [qml.PauliX(0)]) - pauli_word_2 = qml.PauliX(0) - assert not are_identical_pauli_words(pauli_word_1, pauli_word_2) - def test_identities_always_pauli_words(self): """Tests that identity terms are always identical.""" assert are_identical_pauli_words(qml.Identity(0), qml.Identity("a")) @@ -445,42 +434,18 @@ def test_qwc_complement_adj_matrix_exception(self): PAULI_WORD_STRINGS = _make_pauli_word_strings() - @pytest.mark.usefixtures("use_legacy_and_new_opmath") @pytest.mark.parametrize("pauli_word,wire_map,expected_string", PAULI_WORD_STRINGS) def test_pauli_word_to_string(self, pauli_word, wire_map, expected_string): """Test that Pauli words are correctly converted into strings.""" obtained_string = pauli_word_to_string(pauli_word, wire_map) assert obtained_string == expected_string - def test_pauli_word_to_string_tensor(self): - """Test pauli_word_to_string with tensor instances.""" - op = qml.operation.Tensor(qml.X(0), qml.Y(1)) - assert pauli_word_to_string(op) == "XY" - - op = qml.operation.Tensor(qml.Z(0), qml.Y(1), qml.X(2)) - assert pauli_word_to_string(op) == "ZYX" - - with qml.operation.disable_new_opmath_cm(warn=False): - with warnings.catch_warnings(): - warnings.filterwarnings( - "ignore", "qml.ops.Hamiltonian uses", qml.PennyLaneDeprecationWarning - ) - PAULI_WORD_STRINGS_LEGACY = _make_pauli_word_strings() - - @pytest.mark.usefixtures("legacy_opmath_only") - @pytest.mark.parametrize("pauli_word,wire_map,expected_string", PAULI_WORD_STRINGS_LEGACY) - def test_pauli_word_to_string_legacy_opmath(self, pauli_word, wire_map, expected_string): - """Test that Pauli words are correctly converted into strings.""" - obtained_string = pauli_word_to_string(pauli_word, wire_map) - assert obtained_string == expected_string - @pytest.mark.parametrize("non_pauli_word", non_pauli_words) def test_pauli_word_to_string_invalid_input(self, non_pauli_word): """Ensure invalid inputs are handled properly when converting Pauli words to strings.""" with pytest.raises(TypeError): pauli_word_to_string(non_pauli_word) - @pytest.mark.usefixtures("new_opmath_only") @pytest.mark.parametrize( "pauli_string,wire_map,expected_pauli", [ @@ -735,7 +700,7 @@ def test_pauli_mult_using_prod(self, pauli_word_1, pauli_word_2, expected_produc obtained_product = qml.prod(pauli_word_1, pauli_word_2).simplify() if isinstance(obtained_product, qml.ops.SProd): # don't care about phase here obtained_product = obtained_product.base - assert obtained_product == qml.operation.convert_to_opmath(expected_product) + assert obtained_product == expected_product @pytest.mark.parametrize( "pauli_word_1,pauli_word_2,expected_phase", @@ -958,16 +923,10 @@ def test_diagonalize_pauli_word_catch_non_pauli_word(self, non_pauli_word): ), ] - @pytest.mark.parametrize("convert_to_opmath", (True, False)) @pytest.mark.parametrize("qwc_grouping,qwc_sol_tuple", qwc_diagonalization_io) - def test_diagonalize_qwc_pauli_words(self, qwc_grouping, qwc_sol_tuple, convert_to_opmath): + def test_diagonalize_qwc_pauli_words(self, qwc_grouping, qwc_sol_tuple): """Tests for validating diagonalize_qwc_pauli_words solutions.""" - if convert_to_opmath: - qwc_grouping = [qml.operation.convert_to_opmath(o) for o in qwc_grouping] - diag_terms = [qml.operation.convert_to_opmath(o) for o in qwc_sol_tuple[1]] - qwc_sol_tuple = (qwc_sol_tuple[0], diag_terms) - qwc_rot, diag_qwc_grouping = diagonalize_qwc_pauli_words(qwc_grouping) qwc_rot_sol, diag_qwc_grouping_sol = qwc_sol_tuple @@ -993,77 +952,9 @@ def test_diagonalize_qwc_pauli_words_catch_when_not_qwc(self, not_qwc_grouping): assert pytest.raises(ValueError, diagonalize_qwc_pauli_words, not_qwc_grouping) - @pytest.mark.usefixtures( - "legacy_opmath_only" - ) # Handling a LinearCombination is not a problem under new opmath anymore - def test_diagonalize_qwc_pauli_words_catch_invalid_type(self): - """Test for ValueError raise when diagonalize_qwc_pauli_words is given a list - containing invalid operator types.""" - invalid_ops = [qml.PauliX(0), qml.Hamiltonian([1.0], [qml.PauliZ(1)])] - - with pytest.raises(ValueError, match="This function only supports pauli words."): - _ = diagonalize_qwc_pauli_words(invalid_ops) - - -class TestObservableHF: - - with qml.operation.disable_new_opmath_cm(warn=False): - HAMILTONIAN_SIMPLIFY = [ - ( - qml.Hamiltonian( - np.array([0.5, 0.5]), - [qml.PauliX(0) @ qml.PauliY(1), qml.PauliX(0) @ qml.PauliY(1)], - ), - qml.Hamiltonian(np.array([1.0]), [qml.PauliX(0) @ qml.PauliY(1)]), - ), - ( - qml.Hamiltonian( - np.array([0.5, -0.5]), - [qml.PauliX(0) @ qml.PauliY(1), qml.PauliX(0) @ qml.PauliY(1)], - ), - qml.Hamiltonian([], []), - ), - ( - qml.Hamiltonian( - np.array([0.0, -0.5]), - [qml.PauliX(0) @ qml.PauliY(1), qml.PauliX(0) @ qml.PauliZ(1)], - ), - qml.Hamiltonian(np.array([-0.5]), [qml.PauliX(0) @ qml.PauliZ(1)]), - ), - ( - qml.Hamiltonian( - np.array([0.25, 0.25, 0.25, -0.25]), - [ - qml.PauliX(0) @ qml.PauliY(1), - qml.PauliX(0) @ qml.PauliZ(1), - qml.PauliX(0) @ qml.PauliY(1), - qml.PauliX(0) @ qml.PauliY(1), - ], - ), - qml.Hamiltonian( - np.array([0.25, 0.25]), - [qml.PauliX(0) @ qml.PauliY(1), qml.PauliX(0) @ qml.PauliZ(1)], - ), - ), - ] - - @pytest.mark.usefixtures("legacy_opmath_only") - @pytest.mark.parametrize(("hamiltonian", "result"), HAMILTONIAN_SIMPLIFY) - def test_simplify(self, hamiltonian, result): - r"""Test that simplify returns the correct hamiltonian.""" - h = simplify(hamiltonian) - assert h.compare(result) - - def test_simplify_deprecation(self): - """Test that a deprecation warning is raised when using simplify""" - with pytest.warns(qml.PennyLaneDeprecationWarning, match="qml.ops.Hamiltonian"): - h = qml.ops.Hamiltonian([1.5, 2.5], [qml.X(0), qml.Z(0)]) - - with pytest.warns(qml.PennyLaneDeprecationWarning, match="qml.pauli.simplify"): - _ = simplify(h) - -@pytest.mark.usefixtures("legacy_opmath_only") +# removed a fixture to only use legacy_opmath because its not clear why it there +# we'll see what happens when we are ready to run the tests class TestTapering: terms_bin_mat_data = [ diff --git a/tests/pulse/test_rydberg.py b/tests/pulse/test_rydberg.py index 35ad7bb164e..dc17dbe43a3 100644 --- a/tests/pulse/test_rydberg.py +++ b/tests/pulse/test_rydberg.py @@ -205,7 +205,6 @@ def f(p, t): assert len(Hd.ops) == 1 qml.assert_equal(Hd.ops[0], ops_expected[0]) - @pytest.mark.usefixtures("use_legacy_and_new_opmath") def test_no_detuning(self): """Test that when detuning not specified, the drive term is correctly defined.""" diff --git a/tests/pytrees/test_pytrees.py b/tests/pytrees/test_pytrees.py index 518938c8a5c..d6f5e946203 100644 --- a/tests/pytrees/test_pytrees.py +++ b/tests/pytrees/test_pytrees.py @@ -105,7 +105,6 @@ def test_dict(): assert new_x == {"a": 5, "b": {"c": 6, "d": 7}} -@pytest.mark.usefixtures("new_opmath_only") def test_nested_pl_object(): """Test that we can flatten and unflatten nested pennylane object.""" diff --git a/tests/qchem/openfermion_pyscf_tests/test_convert.py b/tests/qchem/openfermion_pyscf_tests/test_convert.py index 99f143c9720..c5cc661c881 100644 --- a/tests/qchem/openfermion_pyscf_tests/test_convert.py +++ b/tests/qchem/openfermion_pyscf_tests/test_convert.py @@ -24,14 +24,12 @@ import pennylane as qml from pennylane import numpy as np from pennylane import qchem -from pennylane.operation import active_new_opmath openfermion = pytest.importorskip("openfermion") openfermionpyscf = pytest.importorskip("openfermionpyscf") pyscf = pytest.importorskip("pyscf") pauli_ops_and_prod = (qml.PauliX, qml.PauliY, qml.PauliZ, qml.Identity, qml.ops.Prod) -pauli_ops_and_tensor = (qml.PauliX, qml.PauliY, qml.PauliZ, qml.Identity, qml.operation.Tensor) @pytest.fixture( @@ -394,7 +392,6 @@ def test_observable_conversion(_, terms_ref, custom_wires, monkeypatch): ) -@pytest.mark.usefixtures("use_legacy_and_new_opmath") @pytest.mark.parametrize("pl_op, of_op, wire_order", ops_wires) def test_operation_conversion(pl_op, of_op, wire_order): """Assert the conversion between pennylane and openfermion operators""" @@ -404,10 +401,7 @@ def test_operation_conversion(pl_op, of_op, wire_order): converted_of_op = qml.qchem.convert._openfermion_to_pennylane(of_op) _, converted_of_op_terms = converted_of_op - assert all( - isinstance(term, pauli_ops_and_prod if active_new_opmath() else pauli_ops_and_tensor) - for term in converted_of_op_terms - ) + assert all(isinstance(term, pauli_ops_and_prod) for term in converted_of_op_terms) assert np.allclose( qml.matrix(qml.dot(*pl_op), wire_order=wire_order), @@ -433,7 +427,7 @@ def test_convert_format_not_supported(terms_ref, lib_name, monkeypatch): invalid_ops = ( - qml.operation.Tensor(qml.PauliZ(0), qml.QuadOperator(0.1, wires=1)), + qml.prod(qml.PauliZ(0), qml.QuadOperator(0.1, wires=1)), qml.prod(qml.PauliX(0), qml.Hadamard(1)), qml.sum(qml.PauliZ(0), qml.Hadamard(1)), ) @@ -447,7 +441,7 @@ def test_not_xyz_pennylane_to_openfermion(op): qml.qchem.convert._pennylane_to_openfermion( np.array([0.1 + 0.0j, 0.0]), [ - qml.operation.Tensor(qml.PauliX(0)), + qml.prod(qml.PauliX(0)), op, ], ) @@ -462,8 +456,8 @@ def test_wires_not_covered_pennylane_to_openfermion(): qml.qchem.convert._pennylane_to_openfermion( np.array([0.1, 0.2]), [ - qml.operation.Tensor(qml.PauliX(wires=["w0"])), - qml.operation.Tensor(qml.PauliY(wires=["w0"]), qml.PauliZ(wires=["w2"])), + qml.prod(qml.PauliX(wires=["w0"])), + qml.prod(qml.PauliY(wires=["w0"]), qml.PauliZ(wires=["w2"])), ], wires=qml.wires.Wires(["w0", "w1"]), ) @@ -529,14 +523,13 @@ def test_types_consistency(): ) -@pytest.mark.usefixtures("use_legacy_and_new_opmath") @pytest.mark.parametrize("of_op, pl_h, pl_op, wires", of_pl_ops) def test_import_operator(of_op, pl_h, pl_op, wires): """Test the import_operator function correctly imports an OpenFermion operator into a PL one.""" of_h = qml.qchem.convert.import_operator(of_op, "openfermion", wires=wires) assert qml.pauli.pauli_sentence(pl_h) == qml.pauli.pauli_sentence(of_h) - assert isinstance(of_h, type(pl_op) if active_new_opmath() else qml.Hamiltonian) + assert isinstance(of_h, type(pl_op)) if isinstance(of_h, qml.ops.Sum): assert all( @@ -614,8 +607,8 @@ def test_pennylane_to_openfermion_no_decomp(): """Test the _pennylane_to_openfermion function with custom wires.""" coeffs = np.array([0.1, 0.2]) ops = [ - qml.operation.Tensor(qml.PauliX(wires=["w0"])), - qml.operation.Tensor(qml.PauliY(wires=["w0"]), qml.PauliZ(wires=["w2"])), + qml.prod(qml.PauliX(wires=["w0"])), + qml.prod(qml.PauliY(wires=["w0"]), qml.PauliZ(wires=["w2"])), ] op_str = str( qml.qchem.convert._pennylane_to_openfermion( @@ -736,8 +729,8 @@ def test_fail_import_openfermion(monkeypatch): qml.qchem.convert._pennylane_to_openfermion( np.array([0.1 + 0.0j, 0.0]), [ - qml.operation.Tensor(qml.PauliX(0)), - qml.operation.Tensor(qml.PauliZ(0), qml.QuadOperator(0.1, wires=1)), + qml.prod(qml.PauliX(0)), + qml.prod(qml.PauliZ(0), qml.QuadOperator(0.1, wires=1)), ], ) diff --git a/tests/qchem/openfermion_pyscf_tests/test_convert_openfermion.py b/tests/qchem/openfermion_pyscf_tests/test_convert_openfermion.py index 2938ca07a29..cb2cc19020f 100644 --- a/tests/qchem/openfermion_pyscf_tests/test_convert_openfermion.py +++ b/tests/qchem/openfermion_pyscf_tests/test_convert_openfermion.py @@ -310,7 +310,7 @@ def test_mapping_wires(self, pl_op, of_op, wires): assert q_op == of_op INVALID_OPS = ( - qml.operation.Tensor(qml.PauliZ(0), qml.QuadOperator(0.1, wires=1)), + qml.prod(qml.PauliZ(0), qml.QuadOperator(0.1, wires=1)), qml.prod(qml.PauliX(0), qml.Hadamard(1)), qml.sum(qml.PauliZ(0), qml.Hadamard(1)), ) @@ -321,7 +321,7 @@ def test_not_xyz(self, op): _match = "Expected a Pennylane operator with a valid Pauli word representation," pl_op = qml.ops.LinearCombination( - np.array([0.1 + 0.0j, 0.0]), [qml.operation.Tensor(qml.PauliX(0)), op] + np.array([0.1 + 0.0j, 0.0]), [qml.prod(qml.PauliX(0)), op] ) with pytest.raises(ValueError, match=_match): qml.to_openfermion(qml.to_openfermion(pl_op)) diff --git a/tests/qchem/openfermion_pyscf_tests/test_dipole_of.py b/tests/qchem/openfermion_pyscf_tests/test_dipole_of.py index 739965c5813..710a757923c 100644 --- a/tests/qchem/openfermion_pyscf_tests/test_dipole_of.py +++ b/tests/qchem/openfermion_pyscf_tests/test_dipole_of.py @@ -207,7 +207,7 @@ (h2o, x_h2o, 0, range(4), [4, 5], "bravyi_kitaev", coeffs_h2o, ops_h2o), ], ) -@pytest.mark.usefixtures("skip_if_no_openfermion_support", "use_legacy_and_new_opmath") +@pytest.mark.usefixtures("skip_if_no_openfermion_support") def test_dipole_obs(symbols, coords, charge, core, active, mapping, coeffs, ops, tol, tmpdir): r"""Tests the correctness of the dipole observable computed by the ``dipole`` function.""" @@ -230,15 +230,6 @@ def test_dipole_obs(symbols, coords, charge, core, active, mapping, coeffs, ops, assert np.allclose(calc_coeffs, exp_coeffs, **tol) r_ops = ops[i] - if not qml.operation.active_new_opmath(): - r_ops = [ - ( - qml.operation.Tensor(*obs.simplify()) - if isinstance(obs.simplify(), (qml.ops.op_math.Prod)) - else obs.simplify() - ) - for obs in ops[i] - ] assert all(isinstance(o1, o2.__class__) for o1, o2 in zip(d_ops, r_ops)) for o1, o2 in zip(d_ops, r_ops): diff --git a/tests/qchem/openfermion_pyscf_tests/test_molecular_dipole.py b/tests/qchem/openfermion_pyscf_tests/test_molecular_dipole.py index 3102d07a579..3c4202bd6ac 100644 --- a/tests/qchem/openfermion_pyscf_tests/test_molecular_dipole.py +++ b/tests/qchem/openfermion_pyscf_tests/test_molecular_dipole.py @@ -227,7 +227,6 @@ (h2o, x_h2o, 0, 2, 2, "bravyi_kitaev", coeffs_h2o, ops_h2o), ], ) -@pytest.mark.usefixtures("use_legacy_and_new_opmath") def test_openfermion_molecular_dipole( symbols, geometry, charge, active_el, active_orb, mapping, coeffs, ops, tol, tmpdir ): @@ -252,15 +251,6 @@ def test_openfermion_molecular_dipole( assert np.allclose(calc_coeffs, exp_coeffs, **tol) r_ops = ops[i] - if not qml.operation.active_new_opmath(): - r_ops = [ - ( - qml.operation.Tensor(*obs.simplify()) - if isinstance(obs.simplify(), (qml.ops.op_math.Prod)) - else obs.simplify() - ) - for obs in ops[i] - ] assert all(isinstance(o1, o2.__class__) for o1, o2 in zip(d_ops, r_ops)) for o1, o2 in zip(d_ops, r_ops): @@ -284,7 +274,6 @@ def test_openfermion_molecular_dipole( (h2o, x_h2o, 0, 2, 2, "bravyi_kitaev", eig_h2o), ], ) -@pytest.mark.usefixtures("use_legacy_and_new_opmath") def test_differentiable_molecular_dipole( symbols, geometry, charge, active_el, active_orb, mapping, eig_ref, tmpdir ): @@ -309,7 +298,6 @@ def test_differentiable_molecular_dipole( assert np.allclose(np.sort(eig), np.sort(eig_ref[idx])) -@pytest.mark.usefixtures("use_legacy_and_new_opmath") @pytest.mark.parametrize( ("wiremap"), [ @@ -379,7 +367,7 @@ def test_molecular_dipole_error(): ), ], ) -@pytest.mark.usefixtures("skip_if_no_openfermion_support", "use_legacy_and_new_opmath") +@pytest.mark.usefixtures("skip_if_no_openfermion_support") def test_real_dipole(method, args, tmpdir): r"""Test that the generated operator has real coefficients.""" diff --git a/tests/qchem/openfermion_pyscf_tests/test_molecular_hamiltonian.py b/tests/qchem/openfermion_pyscf_tests/test_molecular_hamiltonian.py index 9285facb58b..6985cda41ac 100644 --- a/tests/qchem/openfermion_pyscf_tests/test_molecular_hamiltonian.py +++ b/tests/qchem/openfermion_pyscf_tests/test_molecular_hamiltonian.py @@ -21,7 +21,6 @@ from pennylane import I, X, Y, Z from pennylane import numpy as np from pennylane import qchem -from pennylane.operation import active_new_opmath test_symbols = ["C", "C", "N", "H", "H", "H", "H", "H"] test_coordinates = np.array( @@ -70,7 +69,7 @@ (2, 1, "pyscf", 2, 2, "BRAVYI_kitaev"), ], ) -@pytest.mark.usefixtures("skip_if_no_openfermion_support", "use_legacy_and_new_opmath") +@pytest.mark.usefixtures("skip_if_no_openfermion_support") def test_building_hamiltonian( charge, mult, @@ -98,10 +97,7 @@ def test_building_hamiltonian( built_hamiltonian, qubits = qchem.molecular_hamiltonian(*args, **kwargs) - if active_new_opmath(): - assert not isinstance(built_hamiltonian, qml.Hamiltonian) - else: - assert isinstance(built_hamiltonian, qml.Hamiltonian) + assert isinstance(built_hamiltonian, qml.ops.Sum) assert qubits == 2 * nact_orbs @@ -121,7 +117,7 @@ def test_building_hamiltonian( (2, 1, "pyscf", 2, 2, "BRAVYI_kitaev"), ], ) -@pytest.mark.usefixtures("skip_if_no_openfermion_support", "use_legacy_and_new_opmath") +@pytest.mark.usefixtures("skip_if_no_openfermion_support") def test_building_hamiltonian_molecule_class( charge, mult, @@ -147,10 +143,7 @@ def test_building_hamiltonian_molecule_class( built_hamiltonian, qubits = qchem.molecular_hamiltonian(args, **kwargs) - if active_new_opmath(): - assert not isinstance(built_hamiltonian, qml.Hamiltonian) - else: - assert isinstance(built_hamiltonian, qml.Hamiltonian) + assert isinstance(built_hamiltonian, qml.ops.Sum) assert qubits == 2 * nact_orbs @@ -348,7 +341,6 @@ def test_building_hamiltonian_molecule_class( ), ], ) -@pytest.mark.usefixtures("use_legacy_and_new_opmath") def test_differentiable_hamiltonian(symbols, geometry, mapping, h_ref_data): r"""Test that molecular_hamiltonian returns the correct Hamiltonian with the differentiable backend.""" @@ -362,10 +354,7 @@ def test_differentiable_hamiltonian(symbols, geometry, mapping, h_ref_data): geometry.requires_grad = False h_noargs = qchem.molecular_hamiltonian(symbols, geometry, method="dhf", mapping=mapping)[0] - ops = [ - qml.operation.Tensor(*op) if isinstance(op, qml.ops.Prod) else op - for op in map(qml.simplify, h_ref_data[1]) - ] + ops = list(map(qml.simplify, h_ref_data[1])) h_ref = qml.Hamiltonian(h_ref_data[0], ops) h_ref_coeffs, h_ref_ops = h_ref.terms() @@ -580,7 +569,6 @@ def test_differentiable_hamiltonian(symbols, geometry, mapping, h_ref_data): ), ], ) -@pytest.mark.usefixtures("use_legacy_and_new_opmath") def test_differentiable_hamiltonian_molecule_class(symbols, geometry, mapping, h_ref_data): r"""Test that molecular_hamiltonian generated using the molecule class returns the correct Hamiltonian with the differentiable backend.""" @@ -594,10 +582,7 @@ def test_differentiable_hamiltonian_molecule_class(symbols, geometry, mapping, h molecule = qchem.Molecule(symbols, geometry) h_noargs = qchem.molecular_hamiltonian(molecule, method="dhf", mapping=mapping)[0] - ops = [ - qml.operation.Tensor(*op) if isinstance(op, qml.ops.Prod) else op - for op in map(qml.simplify, h_ref_data[1]) - ] + ops = list(map(qml.simplify, h_ref_data[1])) h_ref = qml.Hamiltonian(h_ref_data[0], ops) h_ref_coeffs, h_ref_ops = h_ref.terms() @@ -618,7 +603,6 @@ def test_differentiable_hamiltonian_molecule_class(symbols, geometry, mapping, h ) -@pytest.mark.usefixtures("use_legacy_and_new_opmath") @pytest.mark.parametrize( ("wiremap"), [ @@ -670,7 +654,6 @@ def test_custom_wiremap_hamiltonian_pyscf_molecule_class(wiremap, tmpdir): assert set(hamiltonian.wires) == set(wiremap) -@pytest.mark.usefixtures("use_legacy_and_new_opmath") @pytest.mark.parametrize( ("wiremap", "args"), [ @@ -711,7 +694,6 @@ def test_custom_wiremap_hamiltonian_dhf(wiremap, args, tmpdir): assert wiremap_calc == wiremap_dict -@pytest.mark.usefixtures("use_legacy_and_new_opmath") @pytest.mark.parametrize( ("wiremap", "args"), [ @@ -853,7 +835,7 @@ def test_diff_hamiltonian_error_molecule_class(): ), ], ) -@pytest.mark.usefixtures("skip_if_no_openfermion_support", "use_legacy_and_new_opmath") +@pytest.mark.usefixtures("skip_if_no_openfermion_support") def test_real_hamiltonian(method, args, tmpdir): r"""Test that the generated Hamiltonian has real coefficients.""" @@ -888,7 +870,7 @@ def test_real_hamiltonian(method, args, tmpdir): ), ], ) -@pytest.mark.usefixtures("skip_if_no_openfermion_support", "use_legacy_and_new_opmath") +@pytest.mark.usefixtures("skip_if_no_openfermion_support") def test_real_hamiltonian_molecule_class(method, args, tmpdir): r"""Test that the generated Hamiltonian has real coefficients.""" @@ -940,7 +922,7 @@ def test_pyscf_integrals(symbols, geometry, core_ref, one_ref, two_ref): assert np.allclose(two, two_ref) -@pytest.mark.usefixtures("skip_if_no_openfermion_support", "use_legacy_and_new_opmath") +@pytest.mark.usefixtures("skip_if_no_openfermion_support") def test_molecule_as_kwargs(tmpdir): r"""Test that molecular_hamiltonian function works with molecule as keyword argument @@ -958,10 +940,7 @@ def test_molecule_as_kwargs(tmpdir): outpath=tmpdir.strpath, ) - if active_new_opmath(): - assert not isinstance(built_hamiltonian, qml.Hamiltonian) - else: - assert isinstance(built_hamiltonian, qml.Hamiltonian) + assert isinstance(built_hamiltonian, qml.ops.Sum) assert qubits == 4 @@ -1233,7 +1212,6 @@ def test_error_raised_for_missing_molecule_information(): ), ], ) -@pytest.mark.usefixtures("use_legacy_and_new_opmath") def test_mapped_hamiltonian_pyscf_openfermion( symbols, geometry, charge, mapping, h_ref_data, tmpdir ): @@ -1247,10 +1225,7 @@ def test_mapped_hamiltonian_pyscf_openfermion( molecule, method=method, mapping=mapping, outpath=tmpdir.strpath )[0] - ops = [ - qml.operation.Tensor(*op) if isinstance(op, qml.ops.Prod) else op - for op in map(qml.simplify, h_ref_data[1]) - ] + ops = list(map(qml.simplify, h_ref_data[1])) h_ref = qml.Hamiltonian(h_ref_data[0], ops) h_ref_coeffs, h_ref_ops = h_ref.terms() diff --git a/tests/qchem/test_dipole.py b/tests/qchem/test_dipole.py index 5856ad5087a..c49967039d6 100644 --- a/tests/qchem/test_dipole.py +++ b/tests/qchem/test_dipole.py @@ -22,7 +22,6 @@ from pennylane import numpy as np from pennylane import qchem from pennylane.fermi import from_string -from pennylane.operation import Tensor @pytest.mark.parametrize( @@ -183,13 +182,12 @@ def test_fermionic_dipole(symbols, geometry, core, charge, active, f_ref): ), ], ) -@pytest.mark.usefixtures("use_legacy_and_new_opmath") def test_dipole_moment(symbols, geometry, core, charge, active, coeffs, ops): r"""Test that dipole_moment returns the correct result.""" mol = qchem.Molecule(symbols, geometry, charge=charge) args = [p for p in [geometry] if p.requires_grad] d = qchem.dipole_moment(mol, core=core, active=active, cutoff=1.0e-8)(*args)[0] - dops = [Tensor(*op) if isinstance(op, qml.ops.Prod) else op for op in map(qml.simplify, ops)] + dops = list(map(qml.simplify, ops)) d_ref = qml.Hamiltonian(coeffs, dops) d_coeff, d_ops = d.terms() @@ -216,7 +214,6 @@ def test_dipole_moment(symbols, geometry, core, charge, active, coeffs, ops): ), ], ) -@pytest.mark.usefixtures("use_legacy_and_new_opmath") def test_dipole_moment_631g_basis(symbols, geometry, core, active): r"""Test that the dipole moment is constructed properly with basis sets having different numbers of primitive Gaussian functions.""" diff --git a/tests/qchem/test_factorization.py b/tests/qchem/test_factorization.py index bea18d3c09a..b1261faff65 100644 --- a/tests/qchem/test_factorization.py +++ b/tests/qchem/test_factorization.py @@ -316,7 +316,6 @@ def test_empty_error(two_tensor): ), ], ) -@pytest.mark.usefixtures("use_legacy_and_new_opmath") def test_basis_rotation_output( one_matrix, two_tensor, tol_factor, coeffs_ref, ops_ref, eigvecs_ref ): @@ -364,7 +363,6 @@ def test_basis_rotation_output( ) ], ) -@pytest.mark.usefixtures("use_legacy_and_new_opmath") def test_basis_rotation_utransform(core, one_electron, two_electron): r"""Test that basis_rotation function returns the correct transformation matrices. This test constructs the matrix representation of a factorized Hamiltonian and then applies the diff --git a/tests/qchem/test_hamiltonians.py b/tests/qchem/test_hamiltonians.py index 26c640b3f8e..09b8dde8325 100644 --- a/tests/qchem/test_hamiltonians.py +++ b/tests/qchem/test_hamiltonians.py @@ -22,7 +22,6 @@ from pennylane import numpy as np from pennylane import qchem from pennylane.fermi import from_string -from pennylane.operation import active_new_opmath @pytest.mark.parametrize( @@ -224,7 +223,6 @@ def test_fermionic_hamiltonian(symbols, geometry, alpha, h_ref): ) ], ) -@pytest.mark.usefixtures("use_legacy_and_new_opmath") def test_diff_hamiltonian(symbols, geometry, h_ref_data): r"""Test that diff_hamiltonian returns the correct Hamiltonian.""" @@ -232,10 +230,7 @@ def test_diff_hamiltonian(symbols, geometry, h_ref_data): args = [] h = qchem.diff_hamiltonian(mol)(*args) - ops = [ - qml.operation.Tensor(*op) if isinstance(op, qml.ops.Prod) else op - for op in map(qml.simplify, h_ref_data[1]) - ] + ops = list(map(qml.simplify, h_ref_data[1])) h_ref = qml.Hamiltonian(h_ref_data[0], ops) assert np.allclose(np.sort(h.terms()[0]), np.sort(h_ref.terms()[0])) @@ -243,7 +238,7 @@ def test_diff_hamiltonian(symbols, geometry, h_ref_data): qml.Hamiltonian(np.ones(len(h_ref.terms()[0])), h_ref.terms()[1]) ) - assert isinstance(h, qml.ops.Sum if active_new_opmath() else qml.Hamiltonian) + assert isinstance(h, qml.ops.Sum) wire_order = h_ref.wires assert np.allclose( @@ -252,7 +247,6 @@ def test_diff_hamiltonian(symbols, geometry, h_ref_data): ) -@pytest.mark.usefixtures("use_legacy_and_new_opmath") def test_diff_hamiltonian_active_space(): r"""Test that diff_hamiltonian works when an active space is defined.""" @@ -264,7 +258,7 @@ def test_diff_hamiltonian_active_space(): h = qchem.diff_hamiltonian(mol, core=[0], active=[1, 2])(*args) - assert isinstance(h, qml.ops.Sum if active_new_opmath() else qml.Hamiltonian) + assert isinstance(h, qml.ops.Sum) @pytest.mark.parametrize( @@ -345,7 +339,6 @@ def circuit(*args): assert np.allclose(grad_qml[0][0], grad_finitediff) -@pytest.mark.usefixtures("use_legacy_and_new_opmath") class TestJax: @pytest.mark.jax def test_gradient_expvalH(self): diff --git a/tests/qchem/test_observable_hf.py b/tests/qchem/test_observable_hf.py index f6cbd120ec9..f10dd9c9b31 100644 --- a/tests/qchem/test_observable_hf.py +++ b/tests/qchem/test_observable_hf.py @@ -166,14 +166,10 @@ def test_fermionic_observable(core_constant, integral_one, integral_two, f_ref): (1.23 * from_string(""), [[1.23], [qml.Identity(0)]]), ], ) -@pytest.mark.usefixtures("use_legacy_and_new_opmath") 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 = [ - qml.operation.Tensor(*op) if isinstance(op, qml.ops.Prod) else op - for op in map(qml.simplify, q_observable[1]) - ] + ops = list(map(qml.simplify, q_observable[1])) h_ref = qml.Hamiltonian(q_observable[0], ops) assert h_ref.compare(h_as_op) @@ -195,7 +191,6 @@ def test_qubit_observable(f_observable, q_observable): ), ], ) -@pytest.mark.usefixtures("use_legacy_and_new_opmath") 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))) diff --git a/tests/qchem/test_particle_number.py b/tests/qchem/test_particle_number.py index 2c244f78882..bd1987ab205 100644 --- a/tests/qchem/test_particle_number.py +++ b/tests/qchem/test_particle_number.py @@ -20,10 +20,8 @@ from pennylane import Identity, PauliZ from pennylane import numpy as np from pennylane import qchem -from pennylane.operation import active_new_opmath -@pytest.mark.usefixtures("use_legacy_and_new_opmath") @pytest.mark.parametrize( ("orbitals", "coeffs_ref", "ops_ref"), [ @@ -60,7 +58,7 @@ def test_particle_number(orbitals, coeffs_ref, ops_ref): n = qchem.particle_number(orbitals) n_ref = qml.Hamiltonian(coeffs_ref, ops_ref) assert n_ref.compare(n) - assert isinstance(n, qml.ops.Sum if active_new_opmath() else qml.Hamiltonian) + assert isinstance(n, qml.ops.Sum) wire_order = n_ref.wires assert np.allclose( diff --git a/tests/qchem/test_spin.py b/tests/qchem/test_spin.py index dbe7fb39d50..294254566d4 100644 --- a/tests/qchem/test_spin.py +++ b/tests/qchem/test_spin.py @@ -20,7 +20,6 @@ from pennylane import Identity, PauliX, PauliY, PauliZ from pennylane import numpy as np from pennylane import qchem, simplify -from pennylane.operation import Tensor, active_new_opmath @pytest.mark.parametrize( @@ -116,7 +115,6 @@ def test_spin2_matrix_elements(n_spin_orbs, matrix_ref): assert np.allclose(s2_me_result, matrix_ref) -@pytest.mark.usefixtures("use_legacy_and_new_opmath") @pytest.mark.parametrize( ("electrons", "orbitals", "coeffs_ref", "ops_ref"), [ @@ -175,10 +173,10 @@ def test_spin2(electrons, orbitals, coeffs_ref, ops_ref): built by the function `'spin2'`. """ s2 = qchem.spin.spin2(electrons, orbitals) - sops = [Tensor(*op) if isinstance(op, qml.ops.Prod) else op for op in map(simplify, ops_ref)] + sops = list(map(simplify, ops_ref)) s2_ref = qml.Hamiltonian(coeffs_ref, sops) assert s2_ref.compare(s2) - assert isinstance(s2, qml.ops.Sum if active_new_opmath() else qml.Hamiltonian) + assert isinstance(s2, qml.ops.Sum) wire_order = s2_ref.wires assert np.allclose( @@ -204,7 +202,6 @@ def test_exception_spin2(electrons, orbitals, msg_match): qchem.spin.spin2(electrons, orbitals) -@pytest.mark.usefixtures("use_legacy_and_new_opmath") @pytest.mark.parametrize( ("orbitals", "coeffs_ref", "ops_ref"), [ @@ -234,7 +231,7 @@ def test_spinz(orbitals, coeffs_ref, ops_ref): sz = qchem.spin.spinz(orbitals) sz_ref = qml.Hamiltonian(coeffs_ref, ops_ref) assert sz_ref.compare(sz) - assert isinstance(sz, qml.ops.Sum if active_new_opmath() else qml.Hamiltonian) + assert isinstance(sz, qml.ops.Sum) wire_order = sz_ref.wires assert np.allclose( diff --git a/tests/qchem/test_structure.py b/tests/qchem/test_structure.py index 6fda2ba8a21..82f01c45204 100644 --- a/tests/qchem/test_structure.py +++ b/tests/qchem/test_structure.py @@ -354,9 +354,9 @@ def test_hf_state_basis(electrons, symbols, geometry, charge): state_parity = qchem.hf_state(electrons, qubits, basis="parity") state_bk = qchem.hf_state(electrons, qubits, basis="bravyi_kitaev") - h_occ = qml.jordan_wigner(h_ferm, ps=True, tol=1e-16).hamiltonian() - h_parity = qml.parity_transform(h_ferm, qubits, ps=True, tol=1e-16).hamiltonian() - h_bk = qml.bravyi_kitaev(h_ferm, qubits, ps=True, tol=1e-16).hamiltonian() + h_occ = qml.jordan_wigner(h_ferm, ps=True, tol=1e-16).operation() + h_parity = qml.parity_transform(h_ferm, qubits, ps=True, tol=1e-16).operation() + h_bk = qml.bravyi_kitaev(h_ferm, qubits, ps=True, tol=1e-16).operation() dev = qml.device("default.qubit", wires=qubits) diff --git a/tests/qchem/test_tapering.py b/tests/qchem/test_tapering.py index 08644749ba2..05388b27060 100644 --- a/tests/qchem/test_tapering.py +++ b/tests/qchem/test_tapering.py @@ -196,7 +196,6 @@ def test_kernel(binary_matrix, result): ), ], ) -@pytest.mark.usefixtures("use_legacy_and_new_opmath") 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) @@ -225,7 +224,6 @@ def test_generate_paulis(generators, num_qubits, result): ), ], ) -@pytest.mark.usefixtures("use_legacy_and_new_opmath") def test_symmetry_generators(symbols, geometry, res_generators): r"""Test that symmetry_generators returns the correct result.""" @@ -264,7 +262,6 @@ def test_symmetry_generators(symbols, geometry, res_generators): ), ], ) -@pytest.mark.usefixtures("use_legacy_and_new_opmath") def test_clifford(generator, paulixops, result): r"""Test that clifford returns the correct operator.""" u = clifford(generator, paulixops) @@ -296,7 +293,6 @@ def test_clifford(generator, paulixops, result): ), ], ) -@pytest.mark.usefixtures("use_legacy_and_new_opmath") def test_transform_hamiltonian(symbols, geometry, generator, paulixops, paulix_sector, ham_ref): r"""Test that transform_hamiltonian returns the correct hamiltonian.""" mol = qml.qchem.Molecule(symbols, geometry) @@ -353,7 +349,6 @@ def test_transform_hamiltonian(symbols, geometry, generator, paulixops, paulix_s ), ], ) -@pytest.mark.usefixtures("use_legacy_and_new_opmath") def test_optimal_sector(symbols, geometry, charge, generators, num_electrons, result): r"""Test that find_optimal_sector returns the correct result.""" mol = qml.qchem.Molecule(symbols, geometry, charge) @@ -459,7 +454,6 @@ def test_exceptions_optimal_sector(symbols, geometry, generators, num_electrons, ), ], ) -@pytest.mark.usefixtures("use_legacy_and_new_opmath") def test_transform_hf(generators, paulixops, paulix_sector, num_electrons, num_wires, result): r"""Test that transform_hf returns the correct result.""" @@ -507,7 +501,6 @@ def test_transform_hf(generators, paulixops, paulix_sector, num_electrons, num_w ), ], ) -@pytest.mark.usefixtures("use_legacy_and_new_opmath") def test_taper_obs(symbols, geometry, charge): r"""Test that the expectation values of tapered observables with respect to the tapered Hartree-Fock state (:math:`\langle HF|obs|HF \rangle`) are consistent.""" @@ -603,7 +596,6 @@ def test_taper_obs(symbols, geometry, charge): ), ], ) -@pytest.mark.usefixtures("use_legacy_and_new_opmath") def test_taper_excitations( symbols, geometry, charge, generators, paulixops, paulix_sector, num_commuting ): @@ -738,7 +730,6 @@ def test_inconsistent_taper_ops(operation, op_gen, message_match): ), ], ) -@pytest.mark.usefixtures("use_legacy_and_new_opmath") def test_consistent_taper_ops(operation, op_gen): r"""Test that operations are tapered consistently when their generators are provided manually and when they are constructed internally""" @@ -897,7 +888,6 @@ def test_taper_callable_ops(operation, op_wires, op_gen): ), ], ) -@pytest.mark.usefixtures("use_legacy_and_new_opmath") def test_taper_matrix_ops(operation, op_wires, op_gen): """Test that taper_operation can be used with gate operation built using matrices""" diff --git a/tests/resource/test_specs.py b/tests/resource/test_specs.py index aebe0af0d19..a02b35ef97b 100644 --- a/tests/resource/test_specs.py +++ b/tests/resource/test_specs.py @@ -209,11 +209,11 @@ def circ(): specs = qml.specs(circ)() assert specs["resources"].num_gates == 1 - assert specs["num_diagonalizing_gates"] == (1 if qml.operation.active_new_opmath() else 0) + assert specs["num_diagonalizing_gates"] == 1 specs = qml.specs(circ, level="device")() assert specs["resources"].num_gates == 3 - assert specs["num_diagonalizing_gates"] == (3 if qml.operation.active_new_opmath() else 0) + assert specs["num_diagonalizing_gates"] == 3 def test_splitting_transforms(self): coeffs = [0.2, -0.543, 0.1] diff --git a/tests/shadow/test_shadow_class.py b/tests/shadow/test_shadow_class.py index e9fcb8ef90a..e6bae91ecb6 100644 --- a/tests/shadow/test_shadow_class.py +++ b/tests/shadow/test_shadow_class.py @@ -76,7 +76,6 @@ class TestIntegrationShadows: """Integration tests for classical shadows class""" @pytest.mark.parametrize("shadow", shadows) - @pytest.mark.usefixtures("use_legacy_and_new_opmath") def test_pauli_string_expval(self, shadow): """Testing the output of expectation values match those of exact evaluation""" @@ -100,11 +99,8 @@ def test_pauli_string_expval(self, shadow): @pytest.mark.parametrize("H", Hs) @pytest.mark.parametrize("shadow", shadows) - @pytest.mark.usefixtures("use_legacy_and_new_opmath") def test_expval_input_types(self, shadow, H): """Test ClassicalShadow.expval can handle different inputs""" - if not qml.operation.active_new_opmath(): - H = qml.operation.convert_to_legacy_H(H) assert qml.math.allclose(shadow.expval(H, k=2), 1.0, atol=1e-1) def test_reconstruct_bell_state(self): @@ -349,12 +345,7 @@ def test_non_pauli_error(self): H = qml.Hadamard(0) @ qml.Hadamard(2) - msg = ( - "Observable must have a valid pauli representation" - if qml.operation.active_new_opmath() - else "Observable must be a linear combination of Pauli observables" - ) - with pytest.raises(ValueError, match=msg): + with pytest.raises(ValueError, match="Observable must have a valid pauli representation"): shadow.expval(H, k=10) def test_non_pauli_error_no_pauli_rep(self): @@ -365,11 +356,7 @@ def test_non_pauli_error_no_pauli_rep(self): H = qml.Hadamard(0) @ qml.Hadamard(2) - legacy_msg = "Observable must be a linear combination of Pauli observables" - new_opmath_msg = "Observable must have a valid pauli representation." - msg = new_opmath_msg if qml.operation.active_new_opmath() else legacy_msg - - with pytest.raises(ValueError, match=msg): + with pytest.raises(ValueError, match="Observable must have a valid pauli representation."): shadow.expval(H, k=10) diff --git a/tests/spin/test_spin_hamiltonian.py b/tests/spin/test_spin_hamiltonian.py index 6caccafba21..fe113fa7d2b 100644 --- a/tests/spin/test_spin_hamiltonian.py +++ b/tests/spin/test_spin_hamiltonian.py @@ -33,7 +33,6 @@ ) # pylint: disable=too-many-arguments -pytestmark = pytest.mark.usefixtures("new_opmath_only") def test_coupling_error(): diff --git a/tests/tape/test_tape.py b/tests/tape/test_tape.py index 72033dda15c..fe9e29f46a4 100644 --- a/tests/tape/test_tape.py +++ b/tests/tape/test_tape.py @@ -143,23 +143,6 @@ def test_tensor_observables_rmatmul(self): assert tape.measurements[0].return_type is qml.measurements.Expectation assert tape.measurements[0].obs is t_obs2 - @pytest.mark.usefixtures("legacy_opmath_only") - def test_tensor_observables_tensor_init(self): - """Test that tensor observables are correctly processed from the annotated - queue. Here, we test multiple tensor observables constructed via explicit - Tensor creation.""" - - with QuantumTape() as tape: - op_ = qml.RX(1.0, wires=0) - t_obs1 = qml.PauliZ(1) @ qml.PauliX(0) - t_obs2 = qml.operation.Tensor(t_obs1, qml.Hadamard(2)) - qml.expval(t_obs2) - - assert tape.operations == [op_] - assert tape.observables == [t_obs2] - assert tape.measurements[0].return_type is qml.measurements.Expectation - assert tape.measurements[0].obs is t_obs2 - def test_tensor_observables_tensor_matmul(self): """Test that tensor observables are correctly processed from the annotated queue". Here, wetest multiple tensor observables constructed via matmul diff --git a/tests/templates/test_subroutines/test_qubitization.py b/tests/templates/test_subroutines/test_qubitization.py index 7b8e439d77c..d9cd30e4fdf 100644 --- a/tests/templates/test_subroutines/test_qubitization.py +++ b/tests/templates/test_subroutines/test_qubitization.py @@ -79,19 +79,6 @@ def test_standard_validity(lcu, control, skip_diff): qml.ops.functions.assert_valid(op, skip_differentiation=skip_diff) -@pytest.mark.usefixtures("use_legacy_and_new_opmath") -def test_legacy_new_opmath(): - coeffs, ops = [0.1, -0.3, -0.3], [qml.X(0), qml.Z(1), qml.Y(0) @ qml.Z(2)] - - H1 = qml.dot(coeffs, ops) - matrix_H1 = qml.matrix(qml.Qubitization(H1, control=[3, 4]), wire_order=[3, 4, 0, 1, 2]) - - H2 = qml.Hamiltonian(coeffs, ops) - matrix_H2 = qml.matrix(qml.Qubitization(H2, control=[3, 4]), wire_order=[3, 4, 0, 1, 2]) - - assert np.allclose(matrix_H1, matrix_H2) - - @pytest.mark.parametrize( "hamiltonian, expected_decomposition", ( @@ -248,27 +235,6 @@ def test_qnode_tf(self, shots, seed): assert qml.math.shape(jac) == (4,) assert qml.math.allclose(res, self.exp_grad, atol=0.001) - @pytest.mark.xfail(reason="see https://github.com/PennyLaneAI/pennylane/issues/5507") - @pytest.mark.usefixtures("use_legacy_and_new_opmath") - def test_legacy_new_opmath_diff(self): - coeffs, ops = np.array([0.1, -0.3, -0.3]), [qml.X(0), qml.Z(1), qml.Y(0) @ qml.Z(2)] - - dev = qml.device("default.qubit") - - @qml.qnode(dev) - def circuit_dot(coeffs): - H = qml.dot(coeffs, ops) - qml.Qubitization(H, control=[3, 4]) - return qml.expval(qml.PauliZ(0)) - - @qml.qnode(dev) - def circuit_Hamiltonian(coeffs): - H = qml.Hamiltonian(coeffs, ops) - qml.Qubitization(H, control=[3, 4]) - return qml.expval(qml.PauliZ(0)) - - assert np.allclose(qml.grad(circuit_dot)(coeffs), qml.grad(circuit_Hamiltonian)(coeffs)) - def test_copy(): """Test that a Qubitization operator can be copied.""" diff --git a/tests/test_operation.py b/tests/test_operation.py index f1c9c6bd5d4..e22a2fda953 100644 --- a/tests/test_operation.py +++ b/tests/test_operation.py @@ -15,14 +15,10 @@ Unit tests for :mod:`pennylane.operation`. """ import copy -import itertools -import warnings -from functools import reduce import numpy as np import pytest from gate_data import CNOT, I, Toffoli, X -from numpy.linalg import multi_dot import pennylane as qml from pennylane import numpy as pnp @@ -31,11 +27,9 @@ Operation, Operator, StatePrepBase, - Tensor, - convert_to_legacy_H, operation_derivative, ) -from pennylane.ops import Prod, SProd, Sum, cv +from pennylane.ops import Prod, SProd, Sum from pennylane.wires import Wires # pylint: disable=no-self-use, no-member, protected-access, redefined-outer-name, too-few-public-methods @@ -930,25 +924,6 @@ class DummyOp(qml.operation.Operation): assert op.is_hermitian is False -@pytest.mark.usefixtures("use_legacy_and_new_opmath") -class TestObservableTensorLegacySupport: - """Test legacy support of observables with new opmath types""" - - def test_prod_matmul_with_new_opmath(self): - """Test matmul of an Observable with a new opmath instance""" - res = qml.Hadamard(0) @ qml.s_prod(0.5, qml.PauliX(0)) - assert isinstance(res, qml.ops.Prod) - - def test_Observable_sub_with_new_opmath(self): - """Test sub of an Observable with a new opmath instance""" - res = qml.Hadamard(0) - qml.s_prod(0.5, qml.PauliX(0)) - assert isinstance(res, qml.ops.Sum) - - def test_Tensor_arithmetic_depth(self): - op = qml.operation.Tensor(qml.Hadamard(0), qml.Hadamard(1), qml.Hadamard(2)) - assert op.arithmetic_depth == 1 - - class TestObservableConstruction: """Test custom observables construction.""" @@ -1335,936 +1310,6 @@ def test_label_for_operations_with_id(self): assert '"test_with_id"' not in op.label(decimals=2) -# This test is outside the TestTensor class because that class only runs when legacy op math -# is enabled. We want this test to run in normal CI as well. -def test_tensor_deprecation(): - """Test that a deprecation warning is raised when initializing a Tensor""" - ops = [qml.Z(0), qml.Z(1)] - with pytest.warns(qml.PennyLaneDeprecationWarning, match="qml.operation.Tensor"): - _ = Tensor(*ops) - - -@pytest.mark.usefixtures("legacy_opmath_only") -class TestTensor: - """Unit tests for the Tensor class""" - - def test_construct(self): - """Test construction of a tensor product""" - X = qml.PauliX(0) - Y = qml.PauliY(2) - T = Tensor(X, Y) - assert T.obs == [X, Y] - - T = Tensor(T, Y) - assert T.obs == [X, Y, Y] - - with pytest.raises( - ValueError, match="Can only perform tensor products between observables" - ): - Tensor(T, qml.CNOT(wires=[0, 1])) - - def test_flatten_unflatten(self): - """Test flattening and unflattening for tensors.""" - op1 = qml.PauliX(0) - op2 = qml.Hermitian(np.eye(2), wires=1) - t = Tensor(op1, op2) - - data, metadata = t._flatten() - qml.assert_equal(data[0], op1) - qml.assert_equal(data[1], op2) - assert not metadata - assert hash(metadata) - - new_op = Tensor._unflatten(*t._flatten()) - qml.assert_equal(t, new_op) - - def test_warning_for_overlapping_wires(self): - """Test that creating a Tensor with overlapping wires raises a warning""" - X = qml.PauliX(0) - Y = qml.PauliY(0) - op = qml.PauliX(0) @ qml.PauliY(1) - - with pytest.warns(UserWarning, match="Tensor object acts on overlapping wires"): - Tensor(X, Y) - - with pytest.warns(UserWarning, match="Tensor object acts on overlapping wires"): - _ = op @ qml.PauliZ(1) - - def test_queuing_defined_outside(self): - """Test the queuing of a Tensor object.""" - - op1 = qml.PauliX(0) - op2 = qml.PauliY(1) - T = Tensor(op1, op2) - - with qml.queuing.AnnotatedQueue() as q: - T.queue() - - assert len(q.queue) == 1 - assert q.queue[0] is T - - def test_queuing(self): - """Test the queuing of a Tensor object.""" - - with qml.queuing.AnnotatedQueue() as q: - op1 = qml.PauliX(0) - op2 = qml.PauliY(1) - T = Tensor(op1, op2) - - assert len(q) == 1 - assert q.queue[0] is T - - def test_queuing_observable_matmul(self): - """Test queuing when tensor constructed with matmul.""" - - with qml.queuing.AnnotatedQueue() as q: - op1 = qml.PauliX(0) - op2 = qml.PauliY(1) - t = op1 @ op2 - - assert len(q) == 1 - assert q.queue[0] is t - - def test_queuing_tensor_matmul(self): - """Tests the tensor-specific matmul method updates queuing metadata.""" - - with qml.queuing.AnnotatedQueue() as q: - op1 = qml.PauliX(0) - op2 = qml.PauliY(1) - t = Tensor(op1, op2) - - op3 = qml.PauliZ(2) - t2 = t @ op3 - - assert len(q) == 1 - assert q.queue[0] is t2 - - def test_queuing_tensor_matmul_components_outside(self): - """Tests the tensor-specific matmul method when components are defined outside the - queuing context.""" - - op1 = qml.PauliX(0) - op2 = qml.PauliY(1) - t1 = Tensor(op1, op2) - - with qml.queuing.AnnotatedQueue() as q: - op3 = qml.PauliZ(2) - t2 = t1 @ op3 - - assert len(q) == 1 - assert q.queue[0] is t2 - - def test_queuing_tensor_rmatmul(self): - """Tests tensor-specific rmatmul updates queuing metatadata.""" - - with qml.queuing.AnnotatedQueue() as q: - op1 = qml.PauliX(0) - op2 = qml.PauliY(1) - - t1 = op1 @ op2 - - op3 = qml.PauliZ(3) - - t2 = op3 @ t1 - - assert len(q.queue) == 1 - assert q.queue[0] is t2 - - def test_name(self): - """Test that the names of the observables are - returned as expected""" - X = qml.PauliX(0) - Y = qml.PauliY(2) - t = Tensor(X, Y) - assert t.name == [X.name, Y.name] - - def test_batch_size(self): - """Test that the batch_size attribute of the Tensor is initialized as None.""" - X = qml.PauliX(0) - Y = qml.PauliY(2) - t = Tensor(X, Y) - assert t.batch_size is None - - def test_pauli_rep(self): - """Test that the _pauli_rep attribute of the Tensor is initialized correctly.""" - # Pauli rep not None for Pauli observables - X = qml.PauliX(0) - Y = qml.PauliY(2) - t = Tensor(X, Y) - assert t.pauli_rep == qml.pauli.PauliSentence({qml.pauli.PauliWord({0: "X", 2: "Y"}): 1.0}) - - # Puli rep None if observables not valid Pauli observables - H = qml.Hadamard(1) - t = Tensor(X, H) - assert t.pauli_rep is None - - def test_has_matrix(self): - """Test that the Tensor class has a ``has_matrix`` static attribute set to True.""" - assert Tensor.has_matrix is True - - def test_num_wires(self): - """Test that the correct number of wires is returned""" - p = np.eye(4) - X = qml.PauliX(0) - Y = qml.Hermitian(p, wires=[1, 2]) - t = Tensor(X, Y) - assert t.num_wires == 3 - - def test_wires(self): - """Test that the correct nested list of wires is returned""" - p = np.eye(4) - X = qml.PauliX(0) - Y = qml.Hermitian(p, wires=[1, 2]) - t = Tensor(X, Y) - assert t.wires == Wires([0, 1, 2]) - - def test_params(self): - """Test that the correct flattened list of parameters is returned""" - p = np.eye(4) - X = qml.PauliX(0) - Y = qml.Hermitian(p, wires=[1, 2]) - t = Tensor(X, Y) - assert t.data == (p,) - - def test_data_setter_list(self): - """Test the data setter with a list""" - p = np.eye(4) - X = qml.PauliX(0) - Y = qml.Hermitian(p, wires=[1, 2]) - t = Tensor(X, Y) - assert t.data == (p,) - new_data = np.eye(4) * 6 - t.data = [(), (new_data,)] - assert qml.math.allequal(t.data, (new_data,)) - - def test_data_setter_tuple(self): - """Test the data setter with a tuple""" - p = np.eye(4) - X = qml.PauliX(0) - Y = qml.Hermitian(p, wires=[1, 2]) - t = Tensor(X, Y) - assert t.data == (p,) - new_data = np.eye(4) * 6 - t.data = (new_data,) - assert qml.math.allequal(t.data, (new_data,)) - - def test_num_params(self): - """Test that the correct number of parameters is returned""" - p = np.eye(4) - X = qml.PauliX(0) - Y = qml.Hermitian(p, wires=[1, 2]) - Z = qml.Hermitian(p, wires=[3, 4]) - t = Tensor(X, Y, Z) - assert t.num_params == 2 - - def test_parameters(self): - """Test that the correct nested list of parameters is returned""" - p = np.eye(4) - X = qml.PauliX(0) - Y = qml.Hermitian(p, wires=[1, 2]) - t = Tensor(X, Y) - assert t.parameters == [[], [p]] - - def test_label(self): - """Test that Tensors are labelled as expected""" - - x = qml.PauliX(0) - y = qml.PauliZ(2) - T = Tensor(x, y) - - assert T.label() == "X@Z" - assert T.label(decimals=2) == "X@Z" - assert T.label(base_label=["X0", "Z2"]) == "X0@Z2" - - with pytest.raises(ValueError, match=r"Tensor label requires"): - T.label(base_label="nope") - - def test_multiply_obs(self): - """Test that multiplying two observables - produces a tensor""" - X = qml.PauliX(0) - Y = qml.Hadamard(2) - t = X @ Y - assert isinstance(t, Tensor) - assert t.obs == [X, Y] - - def test_multiply_obs_tensor(self): - """Test that multiplying an observable by a tensor - produces a tensor""" - X = qml.PauliX(0) - Y = qml.Hadamard(2) - Z = qml.PauliZ(1) - - t = X @ Y - t = Z @ t - - assert isinstance(t, Tensor) - assert t.obs == [Z, X, Y] - - def test_multiply_tensor_obs(self): - """Test that multiplying a tensor by an observable - produces a tensor""" - X = qml.PauliX(0) - Y = qml.Hadamard(2) - Z = qml.PauliZ(1) - - t = X @ Y - t = t @ Z - - assert isinstance(t, Tensor) - assert t.obs == [X, Y, Z] - - def test_multiply_tensor_tensor(self): - """Test that multiplying a tensor by a tensor - produces a tensor""" - X = qml.PauliX(0) - Y = qml.PauliY(2) - Z = qml.PauliZ(1) - H = qml.Hadamard(3) - - t1 = X @ Y - t2 = Z @ H - t = t2 @ t1 - - assert isinstance(t, Tensor) - assert t.obs == [Z, H, X, Y] - - def test_multiply_tensor_hamiltonian(self): - """Test that a tensor can be multiplied by a hamiltonian.""" - H = qml.PauliX(0) + qml.PauliY(0) - t = qml.PauliZ(1) @ qml.PauliZ(2) - out = t @ H - - expected = qml.Hamiltonian( - [1, 1], - [ - qml.PauliZ(1) @ qml.PauliZ(2) @ qml.PauliX(0), - qml.PauliZ(1) @ qml.PauliZ(2) @ qml.PauliY(0), - ], - ) - qml.assert_equal(out, expected) - - def test_multiply_tensor_in_place(self): - """Test that multiplying a tensor in-place - produces a tensor""" - X = qml.PauliX(0) - Y = qml.PauliY(2) - Z = qml.PauliZ(1) - H = qml.Hadamard(3) - - t = X - t @= Y - t @= Z @ H - - assert isinstance(t, Tensor) - assert t.obs == [X, Y, Z, H] - - def test_operation_multiply_invalid(self): - """Test that an exception is raised if an observable - is matrix-multiplied by a scalar""" - X = qml.PauliX(0) - Z = qml.PauliZ(1) - - with pytest.raises(TypeError, match="unsupported operand type"): - T = X @ Z - _ = 4 @ T - - def test_tensor_matmul_op_is_prod(self): - """Test that Tensor @ non-observable returns a Prod.""" - tensor = qml.PauliX(0) @ qml.PauliY(1) - assert isinstance(tensor, Tensor) - prod = tensor @ qml.S(0) - assert isinstance(prod, qml.ops.Prod) - assert prod.operands == (qml.PauliX(0), qml.PauliY(1), qml.S(0)) - - def test_eigvals(self): - """Test that the correct eigenvalues are returned for the Tensor""" - X = qml.PauliX(0) - Y = qml.PauliY(2) - t = Tensor(X, Y) - assert np.array_equal(t.eigvals(), np.kron([1, -1], [1, -1])) - - # test that the eigvals are now cached and not recalculated - assert np.array_equal(t._eigvals_cache, t.eigvals()) - - @pytest.mark.usefixtures("tear_down_hermitian") - def test_eigvals_hermitian(self, tol): - """Test that the correct eigenvalues are returned for the Tensor containing an Hermitian observable""" - X = qml.PauliX(0) - hamiltonian = np.array([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 0, 1], [0, 0, 1, 0]]) - Herm = qml.Hermitian(hamiltonian, wires=[1, 2]) - t = Tensor(X, Herm) - d = np.kron(np.array([1.0, -1.0]), np.array([-1.0, 1.0, 1.0, 1.0])) - t = t.eigvals() - assert np.allclose(t, d, atol=tol, rtol=0) - - def test_eigvals_identity(self, tol): - """Test that the correct eigenvalues are returned for the Tensor containing an Identity""" - X = qml.PauliX(0) - Iden = qml.Identity(1) - t = Tensor(X, Iden) - d = np.kron(np.array([1.0, -1.0]), np.array([1.0, 1.0])) - t = t.eigvals() - assert np.allclose(t, d, atol=tol, rtol=0) - - def test_eigvals_identity_and_hermitian(self, tol): - """Test that the correct eigenvalues are returned for the Tensor containing - multiple types of observables""" - H = np.diag([1, 2, 3, 4]) - O = qml.PauliX(0) @ qml.Identity(2) @ qml.Hermitian(H, wires=[4, 5]) - res = O.eigvals() - expected = np.kron(np.array([1.0, -1.0]), np.kron(np.array([1.0, 1.0]), np.arange(1, 5))) - assert np.allclose(res, expected, atol=tol, rtol=0) - - def test_diagonalizing_gates(self, tol): - """Test that the correct diagonalizing gate set is returned for a Tensor of observables""" - H = np.diag([1, 2, 3, 4]) - O = qml.PauliX(0) @ qml.Identity(2) @ qml.PauliY(1) @ qml.Hermitian(H, [5, 6]) - - res = O.diagonalizing_gates() - - # diagonalize the PauliX on wire 0 (H.X.H = Z) - assert isinstance(res[0], qml.Hadamard) - assert res[0].wires == Wires([0]) - - # diagonalize the PauliY on wire 1 (U.Y.U^\dagger = Z - # where U = HSZ). - assert isinstance(res[1], qml.PauliZ) - assert res[1].wires == Wires([1]) - assert isinstance(res[2], qml.S) - assert res[2].wires == Wires([1]) - assert isinstance(res[3], qml.Hadamard) - assert res[3].wires == Wires([1]) - - # diagonalize the Hermitian observable on wires 5, 6 - assert isinstance(res[4], qml.QubitUnitary) - assert res[4].wires == Wires([5, 6]) - - O = O @ qml.Hadamard(4) - res = O.diagonalizing_gates() - - # diagonalize the Hadamard observable on wire 4 - # (RY(-pi/4).H.RY(pi/4) = Z) - assert isinstance(res[-1], qml.RY) - assert res[-1].wires == Wires([4]) - assert np.allclose(res[-1].parameters, -np.pi / 4, atol=tol, rtol=0) - - def test_diagonalizing_gates_numerically_diagonalizes(self, tol): - """Test that the diagonalizing gate set numerically - diagonalizes the tensor observable""" - - # create a tensor observable acting on consecutive wires - H = np.diag([1, 2, 3, 4]) - O = qml.PauliX(0) @ qml.PauliY(1) @ qml.Hermitian(H, [2, 3]) - - O_mat = O.matrix() - diag_gates = O.diagonalizing_gates() - - # group the diagonalizing gates based on what wires they act on - U_list = [] - for _, g in itertools.groupby(diag_gates, lambda x: x.wires.tolist()): - # extract the matrices of each diagonalizing gate - mats = [i.matrix() for i in g] - - # Need to revert the order in which the matrices are applied such that they adhere to the order - # of matrix multiplication - # E.g. for PauliY: [PauliZ(wires=self.wires), S(wires=self.wires), Hadamard(wires=self.wires)] - # becomes Hadamard @ S @ PauliZ, where @ stands for matrix multiplication - mats = mats[::-1] - - if len(mats) > 1: - # multiply all unitaries together before appending - mats = [multi_dot(mats)] - - # append diagonalizing unitary for specific wire to U_list - U_list.append(mats[0]) - - # since the test is assuming consecutive wires for each observable - # in the tensor product, it is sufficient to Kronecker product - # the entire list. - U = reduce(np.kron, U_list) - - res = U @ O_mat @ U.conj().T - expected = np.diag(O.eigvals()) - - # once diagonalized by U, the result should be a diagonal - # matrix of the eigenvalues. - assert np.allclose(res, expected, atol=tol, rtol=0) - - def test_tensor_matrix(self, tol): - """Test that the tensor product matrix method returns - the correct result""" - H = np.diag([1, 2, 3, 4]) - O = qml.PauliX(0) @ qml.PauliY(1) @ qml.Hermitian(H, [2, 3]) - - res = O.matrix() - expected = reduce(np.kron, [qml.PauliX.compute_matrix(), qml.PauliY.compute_matrix(), H]) - - assert np.allclose(res, expected, atol=tol, rtol=0) - - def test_matrix_wire_order_not_implemented(self): - """Test that an exception is raised if a wire_order is passed to the matrix method""" - O = qml.PauliX(0) @ qml.PauliY(1) - with pytest.raises(NotImplementedError, match="wire_order"): - O.matrix(wire_order=[1, 0]) - - def test_tensor_matrix_partial_wires_overlap_warning(self): - """Tests that a warning is raised if the wires the factors in - the tensor product act on have partial overlaps.""" - H = np.diag([1, 2, 3, 4]) - O1 = qml.PauliX(0) @ qml.Hermitian(H, [0, 1]) - O2 = qml.Hermitian(H, [0, 1]) @ qml.PauliY(1) - - for O in (O1, O2): - with pytest.warns(UserWarning, match="partially overlapping"): - O.matrix() - - def test_tensor_matrix_too_large_warning(self): - """Tests that a warning is raised if wires occur in multiple of the - factors in the tensor product, leading to a wrongly-sized matrix.""" - with pytest.warns(UserWarning, match="acts on overlapping wires"): - O = qml.PauliX(0) @ qml.PauliX(1) @ qml.PauliX(0) - with pytest.warns(UserWarning, match="The size of the returned matrix"): - O.matrix() - - @pytest.mark.parametrize("classes", [(qml.PauliX, qml.PauliX), (qml.PauliZ, qml.PauliX)]) - def test_multiplication_matrix(self, tol, classes): - """If using the ``@`` operator on two observables acting on the - same wire, the tensor class should treat this as matrix multiplication.""" - c1, c2 = classes - with pytest.warns(UserWarning, match="acts on overlapping wires"): - O = c1(0) @ c2(0) - - res = O.matrix() - expected = c1.compute_matrix() @ c2.compute_matrix() - - assert np.allclose(res, expected, atol=tol, rtol=0) - - herm_matrix = np.array([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]) - - with qml.operation.disable_new_opmath_cm(warn=False): - tensor_obs = [ - (qml.PauliZ(0) @ qml.Identity(1) @ qml.PauliZ(2), [qml.PauliZ(0), qml.PauliZ(2)]), - ( - qml.Identity(0) - @ qml.PauliX(1) - @ qml.Identity(2) - @ qml.PauliZ(3) - @ qml.PauliZ(4) - @ qml.Identity(5), - [qml.PauliX(1), qml.PauliZ(3), qml.PauliZ(4)], - ), - # List containing single observable is returned - (qml.PauliZ(0) @ qml.Identity(1), [qml.PauliZ(0)]), - (qml.Identity(0) @ qml.PauliX(1) @ qml.Identity(2), [qml.PauliX(1)]), - (qml.Identity(0) @ qml.Identity(1), [qml.Identity(0)]), - ( - qml.Identity(0) @ qml.Identity(1) @ qml.Hermitian(herm_matrix, wires=[2, 3]), - [qml.Hermitian(herm_matrix, wires=[2, 3])], - ), - ] - - @pytest.mark.parametrize("tensor_observable, expected", tensor_obs) - def test_non_identity_obs(self, tensor_observable, expected): - """Tests that the non_identity_obs property returns a list that contains no Identity instances.""" - - O = tensor_observable - for idx, obs in enumerate(O.non_identity_obs): - assert isinstance(obs, type(expected[idx])) - assert obs.wires == expected[idx].wires - - with qml.operation.disable_new_opmath_cm(warn=False): - tensor_obs_pruning = [ - (qml.PauliZ(0) @ qml.Identity(1) @ qml.PauliZ(2), qml.PauliZ(0) @ qml.PauliZ(2)), - ( - qml.Identity(0) - @ qml.PauliX(1) - @ qml.Identity(2) - @ qml.PauliZ(3) - @ qml.PauliZ(4) - @ qml.Identity(5), - qml.PauliX(1) @ qml.PauliZ(3) @ qml.PauliZ(4), - ), - # Single observable is returned - (qml.PauliZ(0) @ qml.Identity(1), qml.PauliZ(0)), - (qml.Identity(0) @ qml.PauliX(1) @ qml.Identity(2), qml.PauliX(1)), - (qml.Identity(0) @ qml.Identity(1), qml.Identity(0)), - (qml.Identity(0) @ qml.Identity(1), qml.Identity(0)), - ( - qml.Identity(0) @ qml.Identity(1) @ qml.Hermitian(herm_matrix, wires=[2, 3]), - qml.Hermitian(herm_matrix, wires=[2, 3]), - ), - ] - - @pytest.mark.parametrize("tensor_observable, expected", tensor_obs_pruning) - def test_prune(self, tensor_observable, expected): - """Tests that the prune method returns the expected Tensor or single non-Tensor Observable.""" - O = tensor_observable - - O_pruned = O.prune() - assert isinstance(O_pruned, type(expected)) - assert O_pruned.wires == expected.wires - - def test_prune_while_queuing_return_tensor(self): - """Tests that pruning a tensor to a tensor in a tape context registers - the pruned tensor as owned by the measurement, - and turns the original tensor into an orphan without an owner.""" - - with qml.queuing.AnnotatedQueue() as q: - # we assign operations to variables here so we can compare them below - a = qml.PauliX(wires=0) - b = qml.PauliY(wires=1) - c = qml.Identity(wires=2) - T = qml.operation.Tensor(a, b, c) - T_pruned = T.prune() - m = qml.expval(T_pruned) - - assert len(q.queue) == 1 - assert q.queue[0] is m - - def test_prune_while_queueing_return_obs(self): - """Tests that pruning a tensor to an observable in a tape context registers - the pruned observable as owned by the measurement, - and turns the original tensor into an orphan without an owner.""" - - with qml.queuing.AnnotatedQueue() as q: - a = qml.PauliX(wires=0) - c = qml.Identity(wires=2) - T = qml.operation.Tensor(a, c) - T_pruned = T.prune() - m = qml.expval(T_pruned) - - assert len(q.queue) == 1 - assert q.queue[0] is m - - def test_sparse_matrix_no_wires(self): - """Tests that the correct sparse matrix representation is used.""" - - t = qml.PauliX(0) @ qml.PauliZ(1) - s = t.sparse_matrix() - - assert np.allclose(s.data, [1, -1, 1, -1]) - assert np.allclose(s.indices, [2, 3, 0, 1]) - assert np.allclose(s.indptr, [0, 1, 2, 3, 4]) - - def test_sparse_matrix_swapped_wires(self): - """Tests that the correct sparse matrix representation is used - when the custom wires swap the order.""" - - t = qml.PauliX(0) @ qml.PauliZ(1) - data = [1, 1, -1, -1] - indices = [1, 0, 3, 2] - indptr = [0, 1, 2, 3, 4] - - s = t.sparse_matrix(wires=[1, 0]) - - assert np.allclose(s.data, data) - assert np.allclose(s.indices, indices) - assert np.allclose(s.indptr, indptr) - - s = t.sparse_matrix(wire_order=[1, 0]) - - assert np.allclose(s.data, data) - assert np.allclose(s.indices, indices) - assert np.allclose(s.indptr, indptr) - - def test_sparse_matrix_extra_wire(self): - """Tests that the correct sparse matrix representation is used - when the custom wires add an extra wire with an implied identity operation.""" - - t = qml.PauliX(0) @ qml.PauliZ(1) - data = [1.0, 1.0, -1.0, -1.0, 1.0, 1.0, -1.0, -1.0] - indices = [4, 5, 6, 7, 0, 1, 2, 3] - indptr = [0, 1, 2, 3, 4, 5, 6, 7, 8] - - s = t.sparse_matrix(wires=[0, 1, 2]) - - assert s.shape == (8, 8) - assert np.allclose(s.data, data) - assert np.allclose(s.indices, indices) - assert np.allclose(s.indptr, indptr) - - s = t.sparse_matrix(wire_order=[0, 1, 2]) - - assert s.shape == (8, 8) - assert np.allclose(s.data, data) - assert np.allclose(s.indices, indices) - assert np.allclose(s.indptr, indptr) - - def test_sparse_matrix_errors(self): - """Tests that errors are raised when the sparse matrix is computed for a tensor - whose constituent operations are not all single-qubit gates, and when both ``wires`` - and ``wire_order`` at specified at once.""" - - t = qml.PauliX(0) @ qml.Hermitian(np.eye(4), wires=[1, 2]) - with pytest.raises(ValueError, match="Can only compute"): - t.sparse_matrix() - - t = qml.PauliX(0) @ qml.PauliZ(1) - with pytest.raises(ValueError, match="Wire order has been specified twice"): - t.sparse_matrix(wires=[0, 1], wire_order=[0, 1]) - - def test_copy(self): - """Test copying of a Tensor.""" - tensor = Tensor(qml.PauliX(0), qml.PauliY(1), qml.PauliZ(2)) - c = copy.copy(tensor) - assert c is not tensor - assert c.wires == Wires([0, 1, 2]) - assert c.batch_size == tensor.batch_size == None - for obs1, obs2 in zip(c.obs, tensor.obs): - qml.assert_equal(obs1, obs2) - - def test_map_wires(self): - """Test the map_wires method.""" - tensor = Tensor(qml.PauliX(0), qml.PauliY(1), qml.PauliZ(2)) - wire_map = {0: 10, 1: 11, 2: 12} - mapped_tensor = tensor.map_wires(wire_map=wire_map) - final_obs = [qml.PauliX(10), qml.PauliY(11), qml.PauliZ(12)] - assert tensor is not mapped_tensor - assert tensor.wires == Wires([0, 1, 2]) - assert mapped_tensor.wires == Wires([10, 11, 12]) - assert mapped_tensor.batch_size == tensor.batch_size - for obs1, obs2 in zip(mapped_tensor.obs, final_obs): - qml.assert_equal(obs1, obs2) - assert mapped_tensor.pauli_rep == Tensor(*final_obs).pauli_rep - - def test_map_wires_no_pauli_rep(self): - """Test that map_wires sets the pauli rep correctly if the original - Tensor did not have a pauli rep.""" - tensor = Tensor(qml.PauliX(0), qml.Hadamard(1)) - wire_map = {0: 10, 1: 11} - expected_tensor = Tensor(qml.PauliX(10), qml.Hadamard(11)) - - mapped_tensor = tensor.map_wires(wire_map=wire_map) - assert tensor is not mapped_tensor - assert mapped_tensor == expected_tensor - assert mapped_tensor.pauli_rep is None - - def test_matmul_not_implemented(self): - """Test that matrix multiplication raises TypeError if unsupported - object is used.""" - - op = Tensor(qml.PauliX(0), qml.PauliZ(1)) - - with pytest.raises(TypeError, match="unsupported operand type"): - _ = op @ 1.0 - - @pytest.mark.jax - def test_matrix_jax_projector(self): - """Test that matrix can be computed with a jax projector.""" - - import jax - - def f(state): - op = qml.Projector(state, wires=0) - return qml.operation.Tensor(op, qml.Z(1)).matrix() - - res = jax.jit(f)(jax.numpy.array([0, 1])) - expected = np.array([[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 1, 0], [0, 0, 0, -1]]) - assert qml.math.allclose(res, expected) - - -with qml.operation.disable_new_opmath_cm(warn=False): - with warnings.catch_warnings(): - warnings.filterwarnings( - "ignore", "qml.ops.Hamiltonian uses", qml.PennyLaneDeprecationWarning - ) - warnings.filterwarnings( - "ignore", "qml.operation.Tensor uses", qml.PennyLaneDeprecationWarning - ) - - equal_obs = [ - (qml.PauliZ(0), qml.PauliZ(0), True), - (qml.PauliZ(0) @ qml.PauliX(1), qml.PauliZ(0) @ qml.PauliX(1) @ qml.Identity(2), True), - (qml.PauliZ("b"), qml.PauliZ("b") @ qml.Identity(1.3), True), - (qml.PauliZ(0) @ qml.Identity(1), qml.PauliZ(0), True), - (qml.PauliZ(0), qml.PauliZ(1) @ qml.Identity(0), False), - ( - qml.Hermitian(np.array([[0, 1], [1, 0]]), 0), - qml.Identity(1) @ qml.Hermitian(np.array([[0, 1], [1, 0]]), 0), - True, - ), - (qml.PauliZ("a") @ qml.PauliX(1), qml.PauliX(1) @ qml.PauliZ("a"), True), - (qml.PauliZ("a"), qml.Hamiltonian([1], [qml.PauliZ("a")]), True), - ] - - add_obs = [ - (qml.PauliZ(0) @ qml.Identity(1), qml.PauliZ(0), qml.Hamiltonian([2], [qml.PauliZ(0)])), - ( - qml.PauliZ(0), - qml.PauliZ(0) @ qml.PauliX(1), - qml.Hamiltonian([1, 1], [qml.PauliZ(0), qml.PauliZ(0) @ qml.PauliX(1)]), - ), - ( - qml.PauliZ("b") @ qml.Identity(1), - qml.Hamiltonian([3], [qml.PauliZ("b")]), - qml.Hamiltonian([4], [qml.PauliZ("b")]), - ), - ( - qml.PauliX(0) @ qml.PauliZ(1), - qml.PauliZ(1) @ qml.Identity(2) @ qml.PauliX(0), - qml.Hamiltonian([2], [qml.PauliX(0) @ qml.PauliZ(1)]), - ), - ( - qml.Hermitian(np.array([[1, 0], [0, -1]]), 1.2), - qml.Hamiltonian([3], [qml.Hermitian(np.array([[1, 0], [0, -1]]), 1.2)]), - qml.Hamiltonian([4], [qml.Hermitian(np.array([[1, 0], [0, -1]]), 1.2)]), - ), - ] - - add_zero_obs = [ - qml.PauliX(0), - qml.Hermitian(np.array([[1, 0], [0, -1]]), 1.2), - qml.PauliX(0) @ qml.Hadamard(2), - # qml.Projector(np.array([1, 1]), wires=[0, 1]), - # qml.SparseHamiltonian(csr_matrix(np.array([[1, 0], [-1.5, 0]])), 1), - # CVObservables - qml.Identity(1), - cv.NumberOperator(wires=[1]), - cv.TensorN(wires=[1]), - cv.QuadX(wires=[1]), - cv.QuadP(wires=[1]), - # cv.QuadOperator(1.234, wires=0), - # cv.FockStateProjector([1,2,3], wires=[0, 1, 2]), - cv.PolyXP(np.array([1.0, 2.0, 3.0]), wires=[0]), - ] - - mul_obs = [ - (qml.PauliZ(0), 3, qml.Hamiltonian([3], [qml.PauliZ(0)])), - (qml.PauliZ(0) @ qml.Identity(1), 3, qml.Hamiltonian([3], [qml.PauliZ(0)])), - ( - qml.PauliZ(0) @ qml.PauliX(1), - 4.5, - qml.Hamiltonian([4.5], [qml.PauliZ(0) @ qml.PauliX(1)]), - ), - ( - qml.Hermitian(np.array([[1, 0], [0, -1]]), "c"), - 3, - qml.Hamiltonian([3], [qml.Hermitian(np.array([[1, 0], [0, -1]]), "c")]), - ), - ] - - matmul_obs = [ - (qml.PauliX(0), qml.PauliZ(1), Tensor(qml.PauliX(0), qml.PauliZ(1))), # obs @ obs - ( - qml.PauliX(0), - qml.PauliZ(1) @ qml.PauliY(2), - Tensor(qml.PauliX(0), qml.PauliZ(1), qml.PauliY(2)), - ), # obs @ tensor - ( - qml.PauliX(0), - qml.Hamiltonian([1.0], [qml.PauliY(1)]), - qml.Hamiltonian([1.0], [qml.PauliX(0) @ qml.PauliY(1)]), - ), # obs @ hamiltonian - ] - - sub_obs = [ - (qml.PauliZ(0) @ qml.Identity(1), qml.PauliZ(0), qml.Hamiltonian([], [])), - ( - qml.PauliZ(0), - qml.PauliZ(0) @ qml.PauliX(1), - qml.Hamiltonian([1, -1], [qml.PauliZ(0), qml.PauliZ(0) @ qml.PauliX(1)]), - ), - ( - qml.PauliZ(0) @ qml.Identity(1), - qml.Hamiltonian([3], [qml.PauliZ(0)]), - qml.Hamiltonian([-2], [qml.PauliZ(0)]), - ), - ( - qml.PauliX(0) @ qml.PauliZ(1), - qml.PauliZ(3) @ qml.Identity(2) @ qml.PauliX(0), - qml.Hamiltonian( - [1, -1], [qml.PauliX(0) @ qml.PauliZ(1), qml.PauliZ(3) @ qml.PauliX(0)] - ), - ), - ( - qml.Hermitian(np.array([[1, 0], [0, -1]]), 1.2), - qml.Hamiltonian([3], [qml.Hermitian(np.array([[1, 0], [0, -1]]), 1.2)]), - qml.Hamiltonian([-2], [qml.Hermitian(np.array([[1, 0], [0, -1]]), 1.2)]), - ), - ] - - -@pytest.mark.usefixtures("legacy_opmath_only") -class TestTensorObservableOperations: - """Tests arithmetic operations between observables/tensors""" - - def test_data(self): - """Tests the data() method for Tensors and Observables""" - - obs = qml.PauliZ(0) - data = obs._obs_data() - - assert data == {("PauliZ", Wires(0), ())} - - obs = qml.PauliZ(0) @ qml.PauliX(1) - data = obs._obs_data() - - assert data == {("PauliZ", Wires(0), ()), ("PauliX", Wires(1), ())} - - obs = qml.Hermitian(np.array([[1, 0], [0, -1]]), 0) - data = obs._obs_data() - - assert data == { - ( - "Hermitian", - Wires(0), - ( - b"\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff", - ), - ) - } - - def test_equality_error(self): - """Tests that the correct error is raised when compare() is called on invalid type""" - - obs = qml.PauliZ(0) - tensor = qml.PauliZ(0) @ qml.PauliX(1) - A = [[1, 0], [0, -1]] - with pytest.raises( - ValueError, - match=r"Can only compare an Observable/Tensor, and a Hamiltonian/Observable/Tensor.", - ): - obs.compare(A) - tensor.compare(A) - - @pytest.mark.parametrize(("obs1", "obs2", "res"), equal_obs) - def test_equality(self, obs1, obs2, res): - """Tests the compare() method for Tensors and Observables""" - assert obs1.compare(obs2) == res - - @pytest.mark.parametrize(("obs1", "obs2", "obs"), add_obs) - def test_addition(self, obs1, obs2, obs): - """Tests addition between Tensors and Observables""" - assert obs.compare(obs1 + obs2) - - @pytest.mark.parametrize("obs", add_zero_obs) - def test_add_zero(self, obs): - """Tests adding Tensors and Observables to zero""" - assert obs.compare(obs + 0) - assert obs.compare(0 + obs) - assert obs.compare(obs + 0.0) - assert obs.compare(0.0 + obs) - assert obs.compare(obs + 0e1) - assert obs.compare(0e1 + obs) - - @pytest.mark.parametrize(("coeff", "obs", "res_obs"), mul_obs) - def test_scalar_multiplication(self, coeff, obs, res_obs): - """Tests scalar multiplication of Tensors and Observables""" - assert res_obs.compare(coeff * obs) - assert res_obs.compare(obs * coeff) - - @pytest.mark.parametrize(("obs1", "obs2", "obs"), sub_obs) - def test_subtraction(self, obs1, obs2, obs): - """Tests subtraction between Tensors and Observables""" - assert obs.compare(obs1 - obs2) - - @pytest.mark.parametrize(("obs1", "obs2", "res"), matmul_obs) - def test_tensor_product(self, obs1, obs2, res): - """Tests the tensor product between Observables""" - assert res.compare(obs1 @ obs2) - - # Dummy class inheriting from Operator class MyOp(Operator): num_wires = 1 @@ -2642,7 +1687,6 @@ def test_composed(self): ] -@pytest.mark.usefixtures("new_opmath_only") class TestNewOpMath: """Tests dunder operations with new operator arithmetic enabled.""" @@ -2767,7 +1811,6 @@ def test_mul_does_auto_simplify(self): class TestHamiltonianLinearCombinationAlias: """Unit tests for using qml.Hamiltonian as an alias for LinearCombination""" - @pytest.mark.usefixtures("new_opmath_only") def test_hamiltonian_linear_combination_alias_enabled(self): """Test that qml.Hamiltonian is an alias for LinearCombination with new operator arithmetic enabled""" @@ -2775,21 +1818,6 @@ def test_hamiltonian_linear_combination_alias_enabled(self): assert isinstance(op, qml.ops.LinearCombination) assert isinstance(op, qml.Hamiltonian) - assert not isinstance(op, qml.ops.Hamiltonian) - assert not isinstance(op, qml.ops.qubit.Hamiltonian) - assert not isinstance(op, qml.ops.qubit.hamiltonian.Hamiltonian) - - @pytest.mark.usefixtures("legacy_opmath_only") - def test_hamiltonian_linear_combination_alias_disabled(self): - """Test that qml.Hamiltonian is not an alias for LinearCombination with new operator - arithmetic disabled""" - op = qml.Hamiltonian([1.0], [qml.X(0)]) - - assert not isinstance(op, qml.ops.LinearCombination) - assert isinstance(op, qml.Hamiltonian) - assert isinstance(op, qml.ops.Hamiltonian) - assert isinstance(op, qml.ops.qubit.Hamiltonian) - assert isinstance(op, qml.ops.qubit.hamiltonian.Hamiltonian) @pytest.mark.parametrize( @@ -2828,46 +1856,6 @@ def test_symmetric_matrix_early_return(op, mocker): assert np.allclose(actual, manually_expanded) -@pytest.mark.usefixtures("use_legacy_and_new_opmath") -def test_op_arithmetic_toggle(): - """Tests toggling op arithmetic on and off""" - - with pytest.warns(qml.PennyLaneDeprecationWarning, match="Toggling the new approach"): - with qml.operation.enable_new_opmath_cm(): - assert qml.operation.active_new_opmath() - assert isinstance(qml.PauliX(0) @ qml.PauliZ(1), Prod) - - with pytest.warns(qml.PennyLaneDeprecationWarning, match="Disabling the new approach"): - with qml.operation.disable_new_opmath_cm(): - assert not qml.operation.active_new_opmath() - assert isinstance(qml.PauliX(0) @ qml.PauliZ(1), Tensor) - - -@pytest.mark.usefixtures("new_opmath_only") -def test_op_arithmetic_default(): - """Test that new op math is enabled by default""" - assert qml.operation.active_new_opmath() - - -@pytest.mark.usefixtures("new_opmath_only") -def test_disable_enable_new_opmath(): - """Test that disabling and re-enabling new opmath works and raises the correct warning""" - with pytest.warns( - qml.PennyLaneDeprecationWarning, match="Disabling the new approach to operator arithmetic" - ): - qml.operation.disable_new_opmath() - - assert not qml.operation.active_new_opmath() - - with pytest.warns( - qml.PennyLaneDeprecationWarning, - match="Toggling the new approach to operator arithmetic", - ): - qml.operation.enable_new_opmath() - - assert qml.operation.active_new_opmath() - - def test_docstring_example_of_operator_class(tol): """Tests an example of how to create an operator which is used in the Operator class docstring, as well as in the 'adding_operators' @@ -2941,137 +1929,6 @@ class CustomOperator(qml.operation.Operator): qml.assert_equal(new_op, CustomOperator(2.3, wires=0)) -@pytest.mark.usefixtures("new_opmath_only") -def test_use_new_opmath_fixture(): - """Test that the fixture for using new opmath in a context works as expected""" - assert qml.operation.active_new_opmath() - - -@pytest.mark.usefixtures("legacy_opmath_only") -def test_legacy_opmath_only_fixture(): - """Test that the fixture for using new opmath in a context works as expected""" - assert not qml.operation.active_new_opmath() - - -with warnings.catch_warnings(): - warnings.filterwarnings("ignore", "qml.operation.Tensor uses", qml.PennyLaneDeprecationWarning) - - CONVERT_HAMILTONIAN = [ - ( - [1.5, 0.5, 1, 1], - [ - qml.Identity(1), - Tensor(qml.Z(1), qml.Z(2)), - Tensor(qml.X(1), qml.Y(2)), - qml.Hadamard(1), - ], - ), - ([0.5], [qml.X(1)]), - ([1], [Tensor(qml.X(0), qml.Y(1))]), - ( - [-0.5, 0.4, -0.3, 0.2], - [ - qml.Identity(0, 1), - Tensor(qml.X(1), qml.Y(2)), - qml.Identity(1), - Tensor(qml.Z(1), qml.Z(2)), - ], - ), - ( - [0.0625, 0.0625, -0.0625, 0.0625, -0.0625, 0.0625, -0.0625, -0.0625], - [ - Tensor(qml.Hadamard(0), qml.X(1), qml.X(2), qml.Y(3)), - Tensor(qml.X(0), qml.X(1), qml.Y(2), qml.X(3)), - Tensor(qml.X(0), qml.Y(1), qml.X(2), qml.X(3)), - Tensor(qml.X(0), qml.Y(1), qml.Y(2), qml.Y(3)), - Tensor(qml.Y(0), qml.X(1), qml.X(2), qml.X(3)), - Tensor(qml.Y(0), qml.X(1), qml.Hadamard(2), qml.Y(3)), - Tensor(qml.Y(0), qml.Y(1), qml.X(2), qml.Y(3)), - Tensor(qml.Y(0), qml.Y(1), qml.Y(2), qml.Hadamard(3)), - ], - ), - ] - - -@pytest.mark.usefixtures("new_opmath_only") -@pytest.mark.parametrize("coeffs, obs", CONVERT_HAMILTONIAN) -def test_convert_to_hamiltonian(coeffs, obs): - """Test that arithmetic operators can be converted to Hamiltonian instances""" - - opmath_instance = qml.dot(coeffs, obs) - with pytest.warns( - qml.PennyLaneDeprecationWarning, match="qml.ops.Hamiltonian uses the old approach" - ): - converted_opmath = convert_to_legacy_H(opmath_instance) - hamiltonian_instance = qml.ops.Hamiltonian(coeffs, obs) - - assert isinstance(converted_opmath, qml.ops.Hamiltonian) - qml.assert_equal(converted_opmath, hamiltonian_instance) - - -@pytest.mark.usefixtures("legacy_opmath_only") -@pytest.mark.parametrize( - "coeffs, obs", [([1], [qml.Hadamard(1)]), ([0.5, 0.5], [qml.Identity(1), qml.Identity(1)])] -) -def test_convert_to_hamiltonian_trivial(coeffs, obs): - """Test that non-arithmetic operator after simplification is returned as an Observable""" - - opmath_instance = qml.dot(coeffs, obs) - converted_opmath = convert_to_legacy_H(opmath_instance) - assert isinstance(converted_opmath, qml.operation.Observable) - - -@pytest.mark.parametrize( - "coeffs, obs", - [ - ([2], [qml.T(1)]), - ([0.5, 2], [qml.T(0), qml.Identity(1)]), - ([1, 2], [qml.T(0), qml.Identity(1)]), - ([0.5, 0.5], [qml.T(0), qml.T(0)]), - ], -) -def test_convert_to_hamiltonian_error(coeffs, obs): - """Test that arithmetic operator raise an error if there is a non-Observable""" - - with pytest.raises(ValueError): - convert_to_legacy_H(qml.dot(coeffs, obs)) - - -@pytest.mark.usefixtures("new_opmath_only") -@pytest.mark.filterwarnings("ignore::pennylane.PennyLaneDeprecationWarning") -def test_convert_to_H(): - operator = ( - 2 * qml.X(0) - + 3 * qml.X(0) - + qml.Y(1) @ qml.Z(2) @ (2 * qml.X(3)) - + 2 * (qml.Hadamard(3) + 3 * qml.Z(2)) - ) - with qml.operation.disable_new_opmath_cm(warn=False): - legacy_H = qml.operation.convert_to_H(operator) - linear_combination = qml.operation.convert_to_H(operator) - - assert isinstance(legacy_H, qml.ops.Hamiltonian) - assert isinstance(linear_combination, qml.ops.LinearCombination) - - # coeffs match - legacy_coeffs, legacy_ops = legacy_H.terms() - coeffs, ops = linear_combination.terms() - assert np.all(legacy_coeffs == coeffs) - - # legacy version has Tensors and not Prods, new version opposite - assert Tensor in [type(o) for o in legacy_ops] - assert Tensor not in [type(o) for o in ops] - assert qml.ops.op_math.Prod not in [type(o) for o in legacy_ops] - assert qml.ops.op_math.Prod in [type(o) for o in ops] - - # ops match - for legacy_op, op in zip(legacy_ops, ops): - assert np.all(legacy_op.matrix() == op.matrix()) - - # the converted op is the same as the original op - qml.assert_equal(operator.simplify(), linear_combination.simplify()) - - # pylint: disable=unused-import,no-name-in-module def test_get_attr(): """Test that importing attributes of operation work as expected""" @@ -3092,28 +1949,3 @@ def test_get_attr(): assert ( StatePrep is qml.operation.StatePrepBase ) # StatePrep imported from operation.py is an alias for StatePrepBase - - -@pytest.mark.parametrize( - "make_op", - [ - lambda: qml.Hamiltonian([1, 2], [qml.PauliX(0), qml.PauliY(1)]), - lambda: 1.2 * qml.PauliX(0), - ], -) -@pytest.mark.usefixtures("use_legacy_and_new_opmath") -def test_convert_to_opmath_queueing(make_op): - """Tests that converting to opmath dequeues the original operation""" - - with qml.queuing.AnnotatedQueue() as q: - if not qml.operation.active_new_opmath(): - with pytest.warns( - qml.PennyLaneDeprecationWarning, match="qml.ops.Hamiltonian uses the old approach" - ): - original_op = make_op() - else: - original_op = make_op() - new_op = qml.operation.convert_to_opmath(original_op) - - assert len(q.queue) == 1 - assert q.queue[0] is new_op diff --git a/tests/test_qaoa.py b/tests/test_qaoa.py index da8d948e4e0..326b46297a3 100644 --- a/tests/test_qaoa.py +++ b/tests/test_qaoa.py @@ -105,7 +105,7 @@ def matrix(hamiltonian: qml.Hamiltonian, n_wires: int) -> csc_matrix: op_matrix = op.sparse_matrix(wire_order=list(range(n_wires))) else: op_wires = np.array(op.wires.tolist()) - op_list = op.non_identity_obs if isinstance(op, qml.operation.Tensor) else [op] + op_list = [op] op_matrices = [] for wire in range(n_wires): @@ -325,7 +325,6 @@ def make_bit_flip_mixer_test_cases(): class TestMixerHamiltonians: """Tests that the mixer Hamiltonians are being generated correctly""" - @pytest.mark.usefixtures("use_legacy_and_new_opmath") def test_x_mixer_output(self): """Tests that the output of the Pauli-X mixer is correct""" @@ -337,7 +336,6 @@ def test_x_mixer_output(self): ) assert mixer_hamiltonian.compare(expected_hamiltonian) - @pytest.mark.usefixtures("use_legacy_and_new_opmath") def test_x_mixer_grouping(self): """Tests that the grouping information is set and correct""" @@ -360,13 +358,9 @@ def test_xy_mixer_type_error(self): ): qaoa.xy_mixer(graph) - @pytest.mark.usefixtures("use_legacy_and_new_opmath") @pytest.mark.parametrize(("graph", "target_hamiltonian"), make_xy_mixer_test_cases()) def test_xy_mixer_output(self, graph, target_hamiltonian): """Tests that the output of the XY mixer is correct""" - - if not qml.operation.active_new_opmath(): - target_hamiltonian = qml.operation.convert_to_legacy_H(target_hamiltonian) hamiltonian = qaoa.xy_mixer(graph) assert hamiltonian.compare(target_hamiltonian) @@ -387,12 +381,8 @@ def test_bit_flip_mixer_errors(self): ("graph", "n", "target_hamiltonian"), make_bit_flip_mixer_test_cases(), ) - @pytest.mark.usefixtures("use_legacy_and_new_opmath") def test_bit_flip_mixer_output(self, graph, n, target_hamiltonian): """Tests that the output of the bit-flip mixer is correct""" - - if not qml.operation.active_new_opmath(): - target_hamiltonian = qml.operation.convert_to_legacy_H(target_hamiltonian) hamiltonian = qaoa.bit_flip_mixer(graph, n) assert hamiltonian.compare(target_hamiltonian) @@ -928,7 +918,6 @@ def test_bit_driver_error(self): with pytest.raises(ValueError, match=r"'b' must be either 0 or 1"): qaoa.bit_driver(range(3), 2) - @pytest.mark.usefixtures("use_legacy_and_new_opmath") def test_bit_driver_output(self): """Tests that the bit driver Hamiltonian has the correct output""" @@ -953,13 +942,9 @@ def test_edge_driver_errors(self): with pytest.raises(ValueError, match=r"Input graph must be a nx.Graph or rx.PyGraph"): qaoa.edge_driver([(0, 1), (1, 2)], ["00", "11"]) - @pytest.mark.usefixtures("use_legacy_and_new_opmath") @pytest.mark.parametrize(("graph", "reward", "hamiltonian"), make_edge_driver_cost_test_cases()) def test_edge_driver_output(self, graph, reward, hamiltonian): """Tests that the edge driver Hamiltonian throws the correct errors""" - - if not qml.operation.active_new_opmath(): - hamiltonian = qml.operation.convert_to_legacy_H(hamiltonian) H = qaoa.edge_driver(graph, reward) assert hamiltonian.compare(H) @@ -988,18 +973,12 @@ def test_cost_graph_error(self): @pytest.mark.parametrize( ("graph", "cost_hamiltonian", "mixer_hamiltonian"), make_max_cut_test_cases() ) - @pytest.mark.usefixtures("use_legacy_and_new_opmath") def test_maxcut_output(self, graph, cost_hamiltonian, mixer_hamiltonian): """Tests that the output of the MaxCut method is correct""" - - if not qml.operation.active_new_opmath(): - cost_hamiltonian = qml.operation.convert_to_legacy_H(cost_hamiltonian) - mixer_hamiltonian = qml.operation.convert_to_legacy_H(mixer_hamiltonian) cost_h, mixer_h = qaoa.maxcut(graph) assert cost_h.compare(cost_hamiltonian) assert mixer_h.compare(mixer_hamiltonian) - @pytest.mark.usefixtures("use_legacy_and_new_opmath") def test_maxcut_grouping(self): """Tests that the grouping information is set and correct""" @@ -1017,18 +996,12 @@ def test_maxcut_grouping(self): ("graph", "constrained", "cost_hamiltonian", "mixer_hamiltonian"), make_max_independent_test_cases(), ) - @pytest.mark.usefixtures("use_legacy_and_new_opmath") def test_mis_output(self, graph, constrained, cost_hamiltonian, mixer_hamiltonian): """Tests that the output of the Max Indepenent Set method is correct""" - - if not qml.operation.active_new_opmath(): - cost_hamiltonian = qml.operation.convert_to_legacy_H(cost_hamiltonian) - mixer_hamiltonian = qml.operation.convert_to_legacy_H(mixer_hamiltonian) cost_h, mixer_h = qaoa.max_independent_set(graph, constrained=constrained) assert cost_h.compare(cost_hamiltonian) assert mixer_h.compare(mixer_hamiltonian) - @pytest.mark.usefixtures("use_legacy_and_new_opmath") def test_mis_grouping(self): """Tests that the grouping information is set and correct""" @@ -1046,18 +1019,12 @@ def test_mis_grouping(self): ("graph", "constrained", "cost_hamiltonian", "mixer_hamiltonian"), make_min_vertex_cover_test_cases(), ) - @pytest.mark.usefixtures("use_legacy_and_new_opmath") def test_mvc_output(self, graph, constrained, cost_hamiltonian, mixer_hamiltonian): """Tests that the output of the Min Vertex Cover method is correct""" - - if not qml.operation.active_new_opmath(): - cost_hamiltonian = qml.operation.convert_to_legacy_H(cost_hamiltonian) - mixer_hamiltonian = qml.operation.convert_to_legacy_H(mixer_hamiltonian) cost_h, mixer_h = qaoa.min_vertex_cover(graph, constrained=constrained) assert cost_h.compare(cost_hamiltonian) assert mixer_h.compare(mixer_hamiltonian) - @pytest.mark.usefixtures("use_legacy_and_new_opmath") def test_mvc_grouping(self): """Tests that the grouping information is set and correct""" @@ -1075,18 +1042,12 @@ def test_mvc_grouping(self): ("graph", "constrained", "cost_hamiltonian", "mixer_hamiltonian"), make_max_clique_test_cases(), ) - @pytest.mark.usefixtures("use_legacy_and_new_opmath") def test_max_clique_output(self, graph, constrained, cost_hamiltonian, mixer_hamiltonian): """Tests that the output of the Maximum Clique method is correct""" - - if not qml.operation.active_new_opmath(): - cost_hamiltonian = qml.operation.convert_to_legacy_H(cost_hamiltonian) - mixer_hamiltonian = qml.operation.convert_to_legacy_H(mixer_hamiltonian) cost_h, mixer_h = qaoa.max_clique(graph, constrained=constrained) assert cost_h.compare(cost_hamiltonian) assert mixer_h.compare(mixer_hamiltonian) - @pytest.mark.usefixtures("use_legacy_and_new_opmath") def test_max_clique_grouping(self): """Tests that the grouping information is set and correct""" @@ -1105,21 +1066,15 @@ def test_max_clique_grouping(self): ("graph", "constrained", "cost_hamiltonian", "mixer_hamiltonian", "mapping"), make_max_weighted_cycle_test_cases(), ) - @pytest.mark.usefixtures("use_legacy_and_new_opmath") def test_max_weight_cycle_output( self, graph, constrained, cost_hamiltonian, mixer_hamiltonian, mapping ): """Tests that the output of the maximum weighted cycle method is correct""" - - if not qml.operation.active_new_opmath(): - cost_hamiltonian = qml.operation.convert_to_legacy_H(cost_hamiltonian) - mixer_hamiltonian = qml.operation.convert_to_legacy_H(mixer_hamiltonian) 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 mapping == m - @pytest.mark.usefixtures("use_legacy_and_new_opmath") def test_max_weight_cycle_grouping(self): """Tests that the grouping information is set and correct""" @@ -1139,7 +1094,7 @@ class TestUtils: """Tests that the utility functions are working properly""" # pylint: disable=protected-access - @pytest.mark.usefixtures("legacy_opmath_only") + # removed a fixture to only use legacy opmath here for now, because I'm not sure why its relevant @pytest.mark.parametrize( ("hamiltonian", "value"), ( @@ -1269,7 +1224,6 @@ def test_cost_layer_output(self, cost, gates): class TestIntegration: """Test integration of the QAOA module with PennyLane""" - @pytest.mark.usefixtures("use_legacy_and_new_opmath") def test_module_example(self, tol): """Test the example in the QAOA module docstring""" @@ -1306,7 +1260,6 @@ def cost_function(params): assert np.allclose(res, expected, atol=tol, rtol=0) - @pytest.mark.usefixtures("use_legacy_and_new_opmath") def test_module_example_rx(self, tol): """Test the example in the QAOA module docstring""" @@ -1424,7 +1377,6 @@ def test_wires_to_edges_rx(self): "g", [nx.complete_graph(4).to_directed(), rx.generators.directed_mesh_graph(4, [0, 1, 2, 3])], ) - @pytest.mark.usefixtures("use_legacy_and_new_opmath") def test_partial_cycle_mixer_complete(self, g): """Test if the _partial_cycle_mixer function returns the expected Hamiltonian for a fixed example""" @@ -1486,7 +1438,6 @@ def test_partial_cycle_mixer_error(self, g): "g", [nx.complete_graph(3).to_directed(), rx.generators.directed_mesh_graph(3, [0, 1, 2])], ) - @pytest.mark.usefixtures("use_legacy_and_new_opmath") def test_cycle_mixer(self, g): """Test if the cycle_mixer Hamiltonian maps valid cycles to valid cycles""" @@ -1561,7 +1512,6 @@ def test_cycle_mixer_error(self, g): cycle_mixer(g) @pytest.mark.parametrize("g", [nx.lollipop_graph(3, 1), lollipop_graph_rx(3, 1)]) - @pytest.mark.usefixtures("use_legacy_and_new_opmath") def test_matrix(self, g): """Test that the matrix function works as expected on a fixed example""" h = qml.qaoa.bit_flip_mixer(g, 0) @@ -1590,7 +1540,6 @@ def test_matrix(self, g): assert np.allclose(mat.toarray(), mat_expected) - @pytest.mark.usefixtures("use_legacy_and_new_opmath") def test_matrix_rx(self): """Test that the matrix function works as expected on a fixed example""" g = rx.generators.star_graph(4, [0, 1, 2, 3]) @@ -1671,7 +1620,6 @@ def test_wires_to_edges_directed(self, g): @pytest.mark.parametrize( "g", [nx.complete_graph(3).to_directed(), rx.generators.directed_mesh_graph(3, [0, 1, 2])] ) - @pytest.mark.usefixtures("use_legacy_and_new_opmath") def test_loss_hamiltonian_complete(self, g): """Test if the loss_hamiltonian function returns the expected result on a manually-calculated example of a 3-node complete digraph""" @@ -1710,7 +1658,6 @@ def test_loss_hamiltonian_error(self): @pytest.mark.parametrize( "g", [nx.lollipop_graph(4, 1).to_directed(), lollipop_graph_rx(4, 1, to_directed=True)] ) - @pytest.mark.usefixtures("use_legacy_and_new_opmath") def test_loss_hamiltonian_incomplete(self, g): """Test if the loss_hamiltonian function returns the expected result on a manually-calculated example of a 4-node incomplete digraph""" @@ -1805,7 +1752,6 @@ def test_missing_edge_weight_data_without_weights(self): with pytest.raises(TypeError, match="does not contain weight data"): loss_hamiltonian(g) - @pytest.mark.usefixtures("use_legacy_and_new_opmath") def test_square_hamiltonian_terms(self): """Test if the _square_hamiltonian_terms function returns the expected result on a fixed example""" @@ -1906,31 +1852,6 @@ def test_inner_net_flow_constraint_hamiltonian(self, g): for op, expected_op in zip(non_zero_ops, expected_ops): assert op.pauli_rep == expected_op.pauli_rep - @pytest.mark.parametrize( - "g", [nx.complete_graph(3).to_directed(), rx.generators.directed_mesh_graph(3, [0, 1, 2])] - ) - @pytest.mark.usefixtures("legacy_opmath_only") - def test_inner_net_flow_constraint_hamiltonian_legacy_opmath(self, g): - """Test if the _inner_net_flow_constraint_hamiltonian function returns the expected result on a manually-calculated - example of a 3-node complete digraph relative to the 0 node""" - h = _inner_net_flow_constraint_hamiltonian(g, 0) - - expected_ops = [ - qml.Identity(0), - qml.PauliZ(0) @ qml.PauliZ(1), - qml.PauliZ(0) @ qml.PauliZ(2), - qml.PauliZ(0) @ qml.PauliZ(4), - qml.PauliZ(1) @ qml.PauliZ(2), - qml.PauliZ(1) @ qml.PauliZ(4), - qml.PauliZ(2) @ qml.PauliZ(4), - ] - expected_coeffs = [4, 2, -2, -2, -2, -2, 2] - - assert np.allclose(expected_coeffs, h.coeffs) - for i, expected_op in enumerate(expected_ops): - assert str(h.ops[i]) == str(expected_op) - assert all(op.wires == exp.wires for op, exp in zip(h.ops, expected_ops)) - @pytest.mark.parametrize("g", [nx.complete_graph(3), rx.generators.mesh_graph(3, [0, 1, 2])]) def test_inner_net_flow_constraint_hamiltonian_error(self, g): """Test if the _inner_net_flow_constraint_hamiltonian function returns raises ValueError""" @@ -1940,7 +1861,6 @@ def test_inner_net_flow_constraint_hamiltonian_error(self, g): @pytest.mark.parametrize( "g", [nx.complete_graph(3).to_directed(), rx.generators.directed_mesh_graph(3, [0, 1, 2])] ) - @pytest.mark.usefixtures("new_opmath_only") def test_inner_out_flow_constraint_hamiltonian_non_complete(self, g): """Test if the _inner_out_flow_constraint_hamiltonian function returns the expected result on a manually-calculated example of a 3-node complete digraph relative to the 0 node, with @@ -1956,25 +1876,6 @@ def test_inner_out_flow_constraint_hamiltonian_non_complete(self, g): for op, expected_op in zip(ops, expected_ops): assert op.pauli_rep == expected_op.pauli_rep - @pytest.mark.parametrize( - "g", [nx.complete_graph(3).to_directed(), rx.generators.directed_mesh_graph(3, [0, 1, 2])] - ) - @pytest.mark.usefixtures("legacy_opmath_only") - def test_inner_out_flow_constraint_hamiltonian_non_complete_legacy_opmath(self, g): - """Test if the _inner_out_flow_constraint_hamiltonian function returns the expected result - on a manually-calculated example of a 3-node complete digraph relative to the 0 node, with - the (0, 1) edge removed""" - g.remove_edge(0, 1) - h = _inner_out_flow_constraint_hamiltonian(g, 0) - - expected_ops = [qml.PauliZ(wires=[0])] - expected_coeffs = [0] - - assert np.allclose(expected_coeffs, h.coeffs) - for i, expected_op in enumerate(expected_ops): - assert str(h.ops[i]) == str(expected_op) - assert all(op.wires == exp.wires for op, exp in zip(h.ops, expected_ops)) - @pytest.mark.parametrize( "g", [nx.complete_graph(3).to_directed(), rx.generators.directed_mesh_graph(3, [0, 1, 2])] ) @@ -1999,32 +1900,6 @@ def test_inner_net_flow_constraint_hamiltonian_non_complete(self, g): for op, expected_op in zip(ops, expected_ops): assert op.pauli_rep == expected_op.pauli_rep - @pytest.mark.parametrize( - "g", [nx.complete_graph(3).to_directed(), rx.generators.directed_mesh_graph(3, [0, 1, 2])] - ) - @pytest.mark.usefixtures("legacy_opmath_only") - def test_inner_net_flow_constraint_hamiltonian_non_complete_legacy_opmath(self, g): - """Test if the _inner_net_flow_constraint_hamiltonian function returns the expected result on a manually-calculated - example of a 3-node complete digraph relative to the 0 node, with the (1, 0) edge removed""" - g.remove_edge(1, 0) - h = _inner_net_flow_constraint_hamiltonian(g, 0) - - expected_ops = [ - qml.Identity(0), - qml.PauliZ(0), - qml.PauliZ(1), - qml.PauliZ(3), - qml.PauliZ(0) @ qml.PauliZ(1), - qml.PauliZ(0) @ qml.PauliZ(3), - qml.PauliZ(1) @ qml.PauliZ(3), - ] - expected_coeffs = [4, -2, -2, 2, 2, -2, -2] - - assert np.allclose(expected_coeffs, h.coeffs) - for i, expected_op in enumerate(expected_ops): - assert str(h.ops[i]) == str(expected_op) - assert all(op.wires == exp.wires for op, exp in zip(h.ops, expected_ops)) - def test_out_flow_constraint_raises(self): """Test the out-flow constraint function may raise an error.""" diff --git a/tests/test_qnode_legacy.py b/tests/test_qnode_legacy.py index f620f51b9e6..4c953cbb810 100644 --- a/tests/test_qnode_legacy.py +++ b/tests/test_qnode_legacy.py @@ -1595,7 +1595,6 @@ def circuit(): assert len(tapes) == 2 - @pytest.mark.usefixtures("new_opmath_only") @pytest.mark.parametrize("grouping", [True, False]) def test_multiple_hamiltonian_expansion_finite_shots(self, grouping): """Test that multiple Hamiltonians works correctly (sum_expand should be used)""" diff --git a/tests/test_queuing.py b/tests/test_queuing.py index 111c5846df7..f9a953e174d 100644 --- a/tests/test_queuing.py +++ b/tests/test_queuing.py @@ -197,31 +197,6 @@ def test_append_qubit_observables(self): ] assert q.queue == ops - @pytest.mark.usefixtures("legacy_opmath_only") - def test_append_tensor_ops(self): - """Test that ops which are used as inputs to `Tensor` - are successfully added to the queue, as well as the `Tensor` object.""" - - with AnnotatedQueue() as q: - A = qml.PauliZ(0) - B = qml.PauliY(1) - tensor_op = qml.operation.Tensor(A, B) - assert q.queue == [tensor_op] - assert tensor_op.obs == [A, B] - - @pytest.mark.usefixtures("legacy_opmath_only") - def test_append_tensor_ops_overloaded(self): - """Test that Tensor ops created using `@` - are successfully added to the queue, as well as the `Tensor` object.""" - - with AnnotatedQueue() as q: - A = qml.PauliZ(0) - B = qml.PauliY(1) - tensor_op = A @ B - assert q.queue == [tensor_op] - assert tensor_op.obs == [A, B] - - @pytest.mark.usefixtures("new_opmath_only") def test_append_prod_ops_overloaded(self): """Test that Prod ops created using `@` are successfully added to the queue, as well as the `Prod` object.""" @@ -293,16 +268,6 @@ def test_update_info_not_in_queue(self): q.update_info(B, inv=True) assert len(q.queue) == 1 - def test_append_annotating_object(self): - """Test appending an object that writes annotations when queuing itself""" - - with AnnotatedQueue() as q: - A = qml.PauliZ(0) - B = qml.PauliY(1) - tensor_op = qml.operation.Tensor(A, B) - - assert q.queue == [tensor_op] - def test_parallel_queues_are_isolated(self): """Tests that parallel queues do not queue each other's constituents.""" q1 = AnnotatedQueue() @@ -325,8 +290,6 @@ def queue_pauli(arg): test_observables = [ qml.PauliZ(0) @ qml.PauliZ(1), - qml.operation.Tensor(qml.PauliZ(0), qml.PauliX(1)), - qml.operation.Tensor(qml.PauliZ(0), qml.PauliX(1)) @ qml.Hadamard(2), qml.Hamiltonian( [0.1, 0.2, 0.3], [qml.PauliZ(0) @ qml.PauliZ(1), qml.PauliY(1), qml.Identity(2)] ), diff --git a/tests/test_vqe.py b/tests/test_vqe.py index 60b16bd2fc1..8ef5b27497b 100644 --- a/tests/test_vqe.py +++ b/tests/test_vqe.py @@ -229,22 +229,6 @@ def amp_embed_and_strong_ent_layer(params, wires=None): add_queue = zip(QUEUE_HAMILTONIANS_1, QUEUE_HAMILTONIANS_2, QUEUES) -##################################################### -# Helper functions - - -def _convert_obs_to_legacy_opmath(obs): - """Convert single-term observables to legacy opmath""" - - if isinstance(obs, qml.ops.Prod): - return qml.operation.Tensor(*obs.operands) - - if isinstance(obs, (list, tuple)): - return [_convert_obs_to_legacy_opmath(o) for o in obs] - - return obs - - ##################################################### # Tests @@ -256,8 +240,6 @@ class TestVQE: @pytest.mark.parametrize("coeffs, observables", list(zip(COEFFS, OBSERVABLES))) def test_cost_evaluate(self, params, ansatz, coeffs, observables): """Tests that the cost function evaluates properly""" - if not qml.operation.active_new_opmath(): - observables = _convert_obs_to_legacy_opmath(observables) hamiltonian = qml.Hamiltonian(coeffs, observables) dev = qml.device("default.qubit", wires=3) expval = generate_cost_fn(ansatz, hamiltonian, dev) @@ -269,8 +251,6 @@ def test_cost_evaluate(self, params, ansatz, coeffs, observables): ) def test_cost_expvals(self, coeffs, observables, expected): """Tests that the cost function returns correct expectation values""" - if not qml.operation.active_new_opmath() and (not coeffs or all(c == 0 for c in coeffs)): - pytest.skip("Legacy opmath does not support zero Hamiltonians") dev = qml.device("default.qubit", wires=2) hamiltonian = qml.Hamiltonian(coeffs, observables) cost = generate_cost_fn(lambda params, **kwargs: None, hamiltonian, dev) @@ -732,9 +712,6 @@ class TestNewVQE: def test_circuits_evaluate(self, ansatz, observables, params, tol): """Tests simple VQE evaluations.""" - if not qml.operation.active_new_opmath(): - observables = _convert_obs_to_legacy_opmath(observables) - coeffs = [1.0] * len(observables) dev = qml.device("default.qubit", wires=3) H = qml.Hamiltonian(coeffs, observables) @@ -883,40 +860,6 @@ def circuit1(): assert res[0] == circuit1() assert res[1] == circuit1() - # the LinearCombination implementation does have diagonalizing gates, - # but legacy Hamiltonian does not and fails - @pytest.mark.usefixtures("legacy_opmath_only") - def test_error_var_measurement(self): - """Tests that error is thrown if var(H) is measured.""" - observables = [qml.PauliZ(0), qml.PauliY(0), qml.PauliZ(1)] - coeffs = [1.0] * len(observables) - dev = qml.device("default.qubit", wires=2) - H = qml.Hamiltonian(coeffs, observables) - - @qml.qnode(dev) - def circuit(): - return qml.var(H) - - with pytest.raises(NotImplementedError): - circuit() - - # the LinearCombination implementation does have diagonalizing gates, - # but legacy Hamiltonian does not and fails - @pytest.mark.usefixtures("legacy_opmath_only") - def test_error_sample_measurement(self): - """Tests that error is thrown if sample(H) is measured.""" - observables = [qml.PauliZ(0), qml.PauliY(0), qml.PauliZ(1)] - coeffs = [1.0] * len(observables) - dev = qml.device("default.qubit", wires=2, shots=10) - H = qml.Hamiltonian(coeffs, observables) - - @qml.qnode(dev) - def circuit(): - return qml.sample(H) - - with pytest.raises(qml.operation.DiagGatesUndefinedError): - circuit() - @pytest.mark.autograd @pytest.mark.parametrize("diff_method", ["parameter-shift", "best"]) def test_grad_autograd(self, diff_method, tol): @@ -1015,7 +958,6 @@ def circuit(w): @pytest.mark.xfail( reason="diagonalizing gates defined but not used, should not be included in specs" ) - @pytest.mark.usefixtures("new_opmath_only") def test_specs(self): """Test that the specs of a VQE circuit can be computed""" dev = qml.device("default.qubit", wires=2) @@ -1036,23 +978,6 @@ def circuit(): # to be revisited in [sc-59117] assert res["num_diagonalizing_gates"] == 0 - @pytest.mark.usefixtures("legacy_opmath_only") - def test_specs_legacy_opmath(self): - """Test that the specs of a VQE circuit can be computed""" - dev = qml.device("default.qubit", wires=2) - H = qml.Hamiltonian([0.1, 0.2], [qml.PauliZ(0), qml.PauliZ(0) @ qml.PauliX(1)]) - - @qml.qnode(dev) - def circuit(): - qml.Hadamard(wires=0) - qml.CNOT(wires=[0, 1]) - return qml.expval(H) - - res = qml.specs(circuit)() - - assert res["num_observables"] == 1 - assert res["num_diagonalizing_gates"] == 0 - class TestInterfaces: """Tests for VQE with interfaces.""" diff --git a/tests/transforms/test_add_noise.py b/tests/transforms/test_add_noise.py index abd8d5f665c..082f8ce6b1b 100644 --- a/tests/transforms/test_add_noise.py +++ b/tests/transforms/test_add_noise.py @@ -106,11 +106,8 @@ def test_noise_tape(self): for o1, o2 in zip(tape.operations, tape_exp.operations) ) assert len(tape.measurements) == 1 - assert ( - tape.observables[0].name == "Prod" - if qml.operation.active_new_opmath() - else ["PauliZ", "PauliZ"] - ) + assert tape.observables[0].name == "Prod" + assert tape.observables[0].wires.tolist() == [0, 1] assert tape.measurements[0].return_type is Expectation @@ -142,11 +139,8 @@ def test_noise_tape_with_state_prep(self): for o1, o2 in zip(tape.operations, tape_exp.operations) ) assert len(tape.measurements) == 1 - assert ( - tape.observables[0].name == "Prod" - if qml.operation.active_new_opmath() - else ["PauliZ", "PauliZ"] - ) + assert tape.observables[0].name == "Prod" + assert tape.observables[0].wires.tolist() == [0, 1] assert tape.measurements[0].return_type is Expectation @@ -245,11 +239,8 @@ def test_add_noise_dev(self, dev_name): for o1, o2 in zip(tape.operations, tape_exp.operations) ) assert len(tape.measurements) == 2 - assert ( - tape.observables[0].name == "Prod" - if qml.operation.active_new_opmath() - else ["PauliZ", "PauliZ"] - ) + assert tape.observables[0].name == "Prod" + assert tape.observables[0].wires.tolist() == [0, 1] assert tape.measurements[0].return_type is Expectation assert tape.observables[1].name == "PauliZ" diff --git a/tests/transforms/test_convert_to_numpy_parameters.py b/tests/transforms/test_convert_to_numpy_parameters.py index d09f80c7eee..693f6b08b82 100644 --- a/tests/transforms/test_convert_to_numpy_parameters.py +++ b/tests/transforms/test_convert_to_numpy_parameters.py @@ -113,10 +113,10 @@ def test_unwraps_arithmetic_op_measurement(): @pytest.mark.autograd -def test_unwraps_tensor_observables(): - """Test that the measurement helper function can set data on a tensor observable.""" +def test_unwraps_prod_observables(): + """Test that the measurement helper function can set data on a prod observable.""" mat = qml.numpy.eye(2) - obs = qml.operation.Tensor(qml.PauliZ(0), qml.Hermitian(mat, 1)) + obs = qml.prod(qml.PauliZ(0), qml.Hermitian(mat, 1)) m = qml.expval(obs) unwrapped_m = _convert_measurement_to_numpy_data(m) diff --git a/tests/transforms/test_defer_measurements.py b/tests/transforms/test_defer_measurements.py index 91d01a46415..aee71070ead 100644 --- a/tests/transforms/test_defer_measurements.py +++ b/tests/transforms/test_defer_measurements.py @@ -630,7 +630,7 @@ def test_measure_with_tensor_obs(self, mid_measure_wire, tp_wires): with qml.queuing.AnnotatedQueue() as q: qml.measure(mid_measure_wire) - qml.expval(qml.operation.Tensor(*[qml.PauliZ(w) for w in tp_wires])) + qml.expval(qml.prod(*[qml.PauliZ(w) for w in tp_wires])) tape = qml.tape.QuantumScript.from_queue(q) tape, _ = qml.defer_measurements(tape) diff --git a/tests/transforms/test_diagonalize_measurements.py b/tests/transforms/test_diagonalize_measurements.py index 70122f5bb69..7cf9efc4349 100644 --- a/tests/transforms/test_diagonalize_measurements.py +++ b/tests/transforms/test_diagonalize_measurements.py @@ -97,11 +97,6 @@ def test_visited_obs_arg(self, obs, apply_gates): "compound_obs, expected_res, base_obs", [ (X(0) @ Y(2), Z(0) @ Z(2), [X(0), Y(2)]), # prod - ( - qml.operation.Tensor(X(0), Y(2)), - qml.operation.Tensor(Z(0), Z(2)), - [X(0), Y(2)], - ), # tensor (X(1) + Y(2), Z(1) + Z(2), [X(1), Y(2)]), # sum (2 * X(1), 2 * Z(1), [X(1)]), # sprod ( @@ -132,35 +127,10 @@ def test_compound_observables(self, compound_obs, expected_res, base_obs): assert visited_obs == (set(base_obs), {o.wires[0] for o in base_obs}) assert diagonalizing_gates == list(expected_diag_gates) - def test_legacy_hamiltonian(self): - """Test that _diagonalize_observable works on legacy Hamiltonians observables""" - - if qml.operation.active_new_opmath(): - with pytest.warns(): - compound_obs = qml.ops.Hamiltonian([2, 3], [Y(0), X(1)]) - expected_res = qml.ops.Hamiltonian([2, 3], [Z(0), Z(1)]) - diagonalizing_gates, new_obs, visited_obs = _diagonalize_observable(compound_obs) - else: - compound_obs = qml.ops.Hamiltonian([2, 3], [Y(0), X(1)]) - expected_res = qml.ops.Hamiltonian([2, 3], [Z(0), Z(1)]) - diagonalizing_gates, new_obs, visited_obs = _diagonalize_observable(compound_obs) - - base_obs = [Y(0), X(1)] - expected_diag_gates = np.concatenate([o.diagonalizing_gates() for o in base_obs]) - - assert new_obs == expected_res - assert visited_obs == (set(base_obs), {o.wires[0] for o in base_obs}) - assert diagonalizing_gates == list(expected_diag_gates) - @pytest.mark.parametrize( "compound_obs, expected_res, base_obs", [ (X(0) @ Y(2), X(0) @ Z(2), [X(0), Y(2)]), # prod - ( - qml.operation.Tensor(X(0), Y(2)), - qml.operation.Tensor(X(0), Z(2)), - [X(0), Y(2)], - ), # tensor (X(1) + Y(2), X(1) + Z(2), [X(1), Y(2)]), # sum (2 * X(1), 2 * X(1), [X(1)]), # sprod ( @@ -327,7 +297,6 @@ def test_diagonalize_all_measurements(self, to_eigvals): assert fn == null_postprocessing - @pytest.mark.usefixtures("new_opmath_only") @pytest.mark.parametrize( "obs, expected_obs, diag_gates", [ @@ -370,47 +339,6 @@ def test_diagonalize_all_measurements_composite_obs( assert fn == null_postprocessing - @pytest.mark.usefixtures("legacy_opmath_only") - def test_diagonalize_all_measurements_hamiltonian(self): - """Test that the diagonalize_measurements transform diagonalizes a Hamiltonian with a pauli_rep - when diagonalizing all measurements""" - obs = qml.ops.Hamiltonian([1, 2], [X(1), Y(2)]) - expected_obs = qml.ops.Hamiltonian([1, 2], [Z(1), Z(2)]) - - assert obs.pauli_rep is not None - - measurements = [qml.expval(obs)] - - tape = QuantumScript([], measurements=measurements) - tapes, fn = diagonalize_measurements(tape) - new_tape = tapes[0] - - assert new_tape.measurements == [qml.expval(expected_obs)] - assert new_tape.operations == diagonalize_qwc_pauli_words(obs.ops)[0] - - assert fn == null_postprocessing - - @pytest.mark.usefixtures("legacy_opmath_only") - def test_diagonalize_all_measurements_tensor(self): - """Test that the diagonalize_measurements transform diagonalizes a Tensor with a pauli_rep - when diagonalizing all measurements""" - - obs = qml.operation.Tensor(X(1), Y(2)) - expected_obs = qml.operation.Tensor(Z(1), Z(2)) - - assert obs.pauli_rep is not None - - measurements = [qml.expval(obs)] - - tape = QuantumScript([], measurements=measurements) - tapes, fn = diagonalize_measurements(tape) - new_tape = tapes[0] - - assert new_tape.measurements == [qml.expval(expected_obs)] - assert new_tape.operations == diagonalize_qwc_pauli_words(obs.obs)[0] - - assert fn == null_postprocessing - def test_diagonalize_subset_of_measurements(self): """Test that the diagonalize_measurements transform diagonalizes the measurements on the tape when diagonalizing a subset of the measurements""" @@ -621,7 +549,6 @@ def test_bad_to_eigvals_input_raises_error(self, supported_base_obs): QuantumScript([]), supported_base_obs=supported_base_obs, to_eigvals=True ) - @pytest.mark.usefixtures("new_opmath_only") @pytest.mark.parametrize("to_eigvals", [True, False]) @pytest.mark.parametrize("supported_base_obs", ([qml.Z], [qml.Z, qml.X], [qml.Z, qml.X, qml.Y])) @pytest.mark.parametrize("shots", [None, 2000, (4000, 5000, 6000)]) diff --git a/tests/transforms/test_insert_ops.py b/tests/transforms/test_insert_ops.py index 9a21d7268ec..bf03d9b7c5c 100644 --- a/tests/transforms/test_insert_ops.py +++ b/tests/transforms/test_insert_ops.py @@ -92,11 +92,7 @@ def test_start(self): for o1, o2 in zip(tape.operations, tape_exp.operations) ) assert len(tape.measurements) == 1 - assert ( - tape.observables[0].name == "Prod" - if qml.operation.active_new_opmath() - else ["PauliZ", "PauliZ"] - ) + assert tape.observables[0].name == "Prod" assert tape.observables[0].wires.tolist() == [0, 1] assert tape.measurements[0].return_type is Expectation @@ -127,11 +123,7 @@ def test_all(self): for o1, o2 in zip(tape.operations, tape_exp.operations) ) assert len(tape.measurements) == 1 - assert ( - tape.observables[0].name == "Prod" - if qml.operation.active_new_opmath() - else ["PauliZ", "PauliZ"] - ) + assert tape.observables[0].name == "Prod" assert tape.observables[0].wires.tolist() == [0, 1] assert tape.measurements[0].return_type is Expectation @@ -161,11 +153,7 @@ def test_before(self): for o1, o2 in zip(tape.operations, tape_exp.operations) ) assert len(tape.measurements) == 1 - assert ( - tape.observables[0].name == "Prod" - if qml.operation.active_new_opmath() - else ["PauliZ", "PauliZ"] - ) + assert tape.observables[0].name == "Prod" assert tape.observables[0].wires.tolist() == [0, 1] assert tape.measurements[0].return_type is Expectation @@ -204,11 +192,7 @@ def test_operation_as_position(self, op): for o1, o2 in zip(tape.operations, tape_exp.operations) ) assert len(tape.measurements) == 1 - assert ( - tape.observables[0].name == "Prod" - if qml.operation.active_new_opmath() - else ["PauliZ", "PauliZ"] - ) + assert tape.observables[0].name == "Prod" assert tape.observables[0].wires.tolist() == [0, 1] assert tape.measurements[0].return_type is Expectation @@ -237,11 +221,7 @@ def test_operation_list_as_position(self): for o1, o2 in zip(tape.operations, tape_exp.operations) ) assert len(tape.measurements) == 1 - assert ( - tape.observables[0].name == "Prod" - if qml.operation.active_new_opmath() - else ["PauliZ", "PauliZ"] - ) + assert tape.observables[0].name == "Prod" assert tape.observables[0].wires.tolist() == [0, 1] assert tape.measurements[0].return_type is Expectation @@ -268,11 +248,7 @@ def test_end(self): for o1, o2 in zip(tape.operations, tape_exp.operations) ) assert len(tape.measurements) == 1 - assert ( - tape.observables[0].name == "Prod" - if qml.operation.active_new_opmath() - else ["PauliZ", "PauliZ"] - ) + assert tape.observables[0].name == "Prod" assert tape.observables[0].wires.tolist() == [0, 1] assert tape.measurements[0].return_type is Expectation @@ -301,11 +277,7 @@ def test_start_with_state_prep(self): for o1, o2 in zip(tape.operations, tape_exp.operations) ) assert len(tape.measurements) == 1 - assert ( - tape.observables[0].name == "Prod" - if qml.operation.active_new_opmath() - else ["PauliZ", "PauliZ"] - ) + assert tape.observables[0].name == "Prod" assert tape.observables[0].wires.tolist() == [0, 1] assert tape.measurements[0].return_type is Expectation @@ -338,11 +310,7 @@ def test_all_with_state_prep(self): for o1, o2 in zip(tape.operations, tape_exp.operations) ) assert len(tape.measurements) == 1 - assert ( - tape.observables[0].name == "Prod" - if qml.operation.active_new_opmath() - else ["PauliZ", "PauliZ"] - ) + assert tape.observables[0].name == "Prod" assert tape.observables[0].wires.tolist() == [0, 1] assert tape.measurements[0].return_type is Expectation @@ -373,11 +341,7 @@ def test_end_with_state_prep(self): for o1, o2 in zip(tape.operations, tape_exp.operations) ) assert len(tape.measurements) == 1 - assert ( - tape.observables[0].name == "Prod" - if qml.operation.active_new_opmath() - else ["PauliZ", "PauliZ"] - ) + assert tape.observables[0].name == "Prod" assert tape.observables[0].wires.tolist() == [0, 1] assert tape.measurements[0].return_type is Expectation @@ -412,11 +376,7 @@ def op(x, y, wires): for o1, o2 in zip(tape.operations, tape_exp.operations) ) assert len(tape.measurements) == 1 - assert ( - tape.observables[0].name == "Prod" - if qml.operation.active_new_opmath() - else ["PauliZ", "PauliZ"] - ) + assert tape.observables[0].name == "Prod" assert tape.observables[0].wires.tolist() == [0, 1] assert tape.measurements[0].return_type is Expectation @@ -500,11 +460,7 @@ def test_insert_dev(dev_name): for o1, o2 in zip(tape.operations, tape_exp.operations) ) assert len(tape.measurements) == 2 - assert ( - tape.observables[0].name == "Prod" - if qml.operation.active_new_opmath() - else ["PauliZ", "PauliZ"] - ) + assert tape.observables[0].name == "Prod" assert tape.observables[0].wires.tolist() == [0, 1] assert tape.measurements[0].return_type is Expectation assert tape.observables[1].name == "PauliZ" diff --git a/tests/transforms/test_qcut.py b/tests/transforms/test_qcut.py index 82d2fefb4cd..1574f9aa45c 100644 --- a/tests/transforms/test_qcut.py +++ b/tests/transforms/test_qcut.py @@ -1585,15 +1585,8 @@ def test_single_measurement(self): assert obs[0].wires.tolist() == [1, 0, 2] assert obs[1].wires.tolist() == [1, 0] - if qml.operation.active_new_opmath(): - - assert [get_name(o) for o in obs[0].terms()[1]] == ["Prod"] - assert [get_name(o) for o in obs[1].terms()[1]] == ["Prod"] - - else: - - assert [get_name(o) for o in obs[0].obs] == ["PauliZ", "PauliX", "PauliZ"] - assert [get_name(o) for o in obs[1].obs] == ["PauliZ", "PauliX"] + assert [get_name(o) for o in obs[0].terms()[1]] == ["Prod"] + assert [get_name(o) for o in obs[1].terms()[1]] == ["Prod"] class TestExpandFragmentTapes: diff --git a/tests/transforms/test_split_non_commuting.py b/tests/transforms/test_split_non_commuting.py index 37931f70ea3..7dd60342224 100644 --- a/tests/transforms/test_split_non_commuting.py +++ b/tests/transforms/test_split_non_commuting.py @@ -70,18 +70,6 @@ ] -def _convert_obs_to_legacy_opmath(obs): - """Convert single-term observables to legacy opmath""" - - if isinstance(obs, qml.ops.Prod): - return qml.operation.Tensor(*obs.operands) - - if isinstance(obs, list): - return [_convert_obs_to_legacy_opmath(o) for o in obs] - - return obs - - def complex_no_grouping_processing_fn(results): """The expected processing function without grouping of complex_obs_list""" @@ -172,8 +160,6 @@ def test_number_of_tapes_single_hamiltonian(self, grouping_strategy, n_tapes, ma """Tests that the correct number of tapes is returned for a single Hamiltonian""" obs_list = single_term_obs_list - if not qml.operation.active_new_opmath(): - obs_list = _convert_obs_to_legacy_opmath(obs_list) obs_list = obs_list + [qml.Y(0), qml.X(0) @ qml.Y(1)] # add duplicate terms coeffs = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7] @@ -224,8 +210,6 @@ def test_existing_grouping_used_for_single_hamiltonian(self, grouping_strategy, what is requested through the ``grouping_strategy`` argument.""" obs_list = single_term_obs_list - if not qml.operation.active_new_opmath(): - obs_list = _convert_obs_to_legacy_opmath(obs_list) H = make_H(obs_list) H.compute_grouping() @@ -354,19 +338,10 @@ def test_grouping_strategies(self): assert qml.math.allclose(fn([0.1, 0.2, 0.3, 0.4, 0.5]), [0.01, 0.04, 0.09, 0.16, 0.25]) tapes, fn = split_non_commuting(tape, grouping_strategy="default") - # When new opmath is disabled, c * o gives Hamiltonians, which leads to wires grouping - if qml.operation.active_new_opmath(): - for actual_tape, expected_tape in zip(tapes, expected_tapes_qwc_grouping): - qml.assert_equal(actual_tape, expected_tape) - assert qml.math.allclose( - fn([[0.1, 0.2], [0.3, 0.4, 0.5]]), [0.01, 0.06, 0.12, 0.08, 0.25] - ) - else: - for actual_tape, expected_tape in zip(tapes, expected_tapes_wires_grouping): - qml.assert_equal(actual_tape, expected_tape) - assert qml.math.allclose( - fn([[0.1, 0.2], 0.3, 0.4, 0.5]), [0.01, 0.06, 0.06, 0.16, 0.25] - ) + + for actual_tape, expected_tape in zip(tapes, expected_tapes_qwc_grouping): + qml.assert_equal(actual_tape, expected_tape) + assert qml.math.allclose(fn([[0.1, 0.2], [0.3, 0.4, 0.5]]), [0.01, 0.06, 0.12, 0.08, 0.25]) tapes, fn = split_non_commuting(tape, grouping_strategy="qwc") for actual_tape, expected_tape in zip(tapes, expected_tapes_qwc_grouping): @@ -388,12 +363,9 @@ def test_grouping_strategies(self): def test_grouping_strategies_single_hamiltonian(self, make_H): """Tests that a single Hamiltonian or Sum is split correctly""" - coeffs, obs_list = [0.1, 0.2, 0.3, 0.4, 0.5], single_term_obs_list - qwc_groups = single_term_qwc_groups - - if not qml.operation.active_new_opmath(): - obs_list = _convert_obs_to_legacy_opmath(obs_list) - qwc_groups = _convert_obs_to_legacy_opmath(single_term_qwc_groups) + coeffs = [0.1, 0.2, 0.3, 0.4, 0.5] + obs_list = single_term_obs_list + H = make_H(coeffs, obs_list) # Tests that constant offset is handled expected_tapes_no_grouping = [ qml.tape.QuantumScript([], [qml.expval(o)], shots=100) for o in obs_list @@ -401,32 +373,24 @@ def test_grouping_strategies_single_hamiltonian(self, make_H): expected_tapes_qwc_grouping = [ qml.tape.QuantumScript([], [qml.expval(o) for o in group], shots=100) - for group in qwc_groups + for group in single_term_qwc_groups ] - if qml.operation.active_new_opmath(): - coeffs, obs_list = coeffs + [0.6], obs_list + [qml.I()] - + coeffs, obs_list = coeffs + [0.6], obs_list + [qml.I()] H = make_H(coeffs, obs_list) # Tests that constant offset is handled - if not qml.operation.active_new_opmath() and isinstance(H, qml.ops.Sum): - pytest.skip("Sum is not part of legacy opmath") - tape = qml.tape.QuantumScript([], [qml.expval(H)], shots=100) tapes, fn = split_non_commuting(tape, grouping_strategy=None) for actual_tape, expected_tape in zip(tapes, expected_tapes_no_grouping): qml.assert_equal(actual_tape, expected_tape) - expected = 0.55 if not qml.operation.active_new_opmath() else 1.15 - assert qml.math.allclose(fn([0.1, 0.2, 0.3, 0.4, 0.5]), expected) + assert qml.math.allclose(fn([0.1, 0.2, 0.3, 0.4, 0.5]), 1.15) tapes, fn = split_non_commuting(tape, grouping_strategy="default") for actual_tape, expected_tape in zip(tapes, expected_tapes_qwc_grouping): qml.assert_equal(actual_tape, expected_tape) - expected = 0.52 if not qml.operation.active_new_opmath() else 1.12 - assert qml.math.allclose(fn([[0.1, 0.2], [0.3, 0.4, 0.5]]), expected) + assert qml.math.allclose(fn([[0.1, 0.2], [0.3, 0.4, 0.5]]), 1.12) - @pytest.mark.usefixtures("new_opmath_only") @pytest.mark.parametrize( "H", [ @@ -446,20 +410,6 @@ def test_single_hamiltonian_non_pauli_words(self, H): for actual_tape, expected_tape in zip(tapes, expected_tapes): qml.assert_equal(actual_tape, expected_tape) - @pytest.mark.usefixtures("legacy_opmath_only") - def test_single_hamiltonian_non_pauli_words_legacy(self): - """Tests that a single Hamiltonian with non-pauli words is split correctly""" - - H = qml.Hamiltonian([1, 2, 3], [qml.X(0), qml.Hadamard(1) @ qml.Z(0), qml.Y(1)]) - tape = qml.tape.QuantumScript([], [qml.expval(H)], shots=100) - tapes, _ = split_non_commuting(tape) - expected_tapes = [ - qml.tape.QuantumScript([], [qml.expval(qml.X(0)), qml.expval(qml.Y(1))], shots=100), - qml.tape.QuantumScript([], [qml.expval(qml.Hadamard(1) @ qml.Z(0))], shots=100), - ] - for actual_tape, expected_tape in zip(tapes, expected_tapes): - qml.assert_equal(actual_tape, expected_tape) - @pytest.mark.parametrize( "grouping_strategy, expected_tapes, processing_fn, mock_results", [ @@ -498,8 +448,6 @@ def test_grouping_strategies_complex( """Tests that the tape is split correctly when containing more complex observables""" obs_list = complex_obs_list - if not qml.operation.active_new_opmath(): - obs_list = obs_list[:-1] # exclude the identity term measurements = [qml.expval(o) for o in obs_list] tape = qml.tape.QuantumScript([], measurements, shots=100) @@ -509,8 +457,6 @@ def test_grouping_strategies_complex( qml.assert_equal(actual_tape, expected_tape) expected = processing_fn(mock_results) - if not qml.operation.active_new_opmath(): - expected = expected[:-1] # exclude the identity term assert qml.math.allclose(fn(mock_results), expected) @@ -561,10 +507,6 @@ def test_tape_with_non_pauli_obs(self, non_pauli_obs): obs_list = single_term_obs_list + non_pauli_obs - if not qml.operation.active_new_opmath(): - non_pauli_obs = _convert_obs_to_legacy_opmath(non_pauli_obs) - obs_list = _convert_obs_to_legacy_opmath(obs_list) - measurements = [ qml.expval(c * o) for c, o in zip([0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7], obs_list) ] @@ -665,12 +607,8 @@ def test_single_expval(self, grouping_strategy, shots, params, expected_results) coeffs, obs = [0.1, 0.2, 0.3, 0.4, 0.5], single_term_obs_list - if not qml.operation.active_new_opmath(): - obs = _convert_obs_to_legacy_opmath(obs) - - if qml.operation.active_new_opmath(): - # test constant offset with new opmath - coeffs, obs = coeffs + [0.6], obs + [qml.I()] + # test constant offset + coeffs, obs = coeffs + [0.6], obs + [qml.I()] dev = qml.device("default.qubit", wires=2, shots=shots) @@ -685,9 +623,8 @@ def circuit(angles): circuit = split_non_commuting(circuit, grouping_strategy=grouping_strategy) res = circuit(params) - if qml.operation.active_new_opmath(): - identity_results = [1] if len(np.shape(params)) == 1 else [[1, 1]] - expected_results = expected_results + identity_results + identity_results = [1] if len(np.shape(params)) == 1 else [[1, 1]] + expected_results = expected_results + identity_results expected = np.dot(coeffs, expected_results) @@ -746,8 +683,6 @@ def test_multiple_expval(self, grouping_strategy, shots, params, expected_result dev = qml.device("default.qubit", wires=2, shots=shots, seed=seed) obs_list = complex_obs_list - if not qml.operation.active_new_opmath(): - obs_list = obs_list[:-1] # exclude the identity term @qml.qnode(dev) def circuit(angles): @@ -760,9 +695,6 @@ def circuit(angles): circuit = split_non_commuting(circuit, grouping_strategy=grouping_strategy) res = circuit(params) - if not qml.operation.active_new_opmath(): - expected_results = expected_results[:-1] # exclude the identity term - if isinstance(shots, list): assert qml.math.shape(res) == (3, *np.shape(expected_results)) for i in range(3): @@ -820,8 +752,6 @@ def test_mixed_measurement_types( dev = qml.device("default.qubit", wires=2, shots=shots, seed=seed) obs_list = complex_obs_list - if not qml.operation.active_new_opmath(): - obs_list = obs_list[:-1] # exclude the identity term @qml.qnode(dev) def circuit(angles): @@ -840,9 +770,6 @@ def circuit(angles): circuit = split_non_commuting(circuit, grouping_strategy=grouping_strategy) res = circuit(params) - if not qml.operation.active_new_opmath(): - expected_results = expected_results[:-1] # exclude the identity term - if isinstance(shots, list): assert len(res) == 3 for i in range(3): @@ -900,7 +827,6 @@ def circuit(): assert dev.tracker.totals == {} assert qml.math.allclose(res, 4.0) - @pytest.mark.usefixtures("new_opmath_only") @pytest.mark.parametrize("grouping_strategy", [None, "default", "qwc", "wires"]) def test_no_obs_tape(self, grouping_strategy): """Tests tapes with only constant offsets (only measurements on Identity)""" @@ -919,7 +845,6 @@ def circuit(): assert _dev.tracker.totals == {} assert qml.math.allclose(res, 1.5) - @pytest.mark.usefixtures("new_opmath_only") @pytest.mark.parametrize("grouping_strategy", [None, "default", "qwc", "wires"]) def test_no_obs_tape_multi_measurement(self, grouping_strategy): """Tests tapes with only constant offsets (only measurements on Identity)""" @@ -996,8 +921,6 @@ def test_autograd(self, grouping_strategy): dev = qml.device("default.qubit", wires=2) obs_list = complex_obs_list - if not qml.operation.active_new_opmath(): - obs_list = obs_list[:-1] # exclude the identity term @partial(split_non_commuting, grouping_strategy=grouping_strategy) @qml.qnode(dev) @@ -1017,9 +940,6 @@ def cost(theta, phi): expected_grad_1 = expected_grad_param_0 expected_grad_2 = expected_grad_param_1 - if not qml.operation.active_new_opmath(): - expected_grad_1 = expected_grad_param_0[:-1] - expected_grad_2 = expected_grad_param_1[:-1] assert qml.math.allclose(grad1, expected_grad_1) assert qml.math.allclose(grad2, expected_grad_2) @@ -1078,8 +998,6 @@ def test_jax(self, grouping_strategy, use_jit): dev = qml.device("default.qubit", wires=2) obs_list = complex_obs_list - if not qml.operation.active_new_opmath(): - obs_list = obs_list[:-1] # exclude the identity term @partial(split_non_commuting, grouping_strategy=grouping_strategy) @qml.qnode(dev) @@ -1102,9 +1020,6 @@ def cost(theta, phi): expected_grad_1 = expected_grad_param_0 expected_grad_2 = expected_grad_param_1 - if not qml.operation.active_new_opmath(): - expected_grad_1 = expected_grad_param_0[:-1] - expected_grad_2 = expected_grad_param_1[:-1] assert qml.math.allclose(grad1, expected_grad_1) assert qml.math.allclose(grad2, expected_grad_2) @@ -1146,8 +1061,6 @@ def test_torch(self, grouping_strategy): dev = qml.device("default.qubit", wires=2) obs_list = complex_obs_list - if not qml.operation.active_new_opmath(): - obs_list = obs_list[:-1] # exclude the identity term @partial(split_non_commuting, grouping_strategy=grouping_strategy) @qml.qnode(dev) @@ -1167,9 +1080,6 @@ def cost(theta, phi): expected_grad_1 = expected_grad_param_0 expected_grad_2 = expected_grad_param_1 - if not qml.operation.active_new_opmath(): - expected_grad_1 = expected_grad_param_0[:-1] - expected_grad_2 = expected_grad_param_1[:-1] assert qml.math.allclose(grad1, expected_grad_1, atol=1e-5) assert qml.math.allclose(grad2, expected_grad_2, atol=1e-5) @@ -1206,8 +1116,6 @@ def test_tensorflow(self, grouping_strategy): dev = qml.device("default.qubit", wires=2) obs_list = complex_obs_list - if not qml.operation.active_new_opmath(): - obs_list = obs_list[:-1] # exclude the identity term @qml.qnode(dev) def circuit(theta, phi): @@ -1227,9 +1135,6 @@ def circuit(theta, phi): expected_grad_1 = expected_grad_param_0 expected_grad_2 = expected_grad_param_1 - if not qml.operation.active_new_opmath(): - expected_grad_1 = expected_grad_param_0[:-1] - expected_grad_2 = expected_grad_param_1[:-1] assert qml.math.allclose(grad1, expected_grad_1, atol=1e-5) assert qml.math.allclose(grad2, expected_grad_2, atol=1e-5) diff --git a/tests/transforms/test_split_to_single_terms.py b/tests/transforms/test_split_to_single_terms.py index 9293b69167c..e30e64985cb 100644 --- a/tests/transforms/test_split_to_single_terms.py +++ b/tests/transforms/test_split_to_single_terms.py @@ -46,26 +46,14 @@ ] -def _convert_obs_to_legacy_opmath(obs): - """Convert single-term observables to legacy opmath""" - - if isinstance(obs, qml.ops.Prod): - return qml.operation.Tensor(*obs.operands) - - if isinstance(obs, list): - return [_convert_obs_to_legacy_opmath(o) for o in obs] - - return obs - - # pylint: disable=too-few-public-methods class NoTermsDevice(qml.devices.DefaultQubit): - """A device that builds on default.qubit, but won't accept Hamiltonian, LinearCombination and Sum""" + """A device that builds on default.qubit, but won't accept LinearCombination or Sum""" def execute(self, circuits, execution_config=qml.devices.DefaultExecutionConfig): for t in circuits: for mp in t.measurements: - if mp.obs and isinstance(mp.obs, (qml.ops.Hamiltonian, qml.ops.Sum)): + if mp.obs and isinstance(mp.obs, qml.ops.Sum): raise ValueError( "no terms device does not accept observables with multiple terms" ) @@ -248,18 +236,15 @@ def test_splitting_sums_in_unsupported_mps_raises_error(self, observable): class TestIntegration: """Tests the ``split_to_single_terms`` transform performed on a QNode. In these tests, - the supported observables of ``default_qubit`` are mocked to make the device reject Sum, - Hamiltonian and LinearCombination, to ensure the transform works as intended.""" + the supported observables of ``default_qubit`` are mocked to make the device reject Sum + and LinearCombination, to ensure the transform works as intended.""" def test_splitting_sums(self): """Test that the transform takes a tape that is not executable on a device that - doesn't support Sum/Hamiltonian, and turns it into one that is""" + doesn't support Sum, and turns it into one that is""" coeffs, obs = [0.1, 0.2, 0.3, 0.4, 0.5], single_term_obs_list - if not qml.operation.active_new_opmath(): - obs = _convert_obs_to_legacy_opmath(obs) - dev = NoTermsDevice(wires=2) @qml.qnode(dev) @@ -309,14 +294,8 @@ def circuit_split(): def test_single_expval(self, shots, params, expected_results): """Tests that a QNode with a single expval measurement is executed correctly""" - coeffs, obs = [0.1, 0.2, 0.3, 0.4, 0.5], single_term_obs_list - - if not qml.operation.active_new_opmath(): - obs = _convert_obs_to_legacy_opmath(obs) - - if qml.operation.active_new_opmath(): - # test constant offset with new opmath - coeffs, obs = coeffs + [0.6], obs + [qml.I()] + coeffs = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6] + obs = single_term_obs_list + [qml.I()] # test constant offset dev = NoTermsDevice(wires=2, shots=shots) @@ -331,9 +310,8 @@ def circuit(angles): circuit = split_to_single_terms(circuit) res = circuit(params) - if qml.operation.active_new_opmath(): - identity_results = [1] if len(np.shape(params)) == 1 else [[1, 1]] - expected_results = expected_results + identity_results + identity_results = [1] if len(np.shape(params)) == 1 else [[1, 1]] + expected_results = expected_results + identity_results expected = np.dot(coeffs, expected_results) @@ -391,8 +369,6 @@ def test_multiple_expval(self, shots, params, expected_results): dev = NoTermsDevice(wires=2, shots=shots) obs_list = complex_obs_list - if not qml.operation.active_new_opmath(): - obs_list = obs_list[:-1] # exclude the identity term @qml.qnode(dev) def circuit(angles): @@ -405,9 +381,6 @@ def circuit(angles): circuit = split_to_single_terms(circuit) res = circuit(params) - if not qml.operation.active_new_opmath(): - expected_results = expected_results[:-1] # exclude the identity term - if isinstance(shots, list): assert qml.math.shape(res) == (3, *np.shape(expected_results)) for i in range(3): @@ -462,8 +435,6 @@ def test_mixed_measurement_types(self, shots, params, expected_results): dev = NoTermsDevice(wires=2, shots=shots) obs_list = complex_obs_list - if not qml.operation.active_new_opmath(): - obs_list = obs_list[:-1] # exclude the identity term @qml.qnode(dev) def circuit(angles): @@ -482,9 +453,6 @@ def circuit(angles): circuit = split_to_single_terms(circuit) res = circuit(params) - if not qml.operation.active_new_opmath(): - expected_results = expected_results[:-1] # exclude the identity term - if isinstance(shots, list): assert len(res) == 3 for i in range(3): diff --git a/tests/transforms/test_transpile.py b/tests/transforms/test_transpile.py index b02ed2c15b6..fe2f6cfe695 100644 --- a/tests/transforms/test_transpile.py +++ b/tests/transforms/test_transpile.py @@ -78,24 +78,9 @@ def circuit(): # build circuit transpiled_qfunc = transpile(circuit, coupling_map=[(0, 1), (1, 2), (2, 3)]) transpiled_qnode = qml.QNode(transpiled_qfunc, dev) - err_msg = "Measuring expectation values of tensor products, Prods, or Hamiltonians is not yet supported" - with pytest.raises(NotImplementedError, match=err_msg): - transpiled_qnode() - - @pytest.mark.usefixtures("legacy_opmath_only") - def test_transpile_raise_not_implemented_tensorproduct_mmt(self): - """test that error is raised when measurement is expectation of a Tensor product""" - dev = qml.device("default.qubit", wires=[0, 1, 2, 3]) - - def circuit(): - qml.CNOT(wires=[0, 1]) - qml.CNOT(wires=[0, 3]) - return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1)) - - # build circuit - transpiled_qfunc = transpile(circuit, coupling_map=[(0, 1), (1, 2), (2, 3)]) - transpiled_qnode = qml.QNode(transpiled_qfunc, dev) - err_msg = r"Measuring expectation values of tensor products, Prods, or Hamiltonians is not yet supported" + err_msg = ( + "Measuring expectation values of tensor products or Hamiltonians is not yet supported" + ) with pytest.raises(NotImplementedError, match=err_msg): transpiled_qnode() @@ -111,7 +96,9 @@ def circuit(): # build circuit transpiled_qfunc = transpile(circuit, coupling_map=[(0, 1), (1, 2), (2, 3)]) transpiled_qnode = qml.QNode(transpiled_qfunc, dev) - err_msg = r"Measuring expectation values of tensor products, Prods, or Hamiltonians is not yet supported" + err_msg = ( + r"Measuring expectation values of tensor products or Hamiltonians is not yet supported" + ) with pytest.raises(NotImplementedError, match=err_msg): transpiled_qnode()