From 356bdce287cf359472685969527c7574069e6eb2 Mon Sep 17 00:00:00 2001 From: Shuli Shu <31480676+multiphaseCFD@users.noreply.github.com> Date: Wed, 17 Jul 2024 22:45:13 -0400 Subject: [PATCH] Add LTensor Hermitian wire length check (#806) ### Before submitting Please complete the following checklist when submitting a PR: - [ ] All new features must include a unit test. If you've fixed a bug or added code that should be tested, add a test to the [`tests`](../tests) directory! - [ ] All new functions and code must be clearly commented and documented. If you do make documentation changes, make sure that the docs build and render correctly by running `make docs`. - [ ] Ensure that the test suite passes, by running `make test`. - [x] Add a new entry to the `.github/CHANGELOG.md` file, summarizing the change, and including a link back to the PR. - [x] Ensure that code is properly formatted by running `make format`. When all the above are checked, delete everything above the dashed line and fill in the pull request template. ------------------------------------------------------------------------------------------------------------ **Context:** As of cutensornet-v24.03.0, only 1-wire Hermitian observables can be correctly supported. This limit was captured by previous python unit tests. This PR add Hermitian obs wires check in both python and C++ layers and updates the python unit tests to ensure this limit can be captured. [SC-69138] **Description of the Change:** **Benefits:** **Possible Drawbacks:** **Related GitHub Issues:** --------- Co-authored-by: ringo-but-quantum --- .github/CHANGELOG.md | 3 + pennylane_lightning/core/_serialize.py | 2 + pennylane_lightning/core/_version.py | 2 +- .../tncuda/observables/ObservablesTNCuda.hpp | 3 + .../tests/Test_Observables_TNCuda.cpp | 16 +- .../lightning_tensor/_measurements.py | 4 + tests/test_expval.py | 18 +- tests/test_measurements.py | 20 +- tests/test_serialize.py | 226 +++++++++++++++--- 9 files changed, 237 insertions(+), 57 deletions(-) diff --git a/.github/CHANGELOG.md b/.github/CHANGELOG.md index f9b61fa81f..4ee8cd92eb 100644 --- a/.github/CHANGELOG.md +++ b/.github/CHANGELOG.md @@ -37,6 +37,9 @@ ### Bug fixes +* Check for the number of wires for Hermitian observables in Lightning-Tensor. Only 1-wire Hermitian observables are supported as of `cuTensorNet-v24.03.0`. + [(#806)](https://github.com/PennyLaneAI/pennylane-lightning/pull/806) + * Set `PL_BACKEND` for the entire `build-wheel-lightning-gpu` Docker-build stage to properly build the Lightning-GPU wheel. [(#791)](https://github.com/PennyLaneAI/pennylane-lightning/pull/791) diff --git a/pennylane_lightning/core/_serialize.py b/pennylane_lightning/core/_serialize.py index b056e7fba8..4f8a9ab567 100644 --- a/pennylane_lightning/core/_serialize.py +++ b/pennylane_lightning/core/_serialize.py @@ -205,6 +205,8 @@ def _hermitian_ob(self, observable, wires_map: dict = None): """Serializes a Hermitian observable""" wires = [wires_map[w] for w in observable.wires] if wires_map else observable.wires.tolist() + if self.device_name == "lightning.tensor" and len(wires) > 1: + raise ValueError("The number of Hermitian observables target wires should be 1.") return self.hermitian_obs(matrix(observable).ravel().astype(self.ctype), wires) def _tensor_ob(self, observable, wires_map: dict = None): diff --git a/pennylane_lightning/core/_version.py b/pennylane_lightning/core/_version.py index 76b178d200..2aa597931d 100644 --- a/pennylane_lightning/core/_version.py +++ b/pennylane_lightning/core/_version.py @@ -16,4 +16,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "0.38.0-dev10" +__version__ = "0.38.0-dev11" diff --git a/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/observables/ObservablesTNCuda.hpp b/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/observables/ObservablesTNCuda.hpp index 3db2825e7b..e17e0c7d5c 100644 --- a/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/observables/ObservablesTNCuda.hpp +++ b/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/observables/ObservablesTNCuda.hpp @@ -20,6 +20,7 @@ #include "Constant.hpp" #include "ConstantUtil.hpp" // lookup +#include "Error.hpp" #include "Util.hpp" #include "cuda_helpers.hpp" @@ -241,6 +242,8 @@ class HermitianObsTNCuda : public ObservableTNCuda { */ HermitianObsTNCuda(MatrixT matrix, std::vector wires) : matrix_{std::move(matrix)}, wires_{std::move(wires)} { + PL_ABORT_IF(wires_.size() != 1, "The number of Hermitian target wires " + "must be 1 for Lightning-Tensor."); PL_ASSERT(matrix_.size() == Pennylane::Util::exp2(2 * wires_.size())); BaseType::coeffs_.emplace_back(PrecisionT{1.0}); BaseType::numTensors_.emplace_back(std::size_t{1}); diff --git a/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/observables/tests/Test_Observables_TNCuda.cpp b/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/observables/tests/Test_Observables_TNCuda.cpp index d43e0aaae5..a62a3a5a99 100644 --- a/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/observables/tests/Test_Observables_TNCuda.cpp +++ b/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/observables/tests/Test_Observables_TNCuda.cpp @@ -86,9 +86,6 @@ TEMPLATE_TEST_CASE("[Hermitian]", "[Observables]", float, double) { using HermitianObsT = HermitianObsTNCuda; SECTION("HermitianObs only accepts correct arguments") { - auto ob1 = - HermitianObsT{std::vector{0.0, 0.0, 0.0, 0.0}, {0}}; - auto ob2 = HermitianObsT{std::vector(16, ComplexT{}), {0, 1}}; REQUIRE_THROWS_AS( HermitianObsT(std::vector{0.0, 0.0, 0.0}, {0}), LightningException); @@ -96,6 +93,11 @@ TEMPLATE_TEST_CASE("[Hermitian]", "[Observables]", float, double) { HermitianObsT(std::vector{0.0, 0.0, 0.0, 0.0, 0.0}, {0, 1}), LightningException); + REQUIRE_THROWS_WITH( + HermitianObsT(std::vector(16, ComplexT{0.0, 0.0}), + {0, 1}), + Catch::Matchers::Contains("The number of Hermitian target wires " + "must be 1 for Lightning-Tensor.")); } SECTION("Test get obs name") { @@ -153,8 +155,8 @@ TEMPLATE_TEST_CASE("[TensorProd]", "[Observables]", float, double) { SECTION("Overlapping wires throw an exception") { auto ob1 = std::make_shared( - std::vector(16, ComplexT{0.0, 0.0}), - std::vector{0, 1}); + std::vector(4, ComplexT{0.0, 0.0}), + std::vector{1}); auto ob2_1 = std::make_shared( "PauliX", std::vector{1}); auto ob2_2 = std::make_shared( @@ -167,8 +169,8 @@ TEMPLATE_TEST_CASE("[TensorProd]", "[Observables]", float, double) { SECTION("Constructing an observable with non-overlapping wires ") { auto ob1 = std::make_shared( - std::vector(16, ComplexT{0.0, 0.0}), - std::vector{0, 1}); + std::vector(4, ComplexT{0.0, 0.0}), + std::vector{1}); auto ob2_1 = std::make_shared( "PauliX", std::vector{2}); auto ob2_2 = std::make_shared( diff --git a/pennylane_lightning/lightning_tensor/_measurements.py b/pennylane_lightning/lightning_tensor/_measurements.py index 6b1ee3203f..f5149f0e61 100644 --- a/pennylane_lightning/lightning_tensor/_measurements.py +++ b/pennylane_lightning/lightning_tensor/_measurements.py @@ -74,6 +74,10 @@ def expval(self, measurementprocess: MeasurementProcess): if isinstance(measurementprocess.obs, qml.SparseHamiltonian): raise NotImplementedError("Sparse Hamiltonians are not supported.") + if isinstance(measurementprocess.obs, qml.Hermitian): + if len(measurementprocess.obs.wires) > 1: + raise ValueError("The number of Hermitian observables target wires should be 1.") + ob_serialized = QuantumScriptSerializer( self._tensornet.device_name, self.dtype == np.complex64 )._ob(measurementprocess.obs) diff --git a/tests/test_expval.py b/tests/test_expval.py index 3063b91c84..dfdc2a72d8 100644 --- a/tests/test_expval.py +++ b/tests/test_expval.py @@ -163,7 +163,7 @@ def circuit(): circ_def = qml.QNode(circuit, dev_def) assert np.allclose(circ(), circ_def(), tol) - @pytest.mark.parametrize("n_wires", range(1, 7 if device_name != "lightning.tensor" else 5)) + @pytest.mark.parametrize("n_wires", range(1, 7)) def test_hermitian_expectation(self, n_wires, theta, phi, qubit_device, tol): """Test that Hermitian expectation value is correct""" n_qubits = 7 @@ -185,14 +185,26 @@ def test_hermitian_expectation(self, n_wires, theta, phi, qubit_device, tol): def circuit(): if device_name != "lightning.tensor": qml.StatePrep(init_state, wires=range(n_qubits)) - qml.RY(theta, wires=[0]) + qml.RX(theta, wires=[0]) qml.RY(phi, wires=[1]) + qml.RX(theta, wires=[2]) + qml.RY(phi, wires=[3]) + qml.RX(theta, wires=[4]) + qml.RY(phi, wires=[5]) + qml.RX(theta, wires=[6]) qml.CNOT(wires=[0, 1]) return qml.expval(obs) circ = qml.QNode(circuit, dev) circ_def = qml.QNode(circuit, dev_def) - assert np.allclose(circ(), circ_def(), tol) + if device_name == "lightning.tensor" and n_wires > 1: + with pytest.raises( + ValueError, + match="The number of Hermitian observables target wires should be 1.", + ): + assert np.allclose(circ(), circ_def(), tol) + else: + assert np.allclose(circ(), circ_def(), tol) @pytest.mark.parametrize( diff --git a/tests/test_measurements.py b/tests/test_measurements.py index 318223fcd8..927354dd19 100644 --- a/tests/test_measurements.py +++ b/tests/test_measurements.py @@ -311,14 +311,18 @@ def circuit(): ( [ qml.PauliX(0) @ qml.PauliZ(1), - qml.Hermitian( - [ - [1.0, 0.0, 0.0, 0.0], - [0.0, 3.0, 0.0, 0.0], - [0.0, 0.0, -1.0, 1.0], - [0.0, 0.0, 1.0, -2.0], - ], - wires=[0, 1], + ( + qml.Hermitian( + [ + [1.0, 0.0, 0.0, 0.0], + [0.0, 3.0, 0.0, 0.0], + [0.0, 0.0, -1.0, 1.0], + [0.0, 0.0, 1.0, -2.0], + ], + wires=[0, 1], + ) + if device_name != "lightning.tensor" + else qml.Hermitian([[1.0, 0.0], [0.0, 1.0]], wires=[0]) ), ], [0.3, 1.0], diff --git a/tests/test_serialize.py b/tests/test_serialize.py index 465622e9c9..5de35aa659 100644 --- a/tests/test_serialize.py +++ b/tests/test_serialize.py @@ -121,6 +121,8 @@ def test_wrong_device_name(): qml.Hermitian(np.eye(2), wires=0) @ qml.Hermitian(np.eye(2), wires=1) @ qml.Projector([0], wires=1) + if device_name != "lightning.tensor" + else qml.Hermitian(np.eye(2), wires=0) ), HermitianObsC128, ), @@ -192,12 +194,19 @@ def test_prod_return_with_overlapping_wires(self, use_csingle, wires_map): hermitian_obs = HermitianObsC64 if use_csingle else HermitianObsC128 c_dtype = np.complex64 if use_csingle else np.complex128 mat = obs.matrix().ravel().astype(c_dtype) - - s, _ = QuantumScriptSerializer(device_name, use_csingle).serialize_observables( - tape, wires_map - ) - s_expected = hermitian_obs(mat, [0, 1, 2]) - assert s[0] == s_expected + if device_name == "lightning.tensor": + with pytest.raises( + ValueError, match="The number of Hermitian observables target wires should be 1." + ): + s, _ = QuantumScriptSerializer(device_name, use_csingle).serialize_observables( + tape, wires_map + ) + else: + s, _ = QuantumScriptSerializer(device_name, use_csingle).serialize_observables( + tape, wires_map + ) + s_expected = hermitian_obs(mat, [0, 1, 2]) + assert s[0] == s_expected @pytest.mark.parametrize("use_csingle", [True, False]) @pytest.mark.parametrize("wires_map", [wires_dict, None]) @@ -209,24 +218,55 @@ def test_hermitian_return(self, use_csingle, wires_map): hermitian_obs = HermitianObsC64 if use_csingle else HermitianObsC128 c_dtype = np.complex64 if use_csingle else np.complex128 - s, _ = QuantumScriptSerializer(device_name, use_csingle).serialize_observables( - tape, wires_map - ) - s_expected = hermitian_obs( - np.array( - [1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0], - dtype=c_dtype, - ), - [0, 1], - ) - assert s[0] == s_expected + if device_name == "lightning.tensor": + with pytest.raises( + ValueError, match="The number of Hermitian observables target wires should be 1." + ): + s, _ = QuantumScriptSerializer(device_name, use_csingle).serialize_observables( + tape, wires_map + ) + else: + s, _ = QuantumScriptSerializer(device_name, use_csingle).serialize_observables( + tape, wires_map + ) + s_expected = hermitian_obs( + np.array( + [ + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + ], + dtype=c_dtype, + ), + [0, 1], + ) + assert s[0] == s_expected @pytest.mark.parametrize("use_csingle", [True, False]) @pytest.mark.parametrize("wires_map", [wires_dict, None]) def test_hermitian_tensor_return(self, use_csingle, wires_map): """Test expected serialization for a Hermitian return""" with qml.tape.QuantumTape() as tape: - qml.expval(qml.Hermitian(np.eye(4), wires=[0, 1]) @ qml.Hermitian(np.eye(2), wires=[2])) + qml.expval( + qml.Hermitian( + np.eye(2 if device_name == "lightning.tensor" else 4), + wires=[1] if device_name == "lightning.tensor" else [0, 1], + ) + @ qml.Hermitian(np.eye(2), wires=[2]) + ) c_dtype = np.complex64 if use_csingle else np.complex128 tensor_prod_obs = TensorProdObsC64 if use_csingle else TensorProdObsC128 @@ -237,7 +277,10 @@ def test_hermitian_tensor_return(self, use_csingle, wires_map): s_expected = tensor_prod_obs( [ - hermitian_obs(np.eye(4, dtype=c_dtype).ravel(), [0, 1]), + hermitian_obs( + np.eye(2 if device_name == "lightning.tensor" else 4, dtype=c_dtype).ravel(), + [1] if device_name == "lightning.tensor" else [0, 1], + ), hermitian_obs(np.eye(2, dtype=c_dtype).ravel(), [2]), ] ) @@ -249,7 +292,13 @@ def test_hermitian_tensor_return(self, use_csingle, wires_map): def test_mixed_tensor_return(self, use_csingle, wires_map): """Test expected serialization for a mixture of Hermitian and Pauli return""" with qml.tape.QuantumTape() as tape: - qml.expval(qml.Hermitian(np.eye(4), wires=[0, 1]) @ qml.PauliY(2)) + qml.expval( + qml.Hermitian( + np.eye(2 if device_name == "lightning.tensor" else 4), + wires=[0] if device_name == "lightning.tensor" else [0, 1], + ) + @ qml.PauliY(2) + ) c_dtype = np.complex64 if use_csingle else np.complex128 tensor_prod_obs = TensorProdObsC64 if use_csingle else TensorProdObsC128 @@ -261,22 +310,48 @@ def test_mixed_tensor_return(self, use_csingle, wires_map): ) s_expected = tensor_prod_obs( - [hermitian_obs(np.eye(4, dtype=c_dtype).ravel(), [0, 1]), named_obs("PauliY", [2])] + [ + hermitian_obs( + np.eye(2 if device_name == "lightning.tensor" else 4, dtype=c_dtype).ravel(), + [0] if device_name == "lightning.tensor" else [0, 1], + ), + named_obs("PauliY", [2]), + ] ) assert s[0] == s_expected + @pytest.mark.parametrize( + "test_hermobs0", + [ + ( + qml.Hermitian(np.eye(4), wires=[0, 1]) + if device_name != "lightning.tensor" + else qml.Hermitian(np.eye(2), wires=[0]) + ) + ], + ) + @pytest.mark.parametrize( + "test_hermobs1", + [ + ( + qml.Hermitian(np.ones((8, 8)), wires=range(3)) + if device_name != "lightning.tensor" + else qml.Hermitian(np.ones((2, 2)), wires=[0]) + ) + ], + ) @pytest.mark.parametrize("use_csingle", [True, False]) @pytest.mark.parametrize("wires_map", [wires_dict, None]) - def test_hamiltonian_return(self, use_csingle, wires_map): + def test_hamiltonian_return(self, test_hermobs0, test_hermobs1, use_csingle, wires_map): """Test expected serialization for a Hamiltonian return""" ham = qml.Hamiltonian( [0.3, 0.5, 0.4], [ - qml.Hermitian(np.eye(4), wires=[0, 1]) @ qml.PauliY(2), + (test_hermobs0 @ qml.PauliY(2)), qml.PauliX(0) @ qml.PauliY(2), - qml.Hermitian(np.ones((8, 8)), wires=range(3)), + (test_hermobs1), ], ) @@ -299,29 +374,57 @@ def test_hamiltonian_return(self, use_csingle, wires_map): [ tensor_prod_obs( [ - hermitian_obs(np.eye(4, dtype=c_dtype).ravel(), [0, 1]), + ( + hermitian_obs(np.eye(4, dtype=c_dtype).ravel(), [0, 1]) + if device_name != "lightning.tensor" + else hermitian_obs(np.eye(2, dtype=c_dtype).ravel(), [0]) + ), named_obs("PauliY", [2]), ] ), tensor_prod_obs([named_obs("PauliX", [0]), named_obs("PauliY", [2])]), - hermitian_obs(np.ones(64, dtype=c_dtype), [0, 1, 2]), + ( + hermitian_obs(np.ones(64, dtype=c_dtype), [0, 1, 2]) + if device_name != "lightning.tensor" + else hermitian_obs(np.ones(4, dtype=c_dtype), [0]) + ), ], ) assert s[0] == s_expected + @pytest.mark.parametrize( + "test_hermobs0", + [ + ( + qml.Hermitian(np.eye(4), wires=[0, 1]) + if device_name != "lightning.tensor" + else qml.Hermitian(np.eye(2), wires=[0]) + ) + ], + ) + @pytest.mark.parametrize( + "test_hermobs1", + [ + ( + qml.Hermitian(np.ones((8, 8)), wires=range(3)) + if device_name != "lightning.tensor" + else qml.Hermitian(np.ones((2, 2)), wires=[0]) + ) + ], + ) @pytest.mark.parametrize("use_csingle", [True, False]) @pytest.mark.parametrize("wires_map", [wires_dict, None]) - def test_hamiltonian_tensor_return(self, use_csingle, wires_map): + def test_hamiltonian_tensor_return(self, test_hermobs0, test_hermobs1, use_csingle, wires_map): """Test expected serialization for a tensor Hamiltonian return""" with qml.tape.QuantumTape() as tape: ham = qml.Hamiltonian( [0.3, 0.5, 0.4], [ - qml.Hermitian(np.eye(4), wires=[0, 1]) @ qml.PauliY(2), + (test_hermobs0 @ qml.PauliY(2)), qml.PauliX(0) @ qml.PauliY(2), - qml.Hermitian(np.ones((8, 8)), wires=range(3)), + (test_hermobs1), ], ) qml.expval(ham @ qml.PauliZ(3)) @@ -345,7 +448,11 @@ def test_hamiltonian_tensor_return(self, use_csingle, wires_map): [ tensor_prod_obs( [ - hermitian_obs(np.eye(4, dtype=c_dtype).ravel(), [0, 1]), + ( + hermitian_obs(np.eye(4, dtype=c_dtype).ravel(), [0, 1]) + if device_name != "lightning.tensor" + else hermitian_obs(np.eye(2, dtype=c_dtype).ravel(), [0]) + ), named_obs("PauliY", [2]), named_obs("PauliZ", [3]), ] @@ -359,7 +466,11 @@ def test_hamiltonian_tensor_return(self, use_csingle, wires_map): ), tensor_prod_obs( [ - hermitian_obs(np.ones(64, dtype=c_dtype), [0, 1, 2]), + ( + hermitian_obs(np.ones(64, dtype=c_dtype), [0, 1, 2]) + if device_name != "lightning.tensor" + else hermitian_obs(np.ones(4, dtype=c_dtype), [0]) + ), named_obs("PauliZ", [3]), ] ), @@ -368,22 +479,49 @@ def test_hamiltonian_tensor_return(self, use_csingle, wires_map): assert s[0] == s_expected + @pytest.mark.parametrize( + "test_hermobs0", + [ + ( + qml.Hermitian(np.eye(4), wires=[0, 1]) + if device_name != "lightning.tensor" + else qml.Hermitian(np.eye(2), wires=[0]) + ) + ], + ) + @pytest.mark.parametrize( + "test_hermobs1", + [ + ( + qml.Hermitian(np.ones((8, 8)), wires=range(3)) + if device_name != "lightning.tensor" + else qml.Hermitian(np.ones((2, 2)), wires=[0]) + ) + ], + ) @pytest.mark.parametrize("use_csingle", [True, False]) @pytest.mark.parametrize("wires_map", [wires_dict, None]) - def test_hamiltonian_mix_return(self, use_csingle, wires_map): + def test_hamiltonian_mix_return(self, test_hermobs0, test_hermobs1, use_csingle, wires_map): """Test expected serialization for a Hamiltonian return""" ham1 = qml.Hamiltonian( [0.3, 0.5, 0.4], [ - qml.Hermitian(np.eye(4), wires=[0, 1]) @ qml.PauliY(2), + (test_hermobs0 @ qml.PauliY(2)), qml.PauliX(0) @ qml.PauliY(2), - qml.Hermitian(np.ones((8, 8)), wires=range(3)), + (test_hermobs1), ], ) ham2 = qml.Hamiltonian( [0.7, 0.3], - [qml.PauliX(0) @ qml.Hermitian(np.eye(4), wires=[1, 2]), qml.PauliY(0) @ qml.PauliX(2)], + [ + ( + qml.PauliX(0) @ qml.Hermitian(np.eye(4), wires=[1, 2]) + if device_name != "lightning.tensor" + else qml.PauliX(0) @ qml.Hermitian(np.eye(2), wires=[1]) + ), + qml.PauliY(0) @ qml.PauliX(2), + ], ) with qml.tape.QuantumTape() as tape: @@ -405,12 +543,20 @@ def test_hamiltonian_mix_return(self, use_csingle, wires_map): [ tensor_prod_obs( [ - hermitian_obs(np.eye(4, dtype=c_dtype).ravel(), [0, 1]), + ( + hermitian_obs(np.eye(4, dtype=c_dtype).ravel(), [0, 1]) + if device_name != "lightning.tensor" + else hermitian_obs(np.eye(2, dtype=c_dtype).ravel(), [0]) + ), named_obs("PauliY", [2]), ] ), tensor_prod_obs([named_obs("PauliX", [0]), named_obs("PauliY", [2])]), - hermitian_obs(np.ones(64, dtype=c_dtype), [0, 1, 2]), + ( + hermitian_obs(np.ones(64, dtype=c_dtype), [0, 1, 2]) + if device_name != "lightning.tensor" + else hermitian_obs(np.ones(4, dtype=c_dtype), [0]) + ), ], ) s_expected2 = hamiltonian_obs( @@ -419,7 +565,11 @@ def test_hamiltonian_mix_return(self, use_csingle, wires_map): tensor_prod_obs( [ named_obs("PauliX", [0]), - hermitian_obs(np.eye(4, dtype=c_dtype).ravel(), [1, 2]), + ( + hermitian_obs(np.eye(4, dtype=c_dtype).ravel(), [1, 2]) + if device_name != "lightning.tensor" + else hermitian_obs(np.eye(2, dtype=c_dtype).ravel(), [1]) + ), ] ), tensor_prod_obs([named_obs("PauliY", [0]), named_obs("PauliX", [2])]),