-
Notifications
You must be signed in to change notification settings - Fork 42
/
Copy pathMeasurementsBase.hpp
296 lines (269 loc) · 10.7 KB
/
MeasurementsBase.hpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
// Copyright 2018-2023 Xanadu Quantum Technologies Inc.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @file MeasurementsBase.hpp
* Defines the Measurements CRTP base class.
*/
#pragma once
#include <string>
#include <vector>
#include "Observables.hpp"
#include "CPUMemoryModel.hpp"
/// @cond DEV
namespace {
using namespace Pennylane::Observables;
} // namespace
/// @endcond
namespace Pennylane::Measures {
/**
* @brief Observable's Measurement Class.
*
* This class performs measurements in the state vector provided to its
* constructor. Observables are defined by its operator(matrix), the observable
* class, or through a string-based function dispatch.
*
* @tparam StateVectorT
* @tparam Derived
*/
template <class StateVectorT, class Derived> class MeasurementsBase {
private:
using PrecisionT = typename StateVectorT::PrecisionT;
using ComplexT = typename StateVectorT::ComplexT;
protected:
#ifdef _ENABLE_PLGPU
StateVectorT &_statevector;
#else
const StateVectorT &_statevector;
#endif
public:
#ifdef _ENABLE_PLGPU
explicit MeasurementsBase(StateVectorT &statevector)
: _statevector{statevector} {};
#else
explicit MeasurementsBase(const StateVectorT &statevector)
: _statevector{statevector} {};
#endif
/**
* @brief Calculate the expectation value for a general Observable.
*
* @param obs Observable.
* @return Expectation value with respect to the given observable.
*/
auto expval(const Observable<StateVectorT> &obs) -> PrecisionT {
return static_cast<Derived *>(this)->expval(obs);
}
/**
* @brief Calculate the variance for a general Observable.
*
* @param obs Observable.
* @return Variance with respect to the given observable.
*/
auto var(const Observable<StateVectorT> &obs) -> PrecisionT {
return static_cast<Derived *>(this)->var(obs);
}
/**
* @brief Probabilities of each computational basis state.
*
* @return Floating point std::vector with probabilities
* in lexicographic order.
*/
auto probs() -> std::vector<PrecisionT> {
return static_cast<Derived *>(this)->probs();
};
/**
* @brief Probabilities for a subset of the full system.
*
* @param wires Wires will restrict probabilities to a subset
* of the full system.
* @return Floating point std::vector with probabilities.
* The basis columns are rearranged according to wires.
*/
auto probs(const std::vector<size_t> &wires) -> std::vector<PrecisionT> {
return static_cast<Derived *>(this)->probs(wires);
};
/**
* @brief Generate samples
*
* @param num_samples Number of samples
* @return 1-D vector of samples in binary with each sample
* separated by a stride equal to the number of qubits.
*/
auto generate_samples(size_t num_samples) -> std::vector<size_t> {
return static_cast<Derived *>(this)->generate_samples(num_samples);
};
/**
* @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