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

Add shot expval in C++ layer #556

Merged
merged 58 commits into from
Nov 17, 2023
Merged
Show file tree
Hide file tree
Changes from 50 commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
8b61616
init commit with PauliX,Y,Z, Hardmard support
multiphaseCFD Nov 7, 2023
fba9221
add unit tests & sample
multiphaseCFD Nov 8, 2023
5c8c286
add PauliX, Y, Z & Hadamard support
multiphaseCFD Nov 9, 2023
56f02e8
move expval shots to base class
multiphaseCFD Nov 9, 2023
d9cacbc
Auto update version
github-actions[bot] Nov 9, 2023
b3a21d1
add MPI support
multiphaseCFD Nov 9, 2023
62b3714
add samples_to_counts to measurement base class
multiphaseCFD Nov 9, 2023
05cfb89
tidy up tests
multiphaseCFD Nov 9, 2023
ddfdb72
move tests to measurement base
multiphaseCFD Nov 9, 2023
44df765
move mpi tests to measurement base class
multiphaseCFD Nov 9, 2023
b3931e9
make format
multiphaseCFD Nov 9, 2023
3a52184
add python layer
multiphaseCFD Nov 10, 2023
d714e0e
add mpi tests
multiphaseCFD Nov 10, 2023
9d1a418
remove else
multiphaseCFD Nov 10, 2023
d9d7a7d
add tensor product support
multiphaseCFD Nov 12, 2023
5745f4f
add more tests for tensor prod obs shots
multiphaseCFD Nov 12, 2023
bcb46d1
make format
multiphaseCFD Nov 12, 2023
68f2865
add Hamiltonian support
multiphaseCFD Nov 14, 2023
bc58332
make format
multiphaseCFD Nov 14, 2023
5288491
quick fix
multiphaseCFD Nov 14, 2023
c7f5ea2
add hamiltonian tests
multiphaseCFD Nov 14, 2023
4b56805
add more py tests for tensorprod
multiphaseCFD Nov 14, 2023
f09342f
remove changes made in py layer
multiphaseCFD Nov 15, 2023
d13e9f2
revert more changes in py layer
multiphaseCFD Nov 15, 2023
9ef8dac
tidy up code
multiphaseCFD Nov 15, 2023
bd8fd5a
update measurement base and kokkos
multiphaseCFD Nov 15, 2023
15d8244
make format
multiphaseCFD Nov 15, 2023
1bc196f
refactor and add _sample_state method
multiphaseCFD Nov 15, 2023
e5b64a0
for loop sum to accumulate sum
multiphaseCFD Nov 15, 2023
6fda049
refactor and add _preprocess_state() method
multiphaseCFD Nov 15, 2023
2949500
add more unit tests for LK backend
multiphaseCFD Nov 16, 2023
70637dc
Merge branch 'master' into add_shot_expval
multiphaseCFD Nov 16, 2023
2cbd160
Auto update version
github-actions[bot] Nov 16, 2023
5595a8a
tidy up docstring
multiphaseCFD Nov 16, 2023
d41f2b2
remove samples() and samples2counts method
multiphaseCFD Nov 16, 2023
ebf80f0
fix typo
multiphaseCFD Nov 16, 2023
3238a08
quick fix
multiphaseCFD Nov 16, 2023
35065d1
update LQRaw object creation
multiphaseCFD Nov 16, 2023
264481f
fix for multiple backends
multiphaseCFD Nov 16, 2023
2d3cf11
update format
multiphaseCFD Nov 16, 2023
b12885d
format fix
multiphaseCFD Nov 16, 2023
ad1e11d
format update
multiphaseCFD Nov 16, 2023
288e01e
make format & trigger MPI CI
multiphaseCFD Nov 16, 2023
e08cb85
add changelog and codecov for expval(obs) method
multiphaseCFD Nov 16, 2023
2e495db
add shot_range coverage
multiphaseCFD Nov 16, 2023
0acc096
tidy up code
multiphaseCFD Nov 16, 2023
4f5f634
add more unit tests
multiphaseCFD Nov 16, 2023
fe042f6
fix typos
multiphaseCFD Nov 16, 2023
06ecd06
add MPI Hamiltonian tests for shots
multiphaseCFD Nov 17, 2023
602f16c
add HamBase tests for applyInPlaceShots
multiphaseCFD Nov 17, 2023
e839cf2
update based on comments
multiphaseCFD Nov 17, 2023
7408813
add more unit tests
multiphaseCFD Nov 17, 2023
00be785
move tests for non-distributed backends to base
multiphaseCFD Nov 17, 2023
6c4dfbd
move all unit tests into the base class
multiphaseCFD Nov 17, 2023
0d07d9c
move getTotalNumQubits to base and tidy up code
multiphaseCFD Nov 17, 2023
055d6b3
tidy up code
multiphaseCFD Nov 17, 2023
1a92ebf
revert changes in Test_MeasurementLQ
multiphaseCFD Nov 17, 2023
62ad2cc
fix typo
multiphaseCFD Nov 17, 2023
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 @@ -2,6 +2,9 @@

