Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove Tensor and Hamiltonian in python frontend for lightning #994

Merged
merged 15 commits into from
Nov 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@
)

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 @@
)

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 @@
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.")

Check warning on line 311 in pennylane_lightning/core/_measurements_base.py

View check run for this annotation

Codecov / codecov/patch

pennylane_lightning/core/_measurements_base.py#L310-L311

Added lines #L310 - L311 were not covered by tests
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):

Check warning on line 316 in pennylane_lightning/core/_measurements_base.py

View check run for this annotation

Codecov / codecov/patch

pennylane_lightning/core/_measurements_base.py#L316

Added line #L316 was not covered by tests
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 @@
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 _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 @@
"""

if self._use_mpi:
Hmat = Hamiltonian([1.0], [Identity(0)]).sparse_matrix()
Hmat = Identity(0).sparse_matrix()

Check warning on line 263 in pennylane_lightning/core/_serialize.py

View check run for this annotation

Codecov / codecov/patch

pennylane_lightning/core/_serialize.py#L263

Added line #L263 was not covered by tests
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 @@
"""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

Check warning on line 27 in pennylane_lightning/core/lightning_base.py

View check run for this annotation

Codecov / codecov/patch

pennylane_lightning/core/lightning_base.py#L27

Added line #L27 was not covered by tests
from pennylane.ops import Prod, Projector, SProd, Sum
from pennylane.wires import Wires

Expand Down Expand Up @@ -178,9 +178,7 @@
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

Check warning on line 181 in pennylane_lightning/core/lightning_base.py

View check run for this annotation

Codecov / codecov/patch

pennylane_lightning/core/lightning_base.py#L181

Added line #L181 was not covered by tests

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 @@
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):

Check warning on line 320 in pennylane_lightning/core/lightning_base.py

View check run for this annotation

Codecov / codecov/patch

pennylane_lightning/core/lightning_base.py#L320

Added line #L320 was not covered by tests
raise qml.QuantumFunctionError(
"Adjoint differentiation method does not support the Projector observable"
)

elif isinstance(observable, SProd):
if isinstance(observable, SProd):

Check warning on line 325 in pennylane_lightning/core/lightning_base.py

View check run for this annotation

Codecov / codecov/patch

pennylane_lightning/core/lightning_base.py#L325

Added line #L325 was not covered by tests
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
Loading