Skip to content

Commit 3d005f0

Browse files
multiphaseCFDringo-but-quantummlxdLee J. O'Riordanmaliasadi
authored
Add shot measurement support to lightning.tensor (#852)
### 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:** [SC-70645] & [SC-65785] This PR adds shot measurement support to `lightning.tensor` via the python layer. Both `MCMC` and `MidMeasurement` are not supported. **Description of the Change:** **Benefits:** **Possible Drawbacks:** **Related GitHub Issues:** --------- Co-authored-by: ringo-but-quantum <[email protected]> Co-authored-by: Lee James O'Riordan <[email protected]> Co-authored-by: Lee J. O'Riordan <[email protected]> Co-authored-by: Ali Asadi <[email protected]> Co-authored-by: Pietropaolo Frisoni <[email protected]> Co-authored-by: erick-xanadu <[email protected]> Co-authored-by: Astral Cai <[email protected]> Co-authored-by: Amintor Dusko <[email protected]> Co-authored-by: Vincent Michaud-Rioux <[email protected]> Co-authored-by: Shiro-Raven <[email protected]> Co-authored-by: albi3ro <[email protected]> Co-authored-by: Luis Alfredo Nuñez Meneses <[email protected]> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: vincentmr <[email protected]> Co-authored-by: paul0403 <[email protected]> Co-authored-by: Raul Torres <[email protected]>
1 parent ffcb279 commit 3d005f0

File tree

17 files changed

+615
-145
lines changed

17 files changed

+615
-145
lines changed

.github/CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
### New features since last release
44

5+
* Add shot measurement support to `lightning.tensor`.
6+
[(#852)](https://github.com/PennyLaneAI/pennylane-lightning/pull/852)
7+
58
* Build and upload Lightning-Tensor wheels (x86_64, AARCH64) to PyPI.
69
[(#862)](https://github.com/PennyLaneAI/pennylane-lightning/pull/862)
710

pennylane_lightning/core/_version.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,4 @@
1616
Version number (major.minor.patch[-label])
1717
"""
1818

19-
__version__ = "0.39.0-dev17"
19+
__version__ = "0.39.0-dev18"

pennylane_lightning/core/src/bindings/Bindings.hpp

+21-1
Original file line numberDiff line numberDiff line change
@@ -746,7 +746,27 @@ void registerLightningTensorBackendAgnosticMeasurements(PyClass &pyclass) {
746746
[](MeasurementsT &M, const std::shared_ptr<ObservableT> &ob) {
747747
return M.var(*ob);
748748
},
749-
"Variance of an observable object.");
749+
"Variance of an observable object.")
750+
.def("generate_samples", [](MeasurementsT &M,
751+
const std::vector<std::size_t> &wires,
752+
const std::size_t num_shots) {
753+
constexpr auto sz = sizeof(std::size_t);
754+
const std::size_t num_wires = wires.size();
755+
const std::size_t ndim = 2;
756+
const std::vector<std::size_t> shape{num_shots, num_wires};
757+
auto &&result = M.generate_samples(wires, num_shots);
758+
759+
const std::vector<std::size_t> strides{sz * num_wires, sz};
760+
// return 2-D NumPy array
761+
return py::array(py::buffer_info(
762+
result.data(), /* data as contiguous array */
763+
sz, /* size of one scalar */
764+
py::format_descriptor<std::size_t>::format(), /* data type */
765+
ndim, /* number of dimensions */
766+
shape, /* shape of the matrix */
767+
strides /* strides for each axis */
768+
));
769+
});
750770
}
751771

752772
/**

pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/measurements/MeasurementsTNCuda.hpp

+92
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
#pragma once
2222

23+
#include <algorithm>
2324
#include <complex>
2425
#include <cuComplex.h>
2526
#include <cutensornet.h>
@@ -164,6 +165,97 @@ template <class TensorNetT> class MeasurementsTNCuda {
164165
return h_res;
165166
}
166167

168+
/**
169+
* @brief Utility method for samples.
170+
*
171+
* @param wires Wires can be a subset or the full system.
172+
* @param num_samples Number of samples
173+
* @param numHyperSamples Number of hyper samples to use in the calculation
174+
* and is default as 1.
175+
*
176+
* @return std::vector<std::size_t> A 1-d array storing the samples.
177+
* Each sample has a length equal to the number of wires. Each sample can
178+
* be accessed using the stride `sample_id * num_wires`, where `sample_id`
179+
* is a number between `0` and `num_samples - 1`.
180+
*/
181+
auto generate_samples(const std::vector<std::size_t> &wires,
182+
const std::size_t num_samples,
183+
const int32_t numHyperSamples = 1)
184+
-> std::vector<std::size_t> {
185+
std::vector<int64_t> samples(num_samples * wires.size());
186+
187+
const std::vector<int32_t> modesToSample =
188+
cuUtil::NormalizeCastIndices<std::size_t, int32_t>(
189+
wires, tensor_network_.getNumQubits());
190+
191+
cutensornetStateSampler_t sampler;
192+
193+
PL_CUTENSORNET_IS_SUCCESS(cutensornetCreateSampler(
194+
/* const cutensornetHandle_t */ tensor_network_.getTNCudaHandle(),
195+
/* cutensornetState_t */ tensor_network_.getQuantumState(),
196+
/* int32_t numModesToSample */ modesToSample.size(),
197+
/* const int32_t *modesToSample */ modesToSample.data(),
198+
/* cutensornetStateSampler_t * */ &sampler));
199+
200+
// Configure the quantum circuit sampler
201+
const cutensornetSamplerAttributes_t samplerAttributes =
202+
CUTENSORNET_SAMPLER_CONFIG_NUM_HYPER_SAMPLES;
203+
204+
PL_CUTENSORNET_IS_SUCCESS(cutensornetSamplerConfigure(
205+
/* const cutensornetHandle_t */ tensor_network_.getTNCudaHandle(),
206+
/* cutensornetStateSampler_t */ sampler,
207+
/* cutensornetSamplerAttributes_t */ samplerAttributes,
208+
/* const void *attributeValue */ &numHyperSamples,
209+
/* size_t attributeSize */ sizeof(numHyperSamples)));
210+
211+
cutensornetWorkspaceDescriptor_t workDesc;
212+
PL_CUTENSORNET_IS_SUCCESS(cutensornetCreateWorkspaceDescriptor(
213+
/* const cutensornetHandle_t */ tensor_network_.getTNCudaHandle(),
214+
/* cutensornetWorkspaceDescriptor_t * */ &workDesc));
215+
216+
const std::size_t scratchSize = cuUtil::getFreeMemorySize() / 2;
217+
218+
// Prepare the quantum circuit sampler for sampling
219+
PL_CUTENSORNET_IS_SUCCESS(cutensornetSamplerPrepare(
220+
/* const cutensornetHandle_t */ tensor_network_.getTNCudaHandle(),
221+
/* cutensornetStateSampler_t */ sampler,
222+
/* size_t maxWorkspaceSizeDevice */ scratchSize,
223+
/* cutensornetWorkspaceDescriptor_t */ workDesc,
224+
/* cudaStream_t unused as of v24.08 */ 0x0));
225+
226+
std::size_t worksize =
227+
getWorkSpaceMemorySize(tensor_network_.getTNCudaHandle(), workDesc);
228+
229+
PL_ABORT_IF(worksize > scratchSize,
230+
"Insufficient workspace size on Device.\n");
231+
232+
const std::size_t d_scratch_length = worksize / sizeof(size_t) + 1;
233+
DataBuffer<std::size_t> d_scratch(d_scratch_length,
234+
tensor_network_.getDevTag(), true);
235+
236+
setWorkSpaceMemory(tensor_network_.getTNCudaHandle(), workDesc,
237+
reinterpret_cast<void *>(d_scratch.getData()),
238+
worksize);
239+
240+
PL_CUTENSORNET_IS_SUCCESS(cutensornetSamplerSample(
241+
/* const cutensornetHandle_t */ tensor_network_.getTNCudaHandle(),
242+
/* cutensornetStateSampler_t */ sampler,
243+
/* int64_t numShots */ num_samples,
244+
/* cutensornetWorkspaceDescriptor_t */ workDesc,
245+
/* int64_t * */ samples.data(),
246+
/* cudaStream_t unused as of v24.08 */ 0x0));
247+
248+
PL_CUTENSORNET_IS_SUCCESS(
249+
cutensornetDestroyWorkspaceDescriptor(workDesc));
250+
PL_CUTENSORNET_IS_SUCCESS(cutensornetDestroySampler(sampler));
251+
252+
std::vector<std::size_t> samples_size_t(samples.size());
253+
254+
std::transform(samples.begin(), samples.end(), samples_size_t.begin(),
255+
[](int64_t x) { return static_cast<std::size_t>(x); });
256+
return samples_size_t;
257+
}
258+
167259
/**
168260
* @brief Calculate var value for a general ObservableTNCuda Observable.
169261
*

pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/measurements/tests/Test_MPSTNCuda_Measure.cpp

+46
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,15 @@
2626
#include "MPSTNCuda.hpp"
2727
#include "MeasurementsTNCuda.hpp"
2828
#include "TNCudaGateCache.hpp"
29+
#include "TestHelpers.hpp"
2930
#include "cuda_helpers.hpp"
3031

3132
/// @cond DEV
3233
namespace {
3334
using namespace Pennylane::LightningTensor::TNCuda::Measures;
3435
using namespace Pennylane::LightningTensor::TNCuda::Observables;
3536
using namespace Pennylane::LightningTensor::TNCuda;
37+
using namespace Pennylane::Util;
3638
} // namespace
3739
/// @endcond
3840

@@ -92,3 +94,47 @@ TEMPLATE_TEST_CASE("Probabilities", "[Measures]", float, double) {
9294
REQUIRE_THROWS_AS(measure.probs({2, 1}), LightningException);
9395
}
9496
}
97+
98+
TEMPLATE_TEST_CASE("Samples", "[Measures]", float, double) {
99+
using TensorNetT = MPSTNCuda<TestType>;
100+
101+
SECTION("Looping over different wire configurations:") {
102+
// Probabilities calculated with Pennylane default.qubit:
103+
std::vector<TestType> expected_probabilities = {
104+
0.67078706, 0.03062806, 0.0870997, 0.00397696,
105+
0.17564072, 0.00801973, 0.02280642, 0.00104134};
106+
107+
// Defining the State Vector that will be measured.
108+
std::size_t bondDim = GENERATE(4, 5);
109+
std::size_t num_qubits = 3;
110+
std::size_t maxBondDim = bondDim;
111+
112+
TensorNetT mps_state{num_qubits, maxBondDim};
113+
114+
mps_state.applyOperations(
115+
{{"RX"}, {"RX"}, {"RY"}, {"RY"}, {"RX"}, {"RY"}},
116+
{{0}, {0}, {1}, {1}, {2}, {2}},
117+
{{false}, {false}, {false}, {false}, {false}, {false}},
118+
{{0.5}, {0.5}, {0.2}, {0.2}, {0.5}, {0.5}});
119+
mps_state.append_mps_final_state();
120+
121+
auto measure = MeasurementsTNCuda<TensorNetT>(mps_state);
122+
123+
std::size_t num_samples = 100000;
124+
const std::vector<std::size_t> wires = {0, 1, 2};
125+
auto samples = measure.generate_samples(wires, num_samples);
126+
auto counts = samples_to_decimal(samples, num_qubits, num_samples);
127+
128+
// compute estimated probabilities from histogram
129+
std::vector<TestType> probabilities(counts.size());
130+
for (std::size_t i = 0; i < counts.size(); i++) {
131+
probabilities[i] = counts[i] / static_cast<TestType>(num_samples);
132+
}
133+
134+
// compare estimated probabilities to real probabilities
135+
SECTION("No wires provided:") {
136+
REQUIRE_THAT(probabilities,
137+
Catch::Approx(expected_probabilities).margin(.1));
138+
}
139+
}
140+
}

pennylane_lightning/core/src/utils/TestHelpers.hpp

+29
Original file line numberDiff line numberDiff line change
@@ -598,6 +598,35 @@ auto randomUnitary(RandomEngine &re, std::size_t num_qubits)
598598
return res;
599599
}
600600

601+
inline auto samples_to_decimal(const std::vector<std::size_t> &samples,
602+
const std::size_t num_qubits,
603+
const std::size_t num_samples)
604+
-> std::vector<std::size_t> {
605+
constexpr uint32_t twos[] = {
606+
1U << 0U, 1U << 1U, 1U << 2U, 1U << 3U, 1U << 4U, 1U << 5U,
607+
1U << 6U, 1U << 7U, 1U << 8U, 1U << 9U, 1U << 10U, 1U << 11U,
608+
1U << 12U, 1U << 13U, 1U << 14U, 1U << 15U, 1U << 16U, 1U << 17U,
609+
1U << 18U, 1U << 19U, 1U << 20U, 1U << 21U, 1U << 22U, 1U << 23U,
610+
1U << 24U, 1U << 25U, 1U << 26U, 1U << 27U, 1U << 28U, 1U << 29U,
611+
1U << 30U, 1U << 31U};
612+
613+
std::size_t N = std::pow(2, num_qubits);
614+
std::vector<std::size_t> counts(N, 0);
615+
std::vector<std::size_t> samples_decimal(num_samples, 0);
616+
617+
// convert samples to decimal and then bin them in counts
618+
for (std::size_t i = 0; i < num_samples; i++) {
619+
for (std::size_t j = 0; j < num_qubits; j++) {
620+
if (samples[i * num_qubits + j] != 0) {
621+
samples_decimal[i] += twos[num_qubits - 1 - j];
622+
}
623+
}
624+
counts[samples_decimal[i]] += 1;
625+
}
626+
627+
return counts;
628+
}
629+
601630
#define PL_REQUIRE_THROWS_MATCHES(expr, type, message_match) \
602631
REQUIRE_THROWS_AS(expr, type); \
603632
REQUIRE_THROWS_WITH(expr, Catch::Matchers::Contains(message_match));

pennylane_lightning/core/src/utils/cuda_utils/tests/CMakeLists.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
cmake_minimum_required(VERSION 3.20)
22

3-
project(lightning_gpu_utils_tests)
3+
project(cuda_utils_tests)
44

55
# Default build type for test code is Debug
66
if(NOT CMAKE_BUILD_TYPE)

0 commit comments

Comments
 (0)