### New features since last release

* Add shots support for expectation value calculation for the observables (NamedObs, TensorProd and Hamiltonian) based on Pauli words, Identity and Hadamard in the C++ layer. All Lightning backends have this support now.
[(#556)](https://github.com/PennyLaneAI/pennylane-lightning/pull/556)

* `qml.QubitUnitary` operators can be included in a circuit differentiated with the adjoint method. Lightning handles circuits with arbitrary non-differentiable `qml.QubitUnitary` operators. 1,2-qubit `qml.QubitUnitary` operators with differentiable parameters can be differentiated using decomposition.
[(#540)] (https://github.com/PennyLaneAI/pennylane-lightning/pull/540)

Expand Down
13 changes: 0 additions & 13 deletions mpitests/test_expval.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,6 @@ class TestExpval:
def test_identity_expectation(self, theta, phi, tol):
"""Test that identity expectation value (i.e. the trace) is 1"""
dev = qml.device(device_name, mpi=True, wires=3)
if device_name == "lightning.gpu" and dev.R_DTYPE == np.float32:
pytest.skip("Skipped FP32 tests for expval in lightning.gpu")

O1 = qml.Identity(wires=[0])
O2 = qml.Identity(wires=[1])
Expand All @@ -49,9 +47,6 @@ def test_pauliz_expectation(self, theta, phi, tol):
"""Test that PauliZ expectation value is correct"""
dev = qml.device(device_name, mpi=True, wires=3)

if device_name == "lightning.gpu" and dev.R_DTYPE == np.float32:
pytest.skip("Skipped FP32 tests for expval in lightning.gpu")

O1 = qml.PauliZ(wires=[0])
O2 = qml.PauliZ(wires=[1])

Expand All @@ -67,9 +62,6 @@ def test_paulix_expectation(self, theta, phi, tol):
"""Test that PauliX expectation value is correct"""
dev = qml.device(device_name, mpi=True, wires=3)

if device_name == "lightning.gpu" and dev.R_DTYPE == np.float32:
pytest.skip("Skipped FP32 tests for expval in lightning.gpu")

O1 = qml.PauliX(wires=[0])
O2 = qml.PauliX(wires=[1])

Expand All @@ -89,9 +81,6 @@ def test_pauliy_expectation(self, theta, phi, tol):
"""Test that PauliY expectation value is correct"""
dev = qml.device(device_name, mpi=True, wires=3)

if device_name == "lightning.gpu" and dev.R_DTYPE == np.float32:
pytest.skip("Skipped FP32 tests for expval in lightning.gpu")

O1 = qml.PauliY(wires=[0])
O2 = qml.PauliY(wires=[1])

Expand Down Expand Up @@ -130,8 +119,6 @@ def test_hermitian_expectation(self, n_wires, theta, phi, tol):
n_qubits = 7
dev_def = qml.device("default.qubit", wires=n_qubits)
dev = qml.device(device_name, mpi=True, wires=n_qubits)
if device_name == "lightning.gpu" and dev.R_DTYPE == np.float32:
pytest.skip("Skipped FP32 tests for expval in lightning.gpu")
comm = MPI.COMM_WORLD

m = 2**n_wires
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.34.0-dev7"
__version__ = "0.34.0-dev8"
184 changes: 182 additions & 2 deletions pennylane_lightning/core/src/measurements/MeasurementsBase.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,13 @@
*/
#pragma once

#include <string>
#include <vector>

#include "Observables.hpp"

#include "CPUMemoryModel.hpp"

/// @cond DEV
namespace {
using namespace Pennylane::Observables;
Expand Down Expand Up @@ -111,6 +114,183 @@ template <class StateVectorT, class Derived> class MeasurementsBase {
auto generate_samples(size_t num_samples) -> std::vector<size_t> {
return static_cast<Derived *>(this)->generate_samples(num_samples);
};
};

} // namespace Pennylane::Measures
/**
* @brief Calculate the expectation value for a general Observable.
*
* @param obs Observable.
* @param num_shots Number of shots used to generate samples
* @param shot_range The range of samples to use. All samples are used
* by default.
*
* @return Expectation value with respect to the given observable.
*/
auto expval(const Observable<StateVectorT> &obs, const size_t &num_shots,
const std::vector<size_t> &shot_range = {}) -> PrecisionT {
PrecisionT result = 0;

if (obs.getObsName().find("SparseHamiltonian") != std::string::npos) {
PL_ABORT("For SparseHamiltonian Observables, expval calculation is "
"not supported by shots");
} else if (obs.getObsName().find("Hermitian") != std::string::npos) {
PL_ABORT("For Hermitian Observables, expval calculation is not "
"supported by shots");
} else if (obs.getObsName().find("Hamiltonian") != std::string::npos) {
auto coeffs = obs.getCoeffs();
for (size_t obs_term_idx = 0; obs_term_idx < coeffs.size();
obs_term_idx++) {
auto obs_samples = measure_with_samples(
obs, num_shots, shot_range, obs_term_idx);
PrecisionT result_per_term = std::accumulate(
obs_samples.begin(), obs_samples.end(), 0.0);

result +=
coeffs[obs_term_idx] * result_per_term / obs_samples.size();
}
} else {
auto obs_samples = measure_with_samples(obs, num_shots, shot_range);
result =
std::accumulate(obs_samples.begin(), obs_samples.end(), 0.0);
result = result / obs_samples.size();
}
return result;
}

/**
* @brief Calculate the expectation value for a general Observable.
*
* @param obs Observable.
* @param num_shots Number of shots used to generate samples
* @param shot_range The range of samples to use. All samples are used
* by default.
* @param term_idx Index of a Hamiltonian term
*
* @return Expectation value with respect to the given observable.
*/
auto measure_with_samples(const Observable<StateVectorT> &obs,
const size_t &num_shots,
const std::vector<size_t> &shot_range,
size_t term_idx = 0) {
const size_t num_qubits = _statevector.getTotalNumQubits();
std::vector<size_t> obs_wires;
std::vector<size_t> identity_wires;

auto sub_samples = _sample_state(obs, num_shots, shot_range, obs_wires,
identity_wires, term_idx);

size_t num_samples = num_shots;
if (!shot_range.empty()) {
num_samples = shot_range.size();
}
std::vector<PrecisionT> obs_samples(num_samples, 0);

size_t num_identity_obs = identity_wires.size();
if (!identity_wires.empty()) {
size_t identity_obs_idx = 0;
for (size_t i = 0; i < obs_wires.size(); i++) {
if (identity_wires[identity_obs_idx] == obs_wires[i]) {
std::swap(obs_wires[identity_obs_idx], obs_wires[i]);
identity_obs_idx++;
}
}
}

for (size_t i = 0; i < num_samples; i++) {
std::vector<size_t> local_sample(obs_wires.size());
size_t idx = 0;
for (auto &obs_wire : obs_wires) {
local_sample[idx] = sub_samples[i * num_qubits + obs_wire];
idx++;
}

if (num_identity_obs != obs_wires.size()) {
// eigen values are `1` and `-1` for PauliX, PauliY, PauliZ,
// Hadamard gates the eigen value for a eigen vector |00001> is
// -1 since sum of the value at each bit position is odd
if ((static_cast<size_t>(std::accumulate(
local_sample.begin() + num_identity_obs,
local_sample.end(), 0)) &
size_t{1}) == 1) {
obs_samples[i] = -1;
} else {
obs_samples[i] = 1;
}
} else {
// eigen value for Identity gate is `1`
obs_samples[i] = 1;
}
}
return obs_samples;
}

private:
/**
* @brief Return preprocess state with a observable
*
* @param obs The observable to sample
* @param obs_wires Observable wires.
* @param identity_wires Wires of Identity gates
* @param term_idx Index of a Hamiltonian term
*
* @return a StateVectorT object
*/
auto _preprocess_state(const Observable<StateVectorT> &obs,
std::vector<size_t> &obs_wires,
std::vector<size_t> &identity_wires,
const size_t &term_idx = 0) {
if constexpr (std::is_same_v<
typename StateVectorT::MemoryStorageT,
Pennylane::Util::MemoryStorageLocation::External>) {
StateVectorT sv(_statevector.getData(), _statevector.getLength());
sv.updateData(_statevector.getData(), _statevector.getLength());
obs.applyInPlaceShots(sv, identity_wires, obs_wires, term_idx);
return sv;
} else {
StateVectorT sv(_statevector);
obs.applyInPlaceShots(sv, identity_wires, obs_wires, term_idx);
return sv;
}
}

/**
* @brief Return samples of a observable
*
* @param obs The observable to sample
* @param num_shots Number of shots used to generate samples
* @param shot_range The range of samples to use. All samples are used by
* default.
* @param obs_wires Observable wires.
* @param identity_wires Wires of Identity gates
* @param term_idx Index of a Hamiltonian term
*
* @return std::vector<size_t> samples in std::vector
*/
auto _sample_state(const Observable<StateVectorT> &obs,
const size_t &num_shots,
const std::vector<size_t> &shot_range,
std::vector<size_t> &obs_wires,
std::vector<size_t> &identity_wires,
const size_t &term_idx = 0) {
const size_t num_qubits = _statevector.getTotalNumQubits();
auto sv = _preprocess_state(obs, obs_wires, identity_wires, term_idx);
Derived measure(sv);
auto samples = measure.generate_samples(num_shots);

if (!shot_range.empty()) {
std::vector<size_t> sub_samples(shot_range.size() * num_qubits);
// Get a slice of samples based on the shot_range vector
size_t shot_idx = 0;
for (const auto &i : shot_range) {
for (size_t j = i * num_qubits; j < (i + 1) * num_qubits; j++) {
// TODO some extra work to make it cache-friendly
sub_samples[shot_idx * num_qubits + j - i * num_qubits] =
samples[j];
}
shot_idx++;
}
return sub_samples;
}
return samples;
}
};
} // namespace Pennylane::Measures
Loading