Skip to content

Commit

Permalink
Add LTensor Hermitian wire length check (#806)
Browse files Browse the repository at this point in the history
### 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 <[email protected]>
  • Loading branch information
multiphaseCFD and ringo-but-quantum authored Jul 18, 2024
1 parent c81ce7d commit 356bdce
Show file tree
Hide file tree
Showing 9 changed files with 237 additions and 57 deletions.
3 changes: 3 additions & 0 deletions .github/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
2 changes: 2 additions & 0 deletions pennylane_lightning/core/_serialize.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
2 changes: 1 addition & 1 deletion pennylane_lightning/core/_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,4 @@
Version number (major.minor.patch[-label])
"""

__version__ = "0.38.0-dev10"
__version__ = "0.38.0-dev11"
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

#include "Constant.hpp"
#include "ConstantUtil.hpp" // lookup
#include "Error.hpp"
#include "Util.hpp"

#include "cuda_helpers.hpp"
Expand Down Expand Up @@ -241,6 +242,8 @@ class HermitianObsTNCuda : public ObservableTNCuda<TensorNetT> {
*/
HermitianObsTNCuda(MatrixT matrix, std::vector<std::size_t> 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});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,16 +86,18 @@ TEMPLATE_TEST_CASE("[Hermitian]", "[Observables]", float, double) {
using HermitianObsT = HermitianObsTNCuda<TensorNetT>;

SECTION("HermitianObs only accepts correct arguments") {
auto ob1 =
HermitianObsT{std::vector<ComplexT>{0.0, 0.0, 0.0, 0.0}, {0}};
auto ob2 = HermitianObsT{std::vector<ComplexT>(16, ComplexT{}), {0, 1}};
REQUIRE_THROWS_AS(
HermitianObsT(std::vector<ComplexT>{0.0, 0.0, 0.0}, {0}),
LightningException);
REQUIRE_THROWS_AS(
HermitianObsT(std::vector<ComplexT>{0.0, 0.0, 0.0, 0.0, 0.0},
{0, 1}),
LightningException);
REQUIRE_THROWS_WITH(
HermitianObsT(std::vector<ComplexT>(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") {
Expand Down Expand Up @@ -153,8 +155,8 @@ TEMPLATE_TEST_CASE("[TensorProd]", "[Observables]", float, double) {

SECTION("Overlapping wires throw an exception") {
auto ob1 = std::make_shared<HermitianObsT>(
std::vector<ComplexT>(16, ComplexT{0.0, 0.0}),
std::vector<std::size_t>{0, 1});
std::vector<ComplexT>(4, ComplexT{0.0, 0.0}),
std::vector<std::size_t>{1});
auto ob2_1 = std::make_shared<NamedObsT>(
"PauliX", std::vector<std::size_t>{1});
auto ob2_2 = std::make_shared<NamedObsT>(
Expand All @@ -167,8 +169,8 @@ TEMPLATE_TEST_CASE("[TensorProd]", "[Observables]", float, double) {

SECTION("Constructing an observable with non-overlapping wires ") {
auto ob1 = std::make_shared<HermitianObsT>(
std::vector<ComplexT>(16, ComplexT{0.0, 0.0}),
std::vector<std::size_t>{0, 1});
std::vector<ComplexT>(4, ComplexT{0.0, 0.0}),
std::vector<std::size_t>{1});
auto ob2_1 = std::make_shared<NamedObsT>(
"PauliX", std::vector<std::size_t>{2});
auto ob2_2 = std::make_shared<NamedObsT>(
Expand Down
4 changes: 4 additions & 0 deletions pennylane_lightning/lightning_tensor/_measurements.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
18 changes: 15 additions & 3 deletions tests/test_expval.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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(
Expand Down
20 changes: 12 additions & 8 deletions tests/test_measurements.py
Original file line number Diff line number Diff line change
Expand Up @@ -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],
Expand Down
Loading

0 comments on commit 356bdce

Please sign in to comment.