Skip to content

Commit

Permalink
Remove Tensor and Hamiltonian in python frontend for lightning (#994)
Browse files Browse the repository at this point in the history
We are removing the Tensor and Hamiltonian classes, so we should remove
their (minimal) remaining references in the Lightning python code.

Blocks running CI for the [PL PR
#6548](PennyLaneAI/pennylane#6548) to remove
legacy_opmath, since every test that uses `lightning.qubit` fails with
Tensor removed.

---------

Co-authored-by: ringo-but-quantum <github-ringo-but-quantum@xanadu.ai>
  • Loading branch information
lillian542 and ringo-but-quantum authored Nov 13, 2024
1 parent 4659093 commit 94577a8
Show file tree
Hide file tree
Showing 18 changed files with 27 additions and 121 deletions.
3 changes: 3 additions & 0 deletions .github/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@

### Breaking changes

* Handling for the legacy operator arithmetic (the `Hamiltonian` and `Tensor` classes in PennyLane) is removed.
[(#994)](https://github.com/PennyLaneAI/pennylane-lightning/pull/994)

### Improvements

* Unify excitation gates memory layout to row-major for both LGPU and LT.
Expand Down
34 changes: 6 additions & 28 deletions pennylane_lightning/core/_measurements_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
StateMeasurement,
VarianceMP,
)
from pennylane.ops import Hamiltonian, SparseHamiltonian, Sum
from pennylane.ops import SparseHamiltonian, Sum
from pennylane.tape import QuantumScript
from pennylane.typing import Result, TensorLike
from pennylane.wires import Wires
Expand Down Expand Up @@ -117,7 +117,7 @@ def expval(self, measurementprocess: MeasurementProcess):
)

if (
isinstance(measurementprocess.obs, (qml.ops.Hamiltonian, qml.Hermitian))
isinstance(measurementprocess.obs, qml.Hermitian)
or (measurementprocess.obs.arithmetic_depth > 0)
or isinstance(measurementprocess.obs.name, List)
):
Expand Down Expand Up @@ -176,7 +176,7 @@ def var(self, measurementprocess: MeasurementProcess):
)

if (
isinstance(measurementprocess.obs, (qml.ops.Hamiltonian, qml.Hermitian))
isinstance(measurementprocess.obs, qml.Hermitian)
or (measurementprocess.obs.arithmetic_depth > 0)
or isinstance(measurementprocess.obs.name, List)
):
Expand Down Expand Up @@ -307,15 +307,13 @@ def measure_with_samples(
raise TypeError(
"ExpectationMP/VarianceMP(SparseHamiltonian) cannot be computed with samples."
)
if isinstance(group[0], VarianceMP) and isinstance(group[0].obs, (Hamiltonian, Sum)):
raise TypeError("VarianceMP(Hamiltonian/Sum) cannot be computed with samples.")
if isinstance(group[0], VarianceMP) and isinstance(group[0].obs, Sum):
raise TypeError("VarianceMP(Sum) cannot be computed with samples.")
if isinstance(group[0], (ClassicalShadowMP, ShadowExpvalMP)):
raise TypeError(
"ExpectationMP(ClassicalShadowMP, ShadowExpvalMP) cannot be computed with samples."
)
if isinstance(group[0], ExpectationMP) and isinstance(group[0].obs, Hamiltonian):
all_res.extend(self._measure_hamiltonian_with_samples(group, shots))
elif isinstance(group[0], ExpectationMP) and isinstance(group[0].obs, Sum):
if isinstance(group[0], ExpectationMP) and isinstance(group[0].obs, Sum):
all_res.extend(self._measure_sum_with_samples(group, shots))
else:
all_res.extend(self._measure_with_samples_diagonalizing_gates(group, shots))
Expand Down Expand Up @@ -372,26 +370,6 @@ def _measure_with_samples_diagonalizing_gates(
TensorLike[Any]: Sample measurement results
"""

def _measure_hamiltonian_with_samples(
self,
mp: List[SampleMeasurement],
shots: Shots,
):
# the list contains only one element based on how we group measurements
mp = mp[0]

# if the measurement process involves a Hamiltonian, measure each
# of the terms separately and sum
def _sum_for_single_shot(s):
results = self.measure_with_samples(
[ExpectationMP(t) for t in mp.obs.terms()[1]],
s,
)
return sum(c * res for c, res in zip(mp.obs.terms()[0], results))

unsqueezed_results = tuple(_sum_for_single_shot(type(shots)(s)) for s in shots)
return [unsqueezed_results] if shots.has_partitioned_shots else [unsqueezed_results[0]]

def _measure_sum_with_samples(
self,
mp: List[SampleMeasurement],
Expand Down
12 changes: 4 additions & 8 deletions pennylane_lightning/core/_serialize.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,7 @@
matrix,
)
from pennylane.math import unwrap
from pennylane.operation import Tensor
from pennylane.ops import Hamiltonian, LinearCombination, Prod, SProd, Sum
from pennylane.ops import LinearCombination, Prod, SProd, Sum
from pennylane.tape import QuantumTape

NAMED_OBS = (Identity, PauliX, PauliY, PauliZ, Hadamard)
Expand Down Expand Up @@ -211,8 +210,7 @@ def _hermitian_ob(self, observable, wires_map: dict = None):

def _tensor_ob(self, observable, wires_map: dict = None):
"""Serialize a tensor observable"""
obs = observable.obs if isinstance(observable, Tensor) else observable.operands
return self.tensor_obs([self._ob(o, wires_map) for o in obs])
return self.tensor_obs([self._ob(o, wires_map) for o in observable.operands])

def _chunk_ham_terms(self, coeffs, ops, split_num: int = 1) -> List:
"Create split_num sub-Hamiltonians from a single high term-count Hamiltonian"
Expand Down Expand Up @@ -262,7 +260,7 @@ def _sparse_hamiltonian(self, observable, wires_map: dict = None):
"""

if self._use_mpi:
Hmat = Hamiltonian([1.0], [Identity(0)]).sparse_matrix()
Hmat = Identity(0).sparse_matrix()
H_sparse = SparseHamiltonian(Hmat, wires=range(1))
spm = H_sparse.sparse_matrix()
# Only root 0 needs the overall sparse matrix data
Expand Down Expand Up @@ -323,11 +321,9 @@ def _ob(self, observable, wires_map: dict = None):
"""Serialize a :class:`pennylane.operation.Observable` into an Observable."""
if isinstance(observable, NAMED_OBS):
return self._named_obs(observable, wires_map)
if isinstance(observable, Hamiltonian):
return self._hamiltonian(observable, wires_map)
if observable.pauli_rep is not None:
return self._pauli_sentence(observable.pauli_rep, wires_map)
if isinstance(observable, (Tensor, Prod)):
if isinstance(observable, Prod):
if isinstance(observable, Prod) and observable.has_overlapping_wires:
return self._hermitian_ob(observable, wires_map)
return self._tensor_ob(observable, wires_map)
Expand Down
3 changes: 2 additions & 1 deletion pennylane_lightning/core/_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@
Version number (major.minor.patch[-label])
"""

__version__ = "0.40.0-dev7"

__version__ = "0.40.0-dev8"
16 changes: 4 additions & 12 deletions pennylane_lightning/core/lightning_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
from pennylane import BasisState, StatePrep
from pennylane.devices import QubitDevice
from pennylane.measurements import Expectation, MeasurementProcess, State
from pennylane.operation import Operation, Tensor
from pennylane.operation import Operation
from pennylane.ops import Prod, Projector, SProd, Sum
from pennylane.wires import Wires

Expand Down Expand Up @@ -178,9 +178,7 @@ def probability(self, wires=None, shot_range=None, bin_size=None):
def _get_diagonalizing_gates(self, circuit: qml.tape.QuantumTape) -> List[Operation]:
# pylint: disable=no-member, protected-access
def skip_diagonalizing(obs):
return isinstance(obs, qml.Hamiltonian) or (
isinstance(obs, qml.ops.Sum) and obs._pauli_rep is not None
)
return isinstance(obs, qml.ops.Sum) and obs._pauli_rep is not None

meas_filtered = list(
filter(lambda m: m.obs is None or not skip_diagonalizing(m.obs), circuit.measurements)
Expand Down Expand Up @@ -319,18 +317,12 @@ def _assert_adjdiff_no_projectors(observable):
Raises:
~pennylane.QuantumFunctionError: if a ``Projector`` is found.
"""
if isinstance(observable, Tensor):
if any(isinstance(o, Projector) for o in observable.non_identity_obs):
raise qml.QuantumFunctionError(
"Adjoint differentiation method does not support the Projector observable"
)

elif isinstance(observable, Projector):
if isinstance(observable, Projector):
raise qml.QuantumFunctionError(
"Adjoint differentiation method does not support the Projector observable"
)

elif isinstance(observable, SProd):
if isinstance(observable, SProd):
LightningBase._assert_adjdiff_no_projectors(observable.base)

elif isinstance(observable, (Sum, Prod)):
Expand Down
8 changes: 1 addition & 7 deletions pennylane_lightning/lightning_gpu/lightning_gpu.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
validate_observables,
)
from pennylane.measurements import MidMeasureMP
from pennylane.operation import DecompositionUndefinedError, Operator, Tensor
from pennylane.operation import DecompositionUndefinedError, Operator
from pennylane.ops import Prod, SProd, Sum
from pennylane.tape import QuantumScript
from pennylane.transforms.core import TransformProgram
Expand Down Expand Up @@ -169,7 +169,6 @@
"PauliZ",
"Hadamard",
"SparseHamiltonian",
"Hamiltonian",
"LinearCombination",
"Hermitian",
"Identity",
Expand Down Expand Up @@ -204,11 +203,6 @@ def adjoint_observables(obs: Operator) -> bool:
if isinstance(obs, qml.Projector):
return False

if isinstance(obs, Tensor):
if any(isinstance(o, qml.Projector) for o in obs.non_identity_obs):
return False
return True

if isinstance(obs, SProd):
return adjoint_observables(obs.base)

Expand Down
8 changes: 1 addition & 7 deletions pennylane_lightning/lightning_kokkos/lightning_kokkos.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
validate_observables,
)
from pennylane.measurements import MidMeasureMP
from pennylane.operation import DecompositionUndefinedError, Operator, Tensor
from pennylane.operation import DecompositionUndefinedError, Operator
from pennylane.ops import Prod, SProd, Sum
from pennylane.tape import QuantumScript
from pennylane.transforms.core import TransformProgram
Expand Down Expand Up @@ -138,7 +138,6 @@
"Identity",
"Projector",
"SparseHamiltonian",
"Hamiltonian",
"LinearCombination",
"Sum",
"SProd",
Expand Down Expand Up @@ -174,11 +173,6 @@ def adjoint_observables(obs: Operator) -> bool:
if isinstance(obs, qml.Projector):
return False

if isinstance(obs, Tensor):
if any(isinstance(o, qml.Projector) for o in obs.non_identity_obs):
return False
return True

if isinstance(obs, SProd):
return adjoint_observables(obs.base)

Expand Down
8 changes: 1 addition & 7 deletions pennylane_lightning/lightning_qubit/lightning_qubit.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
validate_observables,
)
from pennylane.measurements import MidMeasureMP
from pennylane.operation import DecompositionUndefinedError, Operator, Tensor
from pennylane.operation import DecompositionUndefinedError, Operator
from pennylane.ops import Prod, SProd, Sum
from pennylane.tape import QuantumScript
from pennylane.transforms.core import TransformProgram
Expand Down Expand Up @@ -159,7 +159,6 @@
"Identity",
"Projector",
"SparseHamiltonian",
"Hamiltonian",
"LinearCombination",
"Sum",
"SProd",
Expand Down Expand Up @@ -200,11 +199,6 @@ def adjoint_observables(obs: Operator) -> bool:
if isinstance(obs, qml.Projector):
return False

if isinstance(obs, Tensor):
if any(isinstance(o, qml.Projector) for o in obs.non_identity_obs):
return False
return True

if isinstance(obs, SProd):
return adjoint_observables(obs.base)

Expand Down
1 change: 0 additions & 1 deletion pennylane_lightning/lightning_tensor/lightning_tensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,6 @@
"Hadamard",
"Hermitian",
"Identity",
"Hamiltonian",
"LinearCombination",
"Sum",
"SProd",
Expand Down
28 changes: 0 additions & 28 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,34 +206,6 @@ def _statevector(num_wires):
return _statevector


#######################################################################
# Fixtures for testing under new and old opmath


@pytest.fixture(scope="function")
def use_legacy_opmath():
with qml.operation.disable_new_opmath_cm() as cm:
yield cm


@pytest.fixture(scope="function")
def use_new_opmath():
with qml.operation.enable_new_opmath_cm() as cm:
yield cm


@pytest.fixture(
params=[qml.operation.disable_new_opmath_cm, qml.operation.enable_new_opmath_cm],
scope="function",
)
def use_legacy_and_new_opmath(request):
with request.param() as cm:
yield cm


#######################################################################


def validate_counts(shots, results1, results2):
"""Compares two counts.
Expand Down
2 changes: 0 additions & 2 deletions tests/lightning_qubit/test_adjoint_jacobian_class.py
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,6 @@ def test_multiple_rx_gradient_expval_hermitian(self, tol, lightning_sv):

assert np.allclose(expected, result, atol=tol, rtol=0)

@pytest.mark.usefixtures("use_legacy_and_new_opmath")
def test_multiple_rx_gradient_expval_hamiltonian(self, tol, lightning_sv):
"""Tests that the gradient of multiple RX gates in a circuit yields the correct result
with Hermitian observable
Expand Down Expand Up @@ -392,7 +391,6 @@ def calculate_vjp(statevector, tape, vector):

return LightningAdjointJacobian(statevector).calculate_vjp(tape, vector)

@pytest.mark.usefixtures("use_legacy_and_new_opmath")
def test_multiple_measurements(self, tol, lightning_sv):
"""Tests provides correct answer when provided multiple measurements."""
x, y, z = [0.5, 0.3, -0.7]
Expand Down
1 change: 0 additions & 1 deletion tests/lightning_qubit/test_jacobian_method.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,6 @@ def process_and_execute(statevector, tape, dy, execute_and_derivatives=False):
jac = device.vjp(tape, dy, statevector)
return results, jac

@pytest.mark.usefixtures("use_legacy_and_new_opmath")
@pytest.mark.parametrize("theta, phi", list(zip(THETA, PHI)))
@pytest.mark.parametrize(
"obs",
Expand Down
10 changes: 3 additions & 7 deletions tests/new_api/test_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,12 +132,12 @@ def test_accepted_observables(self):
@pytest.mark.parametrize(
"obs, expected",
[
(qml.operation.Tensor(qml.Projector([0], 0), qml.PauliZ(1)), False),
(qml.prod(qml.Projector([0], 0), qml.PauliZ(1)), False),
(qml.prod(qml.Projector([0], 0), qml.PauliZ(1)), False),
(qml.s_prod(1.5, qml.Projector([0], 0)), False),
(qml.sum(qml.Projector([0], 0), qml.Hadamard(1)), False),
(qml.sum(qml.prod(qml.Projector([0], 0), qml.Y(1)), qml.PauliX(1)), False),
(qml.operation.Tensor(qml.Y(0), qml.Z(1)), True),
(qml.prod(qml.Y(0), qml.Z(1)), True),
(qml.prod(qml.Y(0), qml.PauliZ(1)), True),
(qml.s_prod(1.5, qml.Y(1)), True),
(qml.sum(qml.Y(1), qml.Hadamard(1)), True),
Expand Down Expand Up @@ -429,7 +429,6 @@ def test_preprocess_state_prep_middle_op_decomposition(self, op, decomp_depth):
)
assert qml.equal(new_tape, expected_tape)

@pytest.mark.usefixtures("use_legacy_and_new_opmath")
@pytest.mark.parametrize("theta, phi", list(zip(THETA, PHI)))
@pytest.mark.parametrize(
"mp",
Expand All @@ -443,7 +442,7 @@ def test_preprocess_state_prep_middle_op_decomposition(self, op, decomp_depth):
qml.expval(qml.Hamiltonian([-0.5, 1.5], [qml.Y(1), qml.X(1)])),
qml.expval(2.5 * qml.Z(0)),
qml.expval(qml.Z(0) @ qml.X(1)),
qml.expval(qml.operation.Tensor(qml.Z(0), qml.X(1))),
qml.expval(qml.prod(qml.Z(0), qml.X(1))),
qml.expval(
qml.SparseHamiltonian(
qml.Hamiltonian([-1.0, 1.5], [qml.Z(1), qml.X(1)]).sparse_matrix(
Expand Down Expand Up @@ -483,7 +482,6 @@ def test_execute_single_measurement(self, theta, phi, mp, dev):
expected = self.calculate_reference(qs)[0]
assert np.allclose(res, expected)

@pytest.mark.usefixtures("use_legacy_and_new_opmath")
@pytest.mark.parametrize("theta, phi", list(zip(THETA, PHI)))
@pytest.mark.parametrize(
"mp1",
Expand Down Expand Up @@ -652,7 +650,6 @@ def test_supports_derivatives(self, dev, config, tape, expected, batch_obs):
"""Test that supports_derivative returns the correct boolean value."""
assert dev.supports_derivatives(config, tape) == expected

@pytest.mark.usefixtures("use_legacy_and_new_opmath")
@pytest.mark.parametrize("theta, phi", list(zip(THETA, PHI)))
@pytest.mark.parametrize(
"obs",
Expand Down Expand Up @@ -1038,7 +1035,6 @@ def test_supports_vjp(self, dev, config, tape, expected, batch_obs):
"""Test that supports_vjp returns the correct boolean value."""
assert dev.supports_vjp(config, tape) == expected

@pytest.mark.usefixtures("use_legacy_and_new_opmath")
@pytest.mark.parametrize("theta, phi", list(zip(THETA, PHI)))
@pytest.mark.parametrize(
"obs",
Expand Down
Loading

0 comments on commit 94577a8

Please sign in to comment.