diff --git a/.github/workflows/interface-unit-tests.yml b/.github/workflows/interface-unit-tests.yml index a5247123171..41f2f3434e3 100644 --- a/.github/workflows/interface-unit-tests.yml +++ b/.github/workflows/interface-unit-tests.yml @@ -37,6 +37,11 @@ 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" jobs: setup-ci-load: @@ -155,6 +160,7 @@ jobs: pytest_coverage_flags: ${{ inputs.pytest_coverage_flags }} pytest_markers: torch and not qcut and not finite-diff and not param-shift requirements_file: ${{ strategy.job-index == 0 && 'torch.txt' || '' }} + disable_new_opmath: ${{ inputs.disable_new_opmath }} autograd-tests: @@ -186,6 +192,7 @@ jobs: install_pennylane_lightning_master: true pytest_coverage_flags: ${{ inputs.pytest_coverage_flags }} pytest_markers: autograd and not qcut and not finite-diff and not param-shift + disable_new_opmath: ${{ inputs.disable_new_opmath }} tf-tests: @@ -221,6 +228,7 @@ jobs: pytest_additional_args: --splits 3 --group ${{ matrix.group }} --durations-path='.github/workflows/tf_tests_durations.json' additional_pip_packages: pytest-split requirements_file: ${{ strategy.job-index == 0 && 'tf.txt' || '' }} + disable_new_opmath: ${{ inputs.disable_new_opmath }} jax-tests: @@ -256,6 +264,7 @@ jobs: pytest_additional_args: --splits 5 --group ${{ matrix.group }} --durations-path='.github/workflows/jax_tests_durations.json' additional_pip_packages: pytest-split requirements_file: ${{ strategy.job-index == 0 && 'jax.txt' || '' }} + disable_new_opmath: ${{ inputs.disable_new_opmath }} core-tests: @@ -291,6 +300,7 @@ jobs: pytest_additional_args: --splits 5 --group ${{ matrix.group }} --durations-path='.github/workflows/core_tests_durations.json' additional_pip_packages: pytest-split requirements_file: ${{ strategy.job-index == 0 && 'core.txt' || '' }} + disable_new_opmath: ${{ inputs.disable_new_opmath }} all-interfaces-tests: @@ -319,10 +329,11 @@ jobs: install_jax: true install_tensorflow: true install_pytorch: true - install_pennylane_lightning_master: false + install_pennylane_lightning_master: true pytest_coverage_flags: ${{ inputs.pytest_coverage_flags }} pytest_markers: all_interfaces requirements_file: ${{ strategy.job-index == 0 && 'all_interfaces.txt' || '' }} + disable_new_opmath: ${{ inputs.disable_new_opmath }} external-libraries-tests: @@ -351,11 +362,13 @@ jobs: install_jax: true install_tensorflow: true install_pytorch: false + # using lightning master does not work for the tests with external libraries install_pennylane_lightning_master: false pytest_coverage_flags: ${{ inputs.pytest_coverage_flags }} pytest_markers: external additional_pip_packages: pyzx pennylane-catalyst matplotlib stim requirements_file: ${{ strategy.job-index == 0 && 'external.txt' || '' }} + disable_new_opmath: ${{ inputs.disable_new_opmath }} qcut-tests: @@ -384,10 +397,11 @@ jobs: install_jax: true install_tensorflow: true install_pytorch: true - install_pennylane_lightning_master: false + install_pennylane_lightning_master: true pytest_coverage_flags: ${{ inputs.pytest_coverage_flags }} pytest_markers: qcut additional_pip_packages: kahypar==1.1.7 opt_einsum + disable_new_opmath: ${{ inputs.disable_new_opmath }} qchem-tests: @@ -416,10 +430,11 @@ jobs: install_jax: false install_tensorflow: false install_pytorch: false - install_pennylane_lightning_master: false + install_pennylane_lightning_master: true pytest_coverage_flags: ${{ inputs.pytest_coverage_flags }} pytest_markers: qchem additional_pip_packages: openfermionpyscf basis-set-exchange + disable_new_opmath: ${{ inputs.disable_new_opmath }} gradients-tests: needs: @@ -450,9 +465,10 @@ jobs: install_jax: true install_tensorflow: true install_pytorch: true - install_pennylane_lightning_master: false + install_pennylane_lightning_master: true pytest_coverage_flags: ${{ inputs.pytest_coverage_flags }} pytest_markers: ${{ matrix.config.suite }} + disable_new_opmath: ${{ inputs.disable_new_opmath }} data-tests: @@ -481,10 +497,11 @@ jobs: install_jax: false install_tensorflow: false install_pytorch: false - install_pennylane_lightning_master: false + install_pennylane_lightning_master: true pytest_coverage_flags: ${{ inputs.pytest_coverage_flags }} pytest_markers: data additional_pip_packages: h5py + disable_new_opmath: ${{ inputs.disable_new_opmath }} device-tests: @@ -529,10 +546,11 @@ jobs: install_jax: ${{ !contains(matrix.config.skip_interface, 'jax') }} install_tensorflow: ${{ !contains(matrix.config.skip_interface, 'tf') }} install_pytorch: ${{ !contains(matrix.config.skip_interface, 'torch') }} - install_pennylane_lightning_master: false + install_pennylane_lightning_master: true 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_new_opmath: ${{ inputs.disable_new_opmath }} upload-to-codecov: diff --git a/.github/workflows/legacy_op_math.yml b/.github/workflows/legacy_op_math.yml new file mode 100644 index 00000000000..13dd9ee6e82 --- /dev/null +++ b/.github/workflows/legacy_op_math.yml @@ -0,0 +1,16 @@ +name: Legacy opmath tests + +on: + schedule: + - cron: "0 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/.github/workflows/unit-test.yml b/.github/workflows/unit-test.yml index 2599face0cf..6a3e2a89416 100644 --- a/.github/workflows/unit-test.yml +++ b/.github/workflows/unit-test.yml @@ -94,6 +94,11 @@ 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" jobs: test: @@ -170,7 +175,7 @@ jobs: COV_CORE_DATAFILE: .coverage.eager TF_USE_LEGACY_KERAS: "1" # sets to use tf-keras (Keras2) instead of keras (Keras3) when running TF tests # Calling PyTest by invoking Python first as that adds the current directory to sys.path - run: python -m pytest ${{ inputs.pytest_test_directory }} ${{ steps.pytest_args.outputs.args }} ${{ env.PYTEST_MARKER }} + run: python -m pytest ${{ inputs.pytest_test_directory }} ${{ steps.pytest_args.outputs.args }} ${{ env.PYTEST_MARKER }} --disable-opmath=${{ inputs.disable_new_opmath }} - name: Adjust coverage file for Codecov if: inputs.pipeline_mode == 'unit-tests' diff --git a/pennylane/devices/tests/conftest.py b/pennylane/devices/tests/conftest.py index d9cef1bf48d..01e72a50bef 100755 --- a/pennylane/devices/tests/conftest.py +++ b/pennylane/devices/tests/conftest.py @@ -226,6 +226,20 @@ def pytest_addoption(parser): metavar="KEY=VAL", help="Additional device kwargs.", ) + 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): + """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): + qml.operation.disable_new_opmath() def pytest_generate_tests(metafunc): diff --git a/tests/conftest.py b/tests/conftest.py index 824ecdadd66..2d9573ea773 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -178,18 +178,27 @@ def tear_down_thermitian(): # 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): + qml.operation.disable_new_opmath() + + @pytest.fixture(scope="function") def use_legacy_opmath(): with disable_new_opmath_cm() as cm: yield cm -# @pytest.fixture(scope="function") -# def use_legacy_opmath(): -# with disable_new_opmath_cm(): -# yield - - @pytest.fixture(scope="function") def use_new_opmath(): with enable_new_opmath_cm() as cm: diff --git a/tests/data/attributes/operator/test_operator.py b/tests/data/attributes/operator/test_operator.py index e21a9be02e8..0f7d9db085d 100644 --- a/tests/data/attributes/operator/test_operator.py +++ b/tests/data/attributes/operator/test_operator.py @@ -83,6 +83,9 @@ def test_value_init(self, 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 = DatasetOperator(obs_in) assert dset_op.info["type_id"] == "operator" @@ -95,6 +98,9 @@ def test_value_init(self, obs_in): def test_bind_init(self, obs_in): """Test that DatasetOperator can be initialized from a HDF5 group that contains a 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 = DatasetOperator(obs_in).bind dset_op = DatasetOperator(bind=bind) @@ -124,6 +130,9 @@ def test_value_init(self, 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 = DatasetOperator(obs_in) assert dset_op.info["type_id"] == "operator" @@ -135,6 +144,9 @@ def test_value_init(self, obs_in): def test_bind_init(self, 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 = DatasetOperator(obs_in).bind dset_op = DatasetOperator(bind=bind) @@ -160,6 +172,9 @@ def test_value_init(self, op_in): """Test that a DatasetOperator can be value-initialized 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 = DatasetOperator(op_in) assert dset_op.info["type_id"] == "operator" @@ -172,7 +187,9 @@ def test_value_init(self, op_in): def test_value_init_not_supported(self): """Test that a ValueError is raised if attempting to serialize an unsupported operator.""" - class NotSupported(Operator): # pylint: disable=too-few-public-methods + class NotSupported( + Operator + ): # pylint: disable=too-few-public-methods, unnecessary-ellipsis """An operator.""" ... @@ -195,6 +212,9 @@ def test_bind_init(self, op_in): """Test that a DatasetOperator can be bind-initialized 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 = DatasetOperator(op_in).bind dset_op = DatasetOperator(bind=bind) diff --git a/tests/devices/default_qubit/test_default_qubit.py b/tests/devices/default_qubit/test_default_qubit.py index a4990949bc7..6ffb8a88c9e 100644 --- a/tests/devices/default_qubit/test_default_qubit.py +++ b/tests/devices/default_qubit/test_default_qubit.py @@ -2058,6 +2058,9 @@ 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_tracking.py b/tests/devices/default_qubit/test_default_qubit_tracking.py index ff52c6e7239..f12af5eab8d 100644 --- a/tests/devices/default_qubit/test_default_qubit_tracking.py +++ b/tests/devices/default_qubit/test_default_qubit_tracking.py @@ -255,8 +255,11 @@ def test_single_expval(mps, expected_exec, expected_shots): assert dev.tracker.totals["shots"] == 3 * expected_shots -@pytest.mark.xfail # TODO Prod instances are not automatically +@pytest.mark.usefixtures("use_new_opmath") +@pytest.mark.xfail(reason="bug in grouping for tracker with new opmath") def test_multiple_expval_with_prods(): + """Can be combined with test below once the bug is fixed - there shouldn't + be a difference in behaviour between old and new opmath here""" mps, expected_exec, expected_shots = ( [qml.expval(qml.PauliX(0)), qml.expval(qml.PauliX(0) @ qml.PauliY(1))], 1, @@ -274,7 +277,7 @@ def test_multiple_expval_with_prods(): @pytest.mark.usefixtures("use_legacy_opmath") -def test_multiple_expval_with_Tensors_legacy_opmath(): +def test_multiple_expval_with_tensors_legacy_opmath(): mps, expected_exec, expected_shots = ( [qml.expval(qml.PauliX(0)), qml.expval(qml.operation.Tensor(qml.PauliX(0), qml.PauliY(1)))], 1, diff --git a/tests/devices/qutrit_mixed/test_qutrit_mixed_sampling.py b/tests/devices/qutrit_mixed/test_qutrit_mixed_sampling.py index 00e98c5de5a..f3ff8a19bc0 100644 --- a/tests/devices/qutrit_mixed/test_qutrit_mixed_sampling.py +++ b/tests/devices/qutrit_mixed/test_qutrit_mixed_sampling.py @@ -365,7 +365,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 + assert results_gel_1s.dtype == np.float64 if qml.operation.active_new_opmath() else np.int64 assert sorted(np.unique(results_gel_1s)) == [-1, 0, 1] @flaky diff --git a/tests/devices/test_default_qubit_tf.py b/tests/devices/test_default_qubit_tf.py index ac81f6cf36c..195a5e7adfe 100644 --- a/tests/devices/test_default_qubit_tf.py +++ b/tests/devices/test_default_qubit_tf.py @@ -519,6 +519,7 @@ def test_four_qubit_parameters(self, init_state, op, func, theta, tol): expected = func(theta) @ state assert np.allclose(res, expected, atol=tol, rtol=0) + # pylint: disable=use-implicit-booleaness-not-comparison def test_apply_ops_not_supported(self, mocker, monkeypatch): """Test that when a version of TensorFlow before 2.3.0 is used, the _apply_ops dictionary is empty and application of a CNOT gate is performed using _apply_unitary_einsum""" @@ -927,11 +928,12 @@ def test_three_qubit_no_parameters_broadcasted(self, broadcasted_init_state, op, expected = np.einsum("ij,lj->li", mat, state) assert np.allclose(res, expected, atol=tol, rtol=0) + @pytest.mark.usefixtures("use_new_opmath") def test_direct_eval_hamiltonian_broadcasted_tf(self): """Tests that the correct result is returned when attempting to evaluate a Hamiltonian with broadcasting and shots=None directly via its sparse representation with TF.""" dev = qml.device("default.qubit.tf", wires=2) - ham = qml.Hamiltonian(tf.Variable([0.1, 0.2]), [qml.PauliX(0), qml.PauliZ(1)]) + ham = qml.ops.LinearCombination(tf.Variable([0.1, 0.2]), [qml.PauliX(0), qml.PauliZ(1)]) @qml.qnode(dev, diff_method="backprop", interface="tf") def circuit(): diff --git a/tests/devices/test_default_qubit_torch.py b/tests/devices/test_default_qubit_torch.py index 0f8f5200e42..9bcbae44b03 100644 --- a/tests/devices/test_default_qubit_torch.py +++ b/tests/devices/test_default_qubit_torch.py @@ -914,12 +914,13 @@ def test_three_qubit_no_parameters_broadcasted( expected = qml.math.einsum("ij,lj->li", op_mat, state) assert torch.allclose(res, expected, atol=tol, rtol=0) + @pytest.mark.usefixtures("use_new_opmath") def test_direct_eval_hamiltonian_broadcasted_torch(self, device, torch_device, mocker): """Tests that the correct result is returned when attempting to evaluate a Hamiltonian with broadcasting and shots=None directly via its sparse representation with torch.""" dev = device(wires=2, torch_device=torch_device) - ham = qml.Hamiltonian( + ham = qml.ops.LinearCombination( torch.tensor([0.1, 0.2], requires_grad=True), [qml.PauliX(0), qml.PauliZ(1)] ) diff --git a/tests/fermi/test_bravyi_kitaev.py b/tests/fermi/test_bravyi_kitaev.py index b962df0ad4e..eefe3470504 100644 --- a/tests/fermi/test_bravyi_kitaev.py +++ b/tests/fermi/test_bravyi_kitaev.py @@ -427,7 +427,412 @@ def test_error_is_raised_for_dimension_mismatch(): ), ] +with qml.operation.disable_new_opmath_cm(): + 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("use_new_opmath") @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.""" @@ -442,6 +847,24 @@ def test_bravyi_kitaev_fermi_word_ps(fermionic_op, n_qubits, result): assert qubit_op == expected_op +@pytest.mark.usefixtures("use_legacy_opmath") +@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("use_new_opmath") @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] @@ -454,6 +877,19 @@ def test_bravyi_kitaev_fermi_word_operation(fermionic_op, n_qubits, result): assert qml.equal(qubit_op.simplify(), expected_op.simplify()) +@pytest.mark.usefixtures("use_legacy_opmath") +@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) + + assert qml.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.""" assert qml.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 ddf12ece5b7..540f4c67f57 100644 --- a/tests/fermi/test_fermi_mapping.py +++ b/tests/fermi/test_fermi_mapping.py @@ -350,6 +350,336 @@ ] +with qml.operation.disable_new_opmath_cm(): + 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("use_new_opmath") @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.""" @@ -364,6 +694,24 @@ def test_jordan_wigner_fermi_word_ps(fermionic_op, result): assert qubit_op == expected_op +@pytest.mark.usefixtures("use_legacy_opmath") +@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("use_new_opmath") # 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): @@ -377,6 +725,20 @@ def test_jordan_wigner_fermi_word_operation(fermionic_op, result): assert qml.equal(qubit_op.simplify(), expected_op.simplify()) +@pytest.mark.usefixtures("use_legacy_opmath") +# 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) + + assert qml.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.""" assert qml.equal(jordan_wigner(FermiWord({})), qml.Identity(0)) diff --git a/tests/fermi/test_parity_mapping.py b/tests/fermi/test_parity_mapping.py index eafe7c1ed4b..007444dd6ce 100644 --- a/tests/fermi/test_parity_mapping.py +++ b/tests/fermi/test_parity_mapping.py @@ -437,7 +437,422 @@ def test_error_is_raised_for_dimension_mismatch(): ), ] +with qml.operation.disable_new_opmath_cm(): + 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("use_new_opmath") @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.""" @@ -452,6 +867,24 @@ def test_parity_transform_fermi_word_ps(fermionic_op, n_qubits, result): assert qubit_op == expected_op +@pytest.mark.usefixtures("use_legacy_opmath") +@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("use_new_opmath") @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] @@ -464,6 +897,19 @@ def test_parity_transform_fermi_word_operation(fermionic_op, n_qubits, result): assert qml.equal(qubit_op.simplify(), expected_op.simplify()) +@pytest.mark.usefixtures("use_legacy_opmath") +@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) + + assert qml.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.""" assert qml.equal(parity_transform(FermiWord({}), 2), qml.Identity(0)) diff --git a/tests/interfaces/test_autograd.py b/tests/interfaces/test_autograd.py index 7296669ddd2..69b76a88152 100644 --- a/tests/interfaces/test_autograd.py +++ b/tests/interfaces/test_autograd.py @@ -183,6 +183,7 @@ class TestBatchTransformExecution: """Tests to ensure batch transforms can be correctly executed via qml.execute and map_batch_transform""" + @pytest.mark.usefixtures("use_new_opmath") def test_no_batch_transform(self, mocker): """Test that batch transforms can be disabled and enabled""" dev = qml.device("default.qubit.legacy", wires=2, shots=100000) @@ -212,6 +213,36 @@ def test_no_batch_transform(self, mocker): assert res[0].shape == () assert np.allclose(res[0], np.cos(y), atol=0.1) + @pytest.mark.usefixtures("use_legacy_opmath") + def test_no_batch_transform_legacy_opmath(self, mocker): + """Test functionality to enable and disable""" + dev = qml.device("default.qubit.legacy", wires=2, shots=100000) + + H = qml.PauliZ(0) @ qml.PauliZ(1) - qml.PauliX(0) + x = 0.6 + y = 0.2 + + with qml.queuing.AnnotatedQueue() as q: + qml.RX(x, wires=0) + qml.RY(y, wires=1) + qml.CNOT(wires=[0, 1]) + qml.expval(H) + + tape = qml.tape.QuantumScript.from_queue(q) + spy = mocker.spy(dev, "batch_transform") + + with pytest.raises(AssertionError, match="Hamiltonian must be used with shots=None"): + res = qml.execute([tape], dev, None, device_batch_transform=False) + + spy.assert_not_called() + + res = qml.execute([tape], dev, None, device_batch_transform=True) + spy.assert_called() + + assert isinstance(res[0], np.ndarray) + assert res[0].shape == () + assert np.allclose(res[0], np.cos(y), atol=0.1) + def test_batch_transform_dynamic_shots(self): """Tests that the batch transform considers the number of shots for the execution, not those statically on the device.""" diff --git a/tests/measurements/legacy/test_classical_shadow_legacy.py b/tests/measurements/legacy/test_classical_shadow_legacy.py index 506a28a7d2e..691570506e5 100644 --- a/tests/measurements/legacy/test_classical_shadow_legacy.py +++ b/tests/measurements/legacy/test_classical_shadow_legacy.py @@ -420,7 +420,10 @@ def test_non_pauli_error(self): """Test that an error is raised when a non-Pauli observable is passed""" circuit = hadamard_circuit(3) - msg = "Observable must have a valid pauli representation." + 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): circuit(qml.Hadamard(0) @ qml.Hadamard(2)) diff --git a/tests/measurements/test_classical_shadow.py b/tests/measurements/test_classical_shadow.py index 45384c43267..0dba945afc9 100644 --- a/tests/measurements/test_classical_shadow.py +++ b/tests/measurements/test_classical_shadow.py @@ -573,7 +573,10 @@ def test_non_pauli_error(self): """Test that an error is raised when a non-Pauli observable is passed""" circuit = hadamard_circuit(3) - msg = "Observable must have a valid pauli representation." + 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): circuit(qml.Hadamard(0) @ qml.Hadamard(2)) diff --git a/tests/ops/functions/test_equal.py b/tests/ops/functions/test_equal.py index f9d61178894..e0b89c53024 100644 --- a/tests/ops/functions/test_equal.py +++ b/tests/ops/functions/test_equal.py @@ -1647,6 +1647,7 @@ def test_s_prod_comparison_with_tolerance(self): assert not qml.equal(op1, op2, atol=1e-5, rtol=1e-4) +@pytest.mark.usefixtures("use_new_opmath") class TestProdComparisons: """Tests comparisons between Prod operators""" @@ -1744,6 +1745,7 @@ def test_prod_of_prods(self): assert qml.equal(op1, op2) +@pytest.mark.usefixtures("use_new_opmath") class TestSumComparisons: """Tests comparisons between Sum operators""" diff --git a/tests/ops/functions/test_generator.py b/tests/ops/functions/test_generator.py index 935eddb0b90..23a60a946a6 100644 --- a/tests/ops/functions/test_generator.py +++ b/tests/ops/functions/test_generator.py @@ -348,6 +348,7 @@ def test_observable(self): assert gen.name == "Hamiltonian" assert gen.compare(ObservableOp(0.5, wires=0).generator()) + @pytest.mark.usefixtures("use_new_opmath") def test_observable_opmath(self): """Test a generator that returns a single observable is correct with opmath enabled""" gen = qml.generator(ObservableOp, format="observable")(0.5, wires=0) @@ -361,6 +362,7 @@ def test_tensor_observable(self): assert gen.name == "Hamiltonian" assert gen.compare(TensorOp(0.5, wires=[0, 1]).generator()) + @pytest.mark.usefixtures("use_new_opmath") def test_tensor_observable_opmath(self): """Test a generator that returns a tensor observable is correct with opmath enabled""" gen = qml.generator(TensorOp, format="observable")(0.5, wires=[0, 1]) diff --git a/tests/ops/op_math/test_adjoint.py b/tests/ops/op_math/test_adjoint.py index 58504bfd7bc..f84afe58901 100644 --- a/tests/ops/op_math/test_adjoint.py +++ b/tests/ops/op_math/test_adjoint.py @@ -124,6 +124,7 @@ def test_pickling(self, op): class TestInitialization: """Test the initialization process and standard properties.""" + # pylint: disable=use-implicit-booleaness-not-comparison def test_nonparametric_ops(self): """Test adjoint initialization for a non parameteric operation.""" base = qml.PauliX("a") @@ -1049,7 +1050,6 @@ def test_single_op_eager(self): assert isinstance(out, qml.RX) assert out.data == (-x,) - @pytest.mark.xfail # TODO not sure what the expected behavior here is with new opmath def test_observable(self): """Test providing a preconstructed Observable outside of a queuing context.""" @@ -1057,7 +1057,9 @@ def test_observable(self): obs = adjoint(base) assert isinstance(obs, Adjoint) - assert isinstance(obs, qml.operation.Observable) + assert isinstance(base, qml.operation.Observable) == isinstance( + obs, qml.operation.Observable + ) assert obs.base is base def test_single_op_function(self): diff --git a/tests/ops/op_math/test_evolution.py b/tests/ops/op_math/test_evolution.py index 852b557593b..5ee11ea5b52 100644 --- a/tests/ops/op_math/test_evolution.py +++ b/tests/ops/op_math/test_evolution.py @@ -72,13 +72,14 @@ def test_generator(self): assert U.base == U.generator() @pytest.mark.usefixtures("use_legacy_opmath") - def test_num_params_for_parametric_bas_legacy_opmath(self): + 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("use_new_opmath") 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_linear_combination.py b/tests/ops/op_math/test_linear_combination.py index cb05bcbfda0..52970279b70 100644 --- a/tests/ops/op_math/test_linear_combination.py +++ b/tests/ops/op_math/test_linear_combination.py @@ -535,6 +535,7 @@ def circuit2(param): dev = qml.device("default.qubit", wires=2) +@pytest.mark.usefixtures("use_new_opmath") class TestLinearCombination: """Test the LinearCombination class""" @@ -912,7 +913,6 @@ def test_LinearCombination_queue_outside(self): def test_LinearCombination_queue_inside(self): """Tests that LinearCombination are queued correctly when components are instantiated inside the recording context.""" - assert qml.operation.active_new_opmath() with qml.queuing.AnnotatedQueue() as q: m = qml.expval(qml.ops.LinearCombination([1, 3, 1], [X(1), Z(0) @ Z(2), Z(1)])) diff --git a/tests/ops/op_math/test_sum.py b/tests/ops/op_math/test_sum.py index 44ebd22d053..f9fdd3ae12c 100644 --- a/tests/ops/op_math/test_sum.py +++ b/tests/ops/op_math/test_sum.py @@ -328,6 +328,7 @@ 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("use_new_opmath") @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""" diff --git a/tests/ops/qubit/test_hamiltonian.py b/tests/ops/qubit/test_hamiltonian.py index c88b1978a92..9db2647a87f 100644 --- a/tests/ops/qubit/test_hamiltonian.py +++ b/tests/ops/qubit/test_hamiltonian.py @@ -14,7 +14,7 @@ """ Tests for the Hamiltonian class. """ -# pylint: disable=too-many-public-methods +# pylint: disable=too-many-public-methods, superfluous-parens, unnecessary-dunder-call from collections.abc import Iterable from unittest.mock import patch diff --git a/tests/ops/qubit/test_parametric_ops.py b/tests/ops/qubit/test_parametric_ops.py index 88ead3da971..fc3c49928ae 100644 --- a/tests/ops/qubit/test_parametric_ops.py +++ b/tests/ops/qubit/test_parametric_ops.py @@ -3028,6 +3028,7 @@ def test_pauli_rot_generator_legacy_opmath(self): assert gen.operands[0].name == expected.obs[0].name assert gen.operands[1].wires == expected.obs[1].wires + @pytest.mark.usefixtures("use_new_opmath") def test_pauli_rot_generator(self): """Test that the generator of the PauliRot operation is correctly returned.""" diff --git a/tests/pauli/test_conversion.py b/tests/pauli/test_conversion.py index f111c562bb5..9753b953e3e 100644 --- a/tests/pauli/test_conversion.py +++ b/tests/pauli/test_conversion.py @@ -143,6 +143,7 @@ def test_observable_types_legacy_opmath(self, hamiltonian, hide_identity): _, decomposed_obs = qml.pauli_decompose(hamiltonian, hide_identity).terms() assert all((isinstance(o, allowed_obs) for o in decomposed_obs)) + @pytest.mark.usefixtures("use_new_opmath") @pytest.mark.parametrize("hide_identity", [True, False]) @pytest.mark.parametrize("hamiltonian", test_hamiltonians) def test_observable_types(self, hamiltonian, hide_identity): @@ -162,6 +163,7 @@ def test_result_length(self, hamiltonian): tensors = filter(lambda obs: isinstance(obs, Tensor), decomposed_obs) assert all(len(tensor.obs) == n for tensor in tensors) + # pylint: disable = consider-using-generator @pytest.mark.parametrize("hamiltonian", test_hamiltonians) def test_decomposition(self, hamiltonian): """Tests that pauli_decompose successfully decomposes Hamiltonians into a @@ -266,6 +268,7 @@ def test_observable_types_legacy_opmath(self, hamiltonian, hide_identity): ).terms() assert all((isinstance(o, allowed_obs) for o in decomposed_obs)) + @pytest.mark.usefixtures("use_new_opmath") @pytest.mark.parametrize("hide_identity", [True, False]) @pytest.mark.parametrize("hamiltonian", test_hamiltonians) def test_observable_types(self, hamiltonian, hide_identity): @@ -288,6 +291,7 @@ def test_result_length(self, hamiltonian): tensors = filter(lambda obs: isinstance(obs, Tensor), decomposed_obs) assert all(len(tensor.obs) == n for tensor in tensors) + # pylint: disable = consider-using-generator @pytest.mark.parametrize("hamiltonian", test_hamiltonians) def test_decomposition(self, hamiltonian): """Tests that pauli_decompose successfully decomposes Hamiltonians into a @@ -314,6 +318,7 @@ def test_to_paulisentence(self, hamiltonian): assert isinstance(ps, qml.pauli.PauliSentence) assert np.allclose(hamiltonian, ps.to_mat(range(num_qubits))) + # pylint: disable = consider-using-generator @pytest.mark.usefixtures("use_legacy_opmath") @pytest.mark.parametrize("hide_identity", [True, False]) @pytest.mark.parametrize("matrix", test_general_matrix) @@ -342,6 +347,8 @@ def test_observable_types_general_legacy_opmath(self, matrix, 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("use_new_opmath") @pytest.mark.parametrize("hide_identity", [True, False]) @pytest.mark.parametrize("matrix", test_general_matrix) def test_observable_types_general(self, matrix, hide_identity): @@ -428,6 +435,7 @@ def test_builtins(self, matrix): coeffs = qml.pauli_decompose(mat).coeffs assert qml.math.get_interface(coeffs[0]) == interface + # pylint: disable = superfluous-parens # Multiple interfaces will be tested with math module @pytest.mark.all_interfaces @pytest.mark.parametrize( diff --git a/tests/pauli/test_pauli_utils.py b/tests/pauli/test_pauli_utils.py index fa386c493d4..2f21637e06e 100644 --- a/tests/pauli/test_pauli_utils.py +++ b/tests/pauli/test_pauli_utils.py @@ -471,6 +471,7 @@ def test_pauli_word_to_string_invalid_input(self, non_pauli_word): with pytest.raises(TypeError): pauli_word_to_string(non_pauli_word) + @pytest.mark.usefixtures("use_new_opmath") @pytest.mark.parametrize( "pauli_string,wire_map,expected_pauli", [ diff --git a/tests/shadow/test_shadow_class.py b/tests/shadow/test_shadow_class.py index bcd3de1c4ac..025d68d54c4 100644 --- a/tests/shadow/test_shadow_class.py +++ b/tests/shadow/test_shadow_class.py @@ -364,7 +364,10 @@ def test_non_pauli_error_no_pauli_rep(self): H = qml.Hadamard(0) @ qml.Hadamard(2) - msg = "Observable must have a valid pauli representation." + 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): shadow.expval(H, k=10) diff --git a/tests/test_device.py b/tests/test_device.py index 22200272bdc..18eef9b5b36 100644 --- a/tests/test_device.py +++ b/tests/test_device.py @@ -306,6 +306,7 @@ 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("use_new_opmath") def test_check_validity_containing_prod(self, mock_device_supporting_prod): """Tests that the function Device.check_validity works with Prod""" @@ -323,6 +324,7 @@ def test_check_validity_containing_prod(self, mock_device_supporting_prod): dev.check_validity(queue, observables) + @pytest.mark.usefixtures("use_new_opmath") def test_prod_containing_unsupported_nested_observables(self, mock_device_supporting_prod): """Tests that the observables nested within Prod are checked for validity""" @@ -360,6 +362,7 @@ def test_check_validity_on_tensor_support_legacy_opmath(self, mock_device_suppor with pytest.raises(DeviceError, match="Tensor observables not supported"): dev.check_validity(queue, observables) + @pytest.mark.usefixtures("use_new_opmath") 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() diff --git a/tests/test_operation.py b/tests/test_operation.py index f3046cd56a2..b1d9c816f7b 100644 --- a/tests/test_operation.py +++ b/tests/test_operation.py @@ -2632,15 +2632,10 @@ def test_composed(self): ] +@pytest.mark.usefixtures("use_new_opmath") class TestNewOpMath: """Tests dunder operations with new operator arithmetic enabled.""" - # @pytest.fixture(autouse=True, scope="function") # this came from a push to ham-tests but I think it should not be there as it explicitly disabled new opmath after each test, so also leaving it in that state for other tests. - # def run_before_and_after_tests(self): - # qml.operation.enable_new_opmath() - # yield - # qml.operation.disable_new_opmath() - class TestAdd: """Test the __add__/__radd__/__sub__ dunders.""" @@ -2758,32 +2753,33 @@ def test_mul_does_auto_simplify(self): assert qml.equal(op[1], op1) assert qml.equal(op[2], op2) - class TestHamiltonianLinearCombinationAlias: - """Unit tests for using qml.Hamiltonian as an alias for LinearCombination""" - @pytest.mark.usefixtures("use_new_opmath") - def test_hamiltonian_linear_combination_alias_enabled(self): - """Test that qml.Hamiltonian is an alias for LinearCombination with new operator - arithmetic enabled""" - op = qml.Hamiltonian([1.0], [qml.X(0)]) +class TestHamiltonianLinearCombinationAlias: + """Unit tests for using qml.Hamiltonian as an alias for LinearCombination""" + + @pytest.mark.usefixtures("use_new_opmath") + def test_hamiltonian_linear_combination_alias_enabled(self): + """Test that qml.Hamiltonian is an alias for LinearCombination with new operator + arithmetic enabled""" + op = qml.Hamiltonian([1.0], [qml.X(0)]) - 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) + 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("use_legacy_opmath") - 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)]) + @pytest.mark.usefixtures("use_legacy_opmath") + 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) + 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( @@ -2822,6 +2818,7 @@ def test_symmetric_matrix_early_return(op, mocker): assert np.allclose(actual, manually_expanded) +@pytest.mark.usefixtures("use_new_opmath") def test_op_arithmetic_toggle(): """Tests toggling op arithmetic on and off, and that it is on by default.""" assert qml.operation.active_new_opmath() @@ -2957,6 +2954,7 @@ def test_use_legacy_opmath_fixture(): ] +@pytest.mark.usefixtures("use_new_opmath") @pytest.mark.parametrize("coeffs, obs", CONVERT_HAMILTONAIN) def test_convert_to_hamiltonian(coeffs, obs): """Test that arithmetic operators can be converted to Hamiltonian instances""" @@ -3002,6 +3000,7 @@ def test_convert_to_hamiltonian_error(coeffs, obs): convert_to_legacy_H(qml.dot(coeffs, obs)) +@pytest.mark.usefixtures("use_new_opmath") def test_convert_to_H(): operator = ( 2 * qml.X(0) diff --git a/tests/test_qaoa.py b/tests/test_qaoa.py index 84ed32f7446..591b5a5ce3f 100644 --- a/tests/test_qaoa.py +++ b/tests/test_qaoa.py @@ -2018,6 +2018,7 @@ 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("use_new_opmath") 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 diff --git a/tests/test_queuing.py b/tests/test_queuing.py index 7f335027553..48bb6a30891 100644 --- a/tests/test_queuing.py +++ b/tests/test_queuing.py @@ -224,6 +224,7 @@ def test_append_tensor_ops_overloaded(self): assert q.queue == [tensor_op] assert tensor_op.obs == [A, B] + @pytest.mark.usefixtures("use_new_opmath") 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.""" diff --git a/tests/test_vqe.py b/tests/test_vqe.py index 1ab64be9838..84248d35f3d 100644 --- a/tests/test_vqe.py +++ b/tests/test_vqe.py @@ -64,6 +64,18 @@ def res(params): (qml.PauliX(0) @ qml.PauliZ(1), qml.PauliY(0) @ qml.PauliZ(1), qml.PauliZ(1)), ] +with qml.operation.disable_new_opmath_cm(): + OBSERVABLES_LEGACY = [ + (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]),), + ] + + OBSERVABLES_NO_HERMITIAN_LEGACY = [ + (qml.PauliZ(0), qml.PauliY(0), qml.PauliZ(1)), + (qml.PauliX(0) @ qml.PauliZ(1), qml.PauliY(0) @ qml.PauliZ(1), qml.PauliZ(1)), + ] + hamiltonians_with_expvals = [ ((-0.6,), (qml.PauliZ(0),), [-0.6 * 1.0]), ((1.0,), (qml.PauliX(0),), [0.0]), @@ -269,6 +281,7 @@ def get_device(wires=1): class TestVQE: """Test the core functionality of the VQE module""" + @pytest.mark.usefixtures("use_new_opmath") @pytest.mark.parametrize("ansatz, params", CIRCUITS) @pytest.mark.parametrize("coeffs, observables", list(zip(COEFFS, OBSERVABLES))) def test_cost_evaluate(self, params, ansatz, coeffs, observables): @@ -279,6 +292,18 @@ def test_cost_evaluate(self, params, ansatz, coeffs, observables): assert expval(params).dtype == np.float64 assert np.shape(expval(params)) == () # expval should be scalar + @pytest.mark.usefixtures("use_legacy_opmath") + @pytest.mark.parametrize("ansatz, params", CIRCUITS) + @pytest.mark.parametrize("coeffs, observables", list(zip(COEFFS, OBSERVABLES_LEGACY))) + def test_cost_evaluate_legacy(self, params, ansatz, coeffs, observables): + """Tests that the cost function evaluates properly""" + hamiltonian = qml.Hamiltonian(coeffs, observables) + dev = qml.device("default.qubit", wires=3) + expval = generate_cost_fn(ansatz, hamiltonian, dev) + assert expval(params).dtype == np.float64 + assert np.shape(expval(params)) == () # expval should be scalar + + @pytest.mark.usefixtures("use_new_opmath") @pytest.mark.parametrize( "coeffs, observables, expected", hamiltonians_with_expvals + zero_hamiltonians_with_expvals ) @@ -289,6 +314,15 @@ def test_cost_expvals(self, coeffs, observables, expected): cost = generate_cost_fn(lambda params, **kwargs: None, hamiltonian, dev) assert cost([]) == sum(expected) + @pytest.mark.usefixtures("use_legacy_opmath") + @pytest.mark.parametrize("coeffs, observables, expected", hamiltonians_with_expvals) + def test_cost_expvals_legacy(self, coeffs, observables, expected): + """Tests that the cost function returns correct expectation values""" + dev = qml.device("default.qubit", wires=2) + hamiltonian = qml.Hamiltonian(coeffs, observables) + cost = generate_cost_fn(lambda params, **kwargs: None, hamiltonian, dev) + assert cost([]) == sum(expected) + # pylint: disable=protected-access @pytest.mark.torch @pytest.mark.slow @@ -738,6 +772,7 @@ class TestNewVQE: """Test the new VQE syntax of passing the Hamiltonian as an observable.""" # pylint: disable=cell-var-from-loop + @pytest.mark.usefixtures("use_new_opmath") @pytest.mark.parametrize("ansatz, params", CIRCUITS) @pytest.mark.parametrize("observables", OBSERVABLES_NO_HERMITIAN) def test_circuits_evaluate(self, ansatz, observables, params, tol): @@ -768,6 +803,38 @@ def separate_circuit(): assert np.isclose(res, res_expected, atol=tol) + # pylint: disable=cell-var-from-loop + @pytest.mark.usefixtures("use_legacy_opmath") + @pytest.mark.parametrize("ansatz, params", CIRCUITS) + @pytest.mark.parametrize("observables", OBSERVABLES_NO_HERMITIAN_LEGACY) + def test_circuits_evaluate_legacy(self, ansatz, observables, params, tol): + """Tests simple VQE evaluations.""" + coeffs = [1.0] * len(observables) + dev = qml.device("default.qubit", wires=3) + H = qml.Hamiltonian(coeffs, observables) + + # pass H directly + @qml.qnode(dev) + def circuit(): + ansatz(params, wires=range(3)) + return qml.expval(H) + + res = circuit() + + res_expected = [] + for obs in observables: + + @qml.qnode(dev) + def separate_circuit(): + ansatz(params, wires=range(3)) + return qml.expval(obs) + + res_expected.append(separate_circuit()) + + res_expected = np.sum([c * r for c, r in zip(coeffs, res_expected)]) + + assert np.isclose(res, res_expected, atol=tol) + def test_acting_on_subcircuit(self, tol): """Tests a VQE circuit where the observable does not act on all wires.""" dev = qml.device("default.qubit", wires=3) @@ -1019,9 +1086,31 @@ def circuit(w): dc = jax.grad(circuit)(w) assert np.allclose(dc, big_hamiltonian_grad, atol=tol) + @pytest.mark.usefixtures("use_legacy_opmath") + def test_specs_legacy(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 + + # currently this returns 1 instead, because diagonalizing gates exist for H, + # but they aren't used in executing this qnode + # to be revisited in [sc-59117] + assert res["num_diagonalizing_gates"] == 0 + @pytest.mark.xfail( reason="diagonalizing gates defined but not used, should not be included in specs" ) + @pytest.mark.usefixtures("use_new_opmath") def test_specs(self): """Test that the specs of a VQE circuit can be computed""" dev = qml.device("default.qubit", wires=2) diff --git a/tests/transforms/test_qcut.py b/tests/transforms/test_qcut.py index aa2a6e6effd..a59938fff15 100644 --- a/tests/transforms/test_qcut.py +++ b/tests/transforms/test_qcut.py @@ -16,7 +16,7 @@ """ # pylint: disable=protected-access,too-few-public-methods,too-many-arguments # pylint: disable=too-many-public-methods,comparison-with-callable -# pylint: disable=no-value-for-parameter,no-member,not-callable +# pylint: disable=no-value-for-parameter,no-member,not-callable, use-implicit-booleaness-not-comparison import copy import itertools import string @@ -5716,7 +5716,7 @@ def test_circuit_with_hamiltonian_opmath(self, mocker): hamiltonian = qml.Hamiltonian( [1.0, 1.0], - [qml.prod(qml.PauliZ(1), qml.PauliZ(2), qml.PauliZ(3)), qml.PauliY(0) @ qml.PauliX(1)], + [qml.PauliZ(1) @ qml.PauliZ(2) @ qml.PauliZ(3), qml.PauliY(0) @ qml.PauliX(1)], ) def two_qubit_unitary(param, wires): @@ -5840,8 +5840,8 @@ def block(weights, wires): hamiltonian = qml.Hamiltonian( [1.0, 1.0], [ - qml.prod(qml.PauliZ(1), qml.PauliZ(8), qml.PauliZ(3)), - qml.prod(qml.PauliY(5), qml.PauliX(4)), + qml.PauliZ(1) @ qml.PauliZ(8) @ qml.PauliZ(3), + qml.PauliY(5) @ qml.PauliX(4), ], ) @@ -5871,7 +5871,6 @@ def block(weights, wires): # each frag should have the device size constraint satisfied. assert all(len(set(e[2] for e in f.edges.data("wire"))) <= device_size for f in frags) - @pytest.mark.xfail def test_hamiltonian_with_tape(self): """Test that an expand function that generates multiple tapes is applied before the transform and the transform returns correct results."""