pennylane-qrack 0.20.3__tar.gz → 0.21.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of pennylane-qrack might be problematic. Click here for more details.

Files changed (35) hide show
  1. {pennylane_qrack-0.20.3 → pennylane_qrack-0.21.0}/PKG-INFO +1 -1
  2. {pennylane_qrack-0.20.3 → pennylane_qrack-0.21.0}/catalyst/runtime/include/DataView.hpp +2 -0
  3. {pennylane_qrack-0.20.3 → pennylane_qrack-0.21.0}/catalyst/runtime/include/Exception.hpp +1 -1
  4. pennylane_qrack-0.21.0/catalyst/runtime/include/OQDRuntimeCAPI.h +56 -0
  5. pennylane_qrack-0.21.0/catalyst/runtime/include/QuantumDevice.hpp +576 -0
  6. {pennylane_qrack-0.20.3 → pennylane_qrack-0.21.0}/catalyst/runtime/include/RuntimeCAPI.h +9 -4
  7. {pennylane_qrack-0.20.3 → pennylane_qrack-0.21.0}/pennylane_qrack/_version.py +1 -1
  8. {pennylane_qrack-0.20.3 → pennylane_qrack-0.21.0}/pennylane_qrack/qrack_ace_device.py +9 -5
  9. {pennylane_qrack-0.20.3 → pennylane_qrack-0.21.0}/pennylane_qrack/qrack_device.cpp +4 -22
  10. {pennylane_qrack-0.20.3 → pennylane_qrack-0.21.0}/pennylane_qrack/qrack_device.py +3 -1
  11. {pennylane_qrack-0.20.3 → pennylane_qrack-0.21.0}/pennylane_qrack.egg-info/PKG-INFO +1 -1
  12. {pennylane_qrack-0.20.3 → pennylane_qrack-0.21.0}/pennylane_qrack.egg-info/SOURCES.txt +1 -2
  13. pennylane_qrack-0.20.3/catalyst/runtime/include/DynamicLibraryLoader.hpp +0 -79
  14. pennylane_qrack-0.20.3/catalyst/runtime/include/QuantumDevice.hpp +0 -364
  15. pennylane_qrack-0.20.3/pennylane_qrack/libqrack_device.so +0 -0
  16. {pennylane_qrack-0.20.3 → pennylane_qrack-0.21.0}/CHANGELOG.md +0 -0
  17. {pennylane_qrack-0.20.3 → pennylane_qrack-0.21.0}/CMakeLists.txt +0 -0
  18. {pennylane_qrack-0.20.3 → pennylane_qrack-0.21.0}/LICENSE +0 -0
  19. {pennylane_qrack-0.20.3 → pennylane_qrack-0.21.0}/MANIFEST.in +0 -0
  20. {pennylane_qrack-0.20.3 → pennylane_qrack-0.21.0}/Makefile +0 -0
  21. {pennylane_qrack-0.20.3 → pennylane_qrack-0.21.0}/README.rst +0 -0
  22. {pennylane_qrack-0.20.3 → pennylane_qrack-0.21.0}/catalyst/runtime/include/Types.h +0 -0
  23. {pennylane_qrack-0.20.3 → pennylane_qrack-0.21.0}/pennylane_qrack/QrackAceDeviceConfig.toml +0 -0
  24. {pennylane_qrack-0.20.3 → pennylane_qrack-0.21.0}/pennylane_qrack/QrackDeviceConfig.toml +0 -0
  25. {pennylane_qrack-0.20.3 → pennylane_qrack-0.21.0}/pennylane_qrack/__init__.py +0 -0
  26. {pennylane_qrack-0.20.3 → pennylane_qrack-0.21.0}/pennylane_qrack.egg-info/dependency_links.txt +0 -0
  27. {pennylane_qrack-0.20.3 → pennylane_qrack-0.21.0}/pennylane_qrack.egg-info/entry_points.txt +0 -0
  28. {pennylane_qrack-0.20.3 → pennylane_qrack-0.21.0}/pennylane_qrack.egg-info/requires.txt +0 -0
  29. {pennylane_qrack-0.20.3 → pennylane_qrack-0.21.0}/pennylane_qrack.egg-info/top_level.txt +0 -0
  30. {pennylane_qrack-0.20.3 → pennylane_qrack-0.21.0}/requirements.txt +0 -0
  31. {pennylane_qrack-0.20.3 → pennylane_qrack-0.21.0}/setup.cfg +0 -0
  32. {pennylane_qrack-0.20.3 → pennylane_qrack-0.21.0}/setup.py +0 -0
  33. {pennylane_qrack-0.20.3 → pennylane_qrack-0.21.0}/tests/test_apply.py +0 -0
  34. {pennylane_qrack-0.20.3 → pennylane_qrack-0.21.0}/tests/test_integration.py +0 -0
  35. {pennylane_qrack-0.20.3 → pennylane_qrack-0.21.0}/tests/test_units.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pennylane-qrack
3
- Version: 0.20.3
3
+ Version: 0.21.0
4
4
  Summary: PennyLane plugin for Qrack.
5
5
  Home-page: http://github.com/vm6502q
6
6
  Maintainer: vm6502q
@@ -59,6 +59,7 @@ template <typename T, size_t R> class DataView {
59
59
  int64_t idx;
60
60
  for (int64_t i = R; i > 0; --i) {
61
61
  idx = i - 1;
62
+ RT_ASSERT(view.sizes[idx] > 0);
62
63
  if (indices[idx]++ < view.sizes[idx] - 1) {
63
64
  next_axis = idx;
64
65
  break;
@@ -77,6 +78,7 @@ template <typename T, size_t R> class DataView {
77
78
  int64_t idx;
78
79
  for (int64_t i = R; i > 0; --i) {
79
80
  idx = i - 1;
81
+ RT_ASSERT(view.sizes[idx] > 0);
80
82
  if (indices[idx]++ < view.sizes[idx] - 1) {
81
83
  next_axis = idx;
82
84
  break;
@@ -78,7 +78,7 @@ class RuntimeException : public std::exception {
78
78
  const char *function_name)
79
79
  {
80
80
  std::stringstream sstream;
81
- sstream << "[" << file_name << "][Line:" << line << "][Function:" << function_name
81
+ sstream << "[" << file_name << ":" << line << "][Function:" << function_name
82
82
  << "] Error in Catalyst Runtime: " << message;
83
83
 
84
84
  throw RuntimeException(sstream.str());
@@ -0,0 +1,56 @@
1
+ // Copyright 2025 Xanadu Quantum Technologies Inc.
2
+
3
+ // Licensed under the Apache License, Version 2.0 (the "License");
4
+ // you may not use this file except in compliance with the License.
5
+ // You may obtain a copy of the License at
6
+
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ // Unless required by applicable law or agreed to in writing, software
10
+ // distributed under the License is distributed on an "AS IS" BASIS,
11
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ // See the License for the specific language governing permissions and
13
+ // limitations under the License.
14
+
15
+ #pragma once
16
+ #ifndef OQDRUNTIMECAPI_H
17
+ #define OQDRUNTIMECAPI_H
18
+
19
+ #include <array>
20
+ #include <cstdint>
21
+
22
+ #include "Exception.hpp"
23
+ #include "Types.h"
24
+
25
+ #ifdef __cplusplus
26
+ extern "C" {
27
+ #endif
28
+
29
+ struct Beam {
30
+ int64_t transition_index;
31
+ double rabi;
32
+ double detuning;
33
+ std::array<int64_t, 3> polarization;
34
+ std::array<int64_t, 3> wavevector;
35
+ };
36
+
37
+ struct Pulse {
38
+ Beam *beam;
39
+ size_t target;
40
+ double duration;
41
+ double phase;
42
+ };
43
+
44
+ // OQD Runtime Instructions
45
+ void __catalyst__oqd__rt__initialize();
46
+ void __catalyst__oqd__rt__finalize(const std::string &openapl_file_name);
47
+ void __catalyst__oqd__ion(const std::string &ion_specs);
48
+ void __catalyst__oqd__modes(const std::vector<std::string> &phonon_specs);
49
+ Pulse *__catalyst__oqd__pulse(QUBIT *qubit, double duration, double phase, Beam *beam);
50
+ void __catalyst__oqd__ParallelProtocol(Pulse **pulses, size_t n);
51
+
52
+ #ifdef __cplusplus
53
+ } // extern "C"
54
+ #endif
55
+
56
+ #endif
@@ -0,0 +1,576 @@
1
+ // Copyright 2022-2025 Xanadu Quantum Technologies Inc.
2
+
3
+ // Licensed under the Apache License, Version 2.0 (the "License");
4
+ // you may not use this file except in compliance with the License.
5
+ // You may obtain a copy of the License at
6
+
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ // Unless required by applicable law or agreed to in writing, software
10
+ // distributed under the License is distributed on an "AS IS" BASIS,
11
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ // See the License for the specific language governing permissions and
13
+ // limitations under the License.
14
+
15
+ #pragma once
16
+
17
+ #include <complex>
18
+ #include <memory>
19
+ #include <optional>
20
+ #include <random>
21
+ #include <vector>
22
+
23
+ #include "DataView.hpp"
24
+ #include "Types.h"
25
+
26
+ // A helper template macro to generate the <IDENTIFIER>Factory function by
27
+ // calling <CONSTRUCTOR>(kwargs). Check the Custom Devices guideline for details:
28
+ // https://docs.pennylane.ai/projects/catalyst/en/stable/dev/custom_devices.html
29
+ #define GENERATE_DEVICE_FACTORY(IDENTIFIER, CONSTRUCTOR) \
30
+ extern "C" Catalyst::Runtime::QuantumDevice *IDENTIFIER##Factory(const char *kwargs) \
31
+ { \
32
+ return new CONSTRUCTOR(std::string(kwargs)); \
33
+ }
34
+
35
+ namespace Catalyst::Runtime {
36
+
37
+ /**
38
+ * @brief Interface class for Catalyst Runtime device backends.
39
+ *
40
+ * All Catalyst device plugins must implement this class and distribute it as a shared library.
41
+ * See https://docs.pennylane.ai/projects/catalyst/en/stable/dev/custom_devices.html for details.
42
+ *
43
+ * The `QuantumDevice` interface methods can broadly be categorized into device management functions
44
+ * that do not directly impact the computation or quantum state (think qubit management,
45
+ * execution configuration, or recording functionality), and computation functions like quantum
46
+ * gates and measurement procedures.
47
+ * In addition, not all methods are required to be implemented by a plugin, as some features are
48
+ * opt-in or not applicable to all device types. Optional features will be marked as such in the
49
+ * method description and contain a stub implementation.
50
+ */
51
+ struct QuantumDevice {
52
+ QuantumDevice() = default; // LCOV_EXCL_LINE
53
+ virtual ~QuantumDevice() = default; // LCOV_EXCL_LINE
54
+
55
+ QuantumDevice &operator=(const QuantumDevice &) = delete;
56
+ QuantumDevice(const QuantumDevice &) = delete;
57
+ QuantumDevice(QuantumDevice &&) = delete;
58
+ QuantumDevice &operator=(QuantumDevice &&) = delete;
59
+
60
+ // ----------------------------------------
61
+ // QUBIT MANAGEMENT
62
+ // ----------------------------------------
63
+
64
+ /**
65
+ * @brief Allocate an array of qubits.
66
+ *
67
+ * The operation should perform the necessary steps to make the given number of qubits
68
+ * available in a clean |0> state. At minimum, a device must be able to execute this
69
+ * call once before any quantum operations have been run. Handling multiple allocation calls
70
+ * is optional and only useful if the device intends to support dynamic qubit allocations
71
+ * (that is allocations during quantum program execution).
72
+ *
73
+ * The values returned by this operation are integer IDs that will be used to address the
74
+ * allocated qubits in subsequent operations (like gates or measurements). There are no
75
+ * restrictions on the values these IDs can take, but the IDs of all active qubits must not
76
+ * overlap, and once an ID is mapped to a particular qubit that ID must not change until the
77
+ * qubit is explicitly freed.
78
+ *
79
+ * While devices may choose not to distinguish between logical and device IDs, having a logical
80
+ * qubit labeling system can help catch program errors such as addressing previously freed
81
+ * qubits. An example implementation can be found in `QubitManager.hpp`. Devices are also free
82
+ * to disable a resource without physically freeing it, allowing for faster dynamic allocations.
83
+ *
84
+ * @param num_qubits The number of qubits to allocate.
85
+ *
86
+ * @return `std::vector<QubitIdType>` Array of qubit IDs.
87
+ */
88
+ virtual auto AllocateQubits(size_t num_qubits) -> std::vector<QubitIdType> = 0;
89
+
90
+ /**
91
+ * @brief Release all qubits and reset device state.
92
+ *
93
+ * This operation can be considered a device-wide quantum resource release, regardless of how
94
+ * many allocation calls have been run (although on devices with static allocation there would
95
+ * only be one matching `AllocateQubits` - `ReleaseAllQubits` pair).
96
+ * After executing this call, a device should be in a state ready to allocate new qubits and
97
+ * execute another quantum program. Configurable execution parameters, whether explicit in
98
+ * the interface (like shots) or internal via the constructor kwargs, should be maintained
99
+ * in the same state as when entering this method.
100
+ */
101
+ virtual void ReleaseAllQubits() = 0;
102
+
103
+ /**
104
+ * @brief Get the number of currently allocated qubits.
105
+ *
106
+ * @return `size_t`
107
+ */
108
+ virtual auto GetNumQubits() const -> size_t = 0;
109
+
110
+ /**
111
+ * @brief (Optional) Allocate a qubit.
112
+ *
113
+ * Allocate a new qubit in the |0> state. This method is only needed for devices with dynamic
114
+ * qubit allocation.
115
+ *
116
+ * See `AllocateQubits` for more details on allocation semantics.
117
+ *
118
+ * @return `QubitIdType` Qubit ID.
119
+ */
120
+ virtual auto AllocateQubit() -> QubitIdType
121
+ {
122
+ RT_FAIL("Dynamic qubit allocation is unsupported by device");
123
+ }
124
+
125
+ /**
126
+ * @brief (Optional) Release a qubit.
127
+ *
128
+ * Release the provided qubit. This method is only needed for devices with dynamic qubit
129
+ * allocation.
130
+ *
131
+ * Note that the interface does not require the qubit to be in a particular state. The behaviour
132
+ * for releasing an entangled qubit is left up to the device, and could include:
133
+ * - raising a runtime error (assuming the device can detect impure states)
134
+ * - continuing execution with an entangled but inaccessible qubit
135
+ * - resetting the qubit with or without measuring its state
136
+ *
137
+ * Opposite of `AllocateQubit`.
138
+ *
139
+ * @param qubit ID of the qubit to release.
140
+ */
141
+ virtual void ReleaseQubit(QubitIdType qubit)
142
+ {
143
+ RT_FAIL("Dynamic qubit release is unsupported by device");
144
+ }
145
+
146
+ // ----------------------------------------
147
+ // EXECUTION MANAGEMENT
148
+ // ----------------------------------------
149
+
150
+ /**
151
+ * @brief Set the number of execution shots.
152
+ *
153
+ * The Runtime will call this function to set the number of times the quantum execution is to
154
+ * be repeated. Generally, it will be called once before any quantum instructions are run, but
155
+ * some devices may choose to support it at arbitrary points in the program if they have the
156
+ * capability to simulate shot noise.
157
+ *
158
+ * Devices with no or restricted support are encouraged to raise a runtime error.
159
+ *
160
+ * @param shots Shot number.
161
+ */
162
+ virtual void SetDeviceShots(size_t shots) = 0;
163
+
164
+ /**
165
+ * @brief Get the number of execution shots.
166
+ *
167
+ * @return `size_t` Shot number.
168
+ */
169
+ virtual auto GetDeviceShots() const -> size_t = 0;
170
+
171
+ /**
172
+ * @brief (Optional) Set the PRNG of the device.
173
+ *
174
+ * The Catalyst runtime enables seeded program execution on non-hardware devices.
175
+ * A random number generator instance is managed by the runtime to predictably
176
+ * generate results for non-deterministic programs, such as those involving `Measure`
177
+ * calls.
178
+ * Devices implementing support for this feature do not need to use the provided
179
+ * PRNG instance as their sole source of randomness, but it is expected that the
180
+ * the same instance state will predictably and reproducibly generate the same
181
+ * program results (considering all random processes in the device).
182
+ * It is also expected that the provided PRNG state is evolved sufficiently so that two copies
183
+ * of a device provided with the same PRNG instance do not produce identical results when run
184
+ * in succession.
185
+ * Note that the provided PRNG instance is not thread-locked, and devices wishing to use it
186
+ * across internal threads will need to provide their own thread-safety.
187
+ *
188
+ * @param gen Pointer to a Catalyst-managed Mersenne Twister instance.
189
+ */
190
+ virtual void SetDevicePRNG(std::mt19937 *gen) {};
191
+
192
+ // ----------------------------------------
193
+ // QUANTUM OPERATIONS
194
+ // ----------------------------------------
195
+
196
+ /**
197
+ * @brief Apply an arbitrary quantum gate to the device.
198
+ *
199
+ * This instruction is opaque to the chosen quantum operation, which is specified via a
200
+ * string identifier. Additionally, an array of floating point parameters can be supplied,
201
+ * as well certain quantum modifiers, like the Hermitian adjoint (inverse) and control qubits.
202
+ * If the supplied combination of parameters is invalid or unsupported, the device should
203
+ * raise a runtime error.
204
+ *
205
+ * @param name A string identifier for the operation to apply.
206
+ * @param params Float parameters for parametric gates (may be empty).
207
+ * @param wires Qubits to apply the operation to.
208
+ * @param inverse Apply the inverse (Hermitian adjoint) of the operation.
209
+ * @param controlled_wires Control qubits applied to the operation.
210
+ * @param controlled_values Control values associated to the control qubits (equal length).
211
+ */
212
+ virtual void NamedOperation(const std::string &name, const std::vector<double> &params,
213
+ const std::vector<QubitIdType> &wires, bool inverse = false,
214
+ const std::vector<QubitIdType> &controlled_wires = {},
215
+ const std::vector<bool> &controlled_values = {}) = 0;
216
+ /**
217
+ * @brief Perform a computational-basis measurement on one qubit.
218
+ *
219
+ * This instruction is generally used for mid-circuit measurements, implementing an immediate,
220
+ * random projective measurement and producing a classical result as output. However, it may
221
+ * also be used in terminal measurements where the measurement processes below are unsupported.
222
+ *
223
+ * @param wire The qubit to measure.
224
+ * @param postselect Optional parameter to force the result to the provided state (roughly
225
+ * equivalent to post-selection).
226
+ *
227
+ * @return `Result` The measurement result.
228
+ */
229
+ virtual auto Measure(QubitIdType wire, std::optional<int32_t> postselect) -> Result = 0;
230
+
231
+ /**
232
+ * @brief (Optional) Apply an arbitrary unitary matrix to the device.
233
+ *
234
+ * Instead of identifying an operation by name, this instruction uses the mathematical
235
+ * representation of a quantum operator as a unitary matrix in the computational basis.
236
+ *
237
+ * See `NamedOperation` for additional gate semantics.
238
+ *
239
+ * @param matrix A 1D array representation of the matrix in row-major format.
240
+ * @param wires Qubits to apply the operation to.
241
+ * @param inverse Apply the inverse of the operation.
242
+ * @param controlled_wires Control qubits applied to the operation.
243
+ * @param controlled_values Control values associated to the control qubits (equal length).
244
+ */
245
+ virtual void MatrixOperation(const std::vector<std::complex<double>> &matrix,
246
+ const std::vector<QubitIdType> &wires, bool inverse = false,
247
+ const std::vector<QubitIdType> &controlled_wires = {},
248
+ const std::vector<bool> &controlled_values = {})
249
+ {
250
+ RT_FAIL("MatrixOperation is unsupported by device");
251
+ }
252
+
253
+ /**
254
+ * @brief (Optional) Initialize qubits to a computational basis state.
255
+ *
256
+ * This instruction initializes a set of qubits to the provided computational basis state.
257
+ * Although the instruction is typically used for state initialization at the beginning of
258
+ * a quantum program, devices are encouraged to support arbitrary re-initialization if
259
+ * capable. Reinitialization on a subset of qubits is equivalent to deallocation -> allocation
260
+ * -> initialization on the same set of qubits.
261
+ * See `ReleaseQubit` for caveats around releasing / resetting entangled qubits.
262
+ *
263
+ * @param n Bitstring representation of the basis state |n>, stored as a Byte-array.
264
+ * @param wires The qubits to initialize.
265
+ */
266
+ virtual void SetBasisState(DataView<int8_t, 1> &n, std::vector<QubitIdType> &wires)
267
+ {
268
+ RT_FAIL("SetBasisState is unsupported by device");
269
+ }
270
+
271
+ /**
272
+ * @brief (Optional) Initialize qubits to an arbitrary quantum state.
273
+ *
274
+ * Like `SetBasisState`, but instead of initializing to a single computational basis state
275
+ * this instruction works with a vector of complex amplitudes, one for each possible basis
276
+ * state.
277
+ *
278
+ * @param state Quantum state vector of size 2^len(wires).
279
+ * @param wires The qubits to initialize.
280
+ */
281
+ virtual void SetState(DataView<std::complex<double>, 1> &state, std::vector<QubitIdType> &wires)
282
+ {
283
+ RT_FAIL("SetState is unsupported by device");
284
+ }
285
+
286
+ // ----------------------------------------
287
+ // QUANTUM OBSERVABLES
288
+ // ----------------------------------------
289
+
290
+ /**
291
+ * @brief (Optional) Construct a named observable.
292
+ *
293
+ * This operation instructs the device to generate a named observable based on the provided enum
294
+ * `id`, which will be one of {Identity, PauliX, PauliY, PauliZ, Hermitian}. The Hermitian kind
295
+ * uses additional data to define the observable from a 2D complex matrix, which is expected to
296
+ * be Hermitian. If this is not the case, the device is free to raise an error or produce
297
+ * undefined behaviour. Additionally, the operation accepts a list of target qubits whose length
298
+ * is expected to match the dimensionality of the observable.
299
+ *
300
+ * The observable system in the Catalyst Runtime relies primarily on the device's implementation
301
+ * for support. As such, the device is free to represent observables in any way they wish.
302
+ * Before using an observable in measurement processes, Catalyst will invoke one or more
303
+ * observable methods from the interface. The device is expected to process and cache the
304
+ * information in such a way that it is ready to use in a subsequent measurement process call.
305
+ * The device must return an ID than unambiguously identifies the requested observable, but this
306
+ * ID is not required to be unique across different calls supplied with the same information.
307
+ *
308
+ * @param id The name of the observable.
309
+ * @param matrix The matrix of data to use for a Hermitian observable (unused otherwise).
310
+ * @param wires Qubits the observable applies to.
311
+ *
312
+ * @return `ObsIdType` ID of the constructed observable.
313
+ */
314
+ virtual auto Observable(ObsId id, const std::vector<std::complex<double>> &matrix,
315
+ const std::vector<QubitIdType> &wires) -> ObsIdType
316
+ {
317
+ RT_FAIL("Observable is unsupported by device");
318
+ }
319
+
320
+ /**
321
+ * @brief (Optional) Construct a tensor product of existing observables (prod).
322
+ *
323
+ * Given a list of observable IDs, construct the tensor product of all supplied observables.
324
+ * If wires overlap across observables, the device should raise an error unless it can produce
325
+ * sensible results.
326
+ *
327
+ * See `Observable` for additional details.
328
+ *
329
+ * @param obs The list of observables IDs.
330
+ *
331
+ * @return `ObsIdType` ID of the constructed observable.
332
+ */
333
+ virtual auto TensorObservable(const std::vector<ObsIdType> &obs) -> ObsIdType
334
+ {
335
+ RT_FAIL("TensorObservable is unsupported by device");
336
+ }
337
+
338
+ /**
339
+ * @brief (Optional) Construct a linear combination of existing observables (sum).
340
+ *
341
+ * Given a list of observable IDs and associated coefficients, construct the linear combination
342
+ * the supplied terms. The coefficients and observables are expected to have the same length.
343
+ *
344
+ *
345
+ * See `Observable` for additional details.
346
+ *
347
+ * @param coeffs The list of coefficients.
348
+ * @param obs The list of observables IDs.
349
+ *
350
+ * @return `ObsIdType` ID of the constructed observable.
351
+ */
352
+ virtual auto HamiltonianObservable(const std::vector<double> &coeffs,
353
+ const std::vector<ObsIdType> &obs) -> ObsIdType
354
+ {
355
+ RT_FAIL("HamiltonianObservable is unsupported by device");
356
+ }
357
+
358
+ // ----------------------------------------
359
+ // MEASUREMENT PROCESSES
360
+ // ----------------------------------------
361
+
362
+ /**
363
+ * @brief (Optional) Compute raw samples on all qubits.
364
+ *
365
+ * Perform measurement sampling in the computational basis on all qubits. The number of samples
366
+ * to take is the current active shot count in the device (see also `SetDeviceShots`). When
367
+ * possible, the result should be produced without affecting the internal quantum state.
368
+ *
369
+ * The samples are taken individually on each qubit and stored in a 2D array with the shots
370
+ * being the outer dimension and the qubits being the inner dimension. For example, a 4-shot
371
+ * 2-qubit sample call might produce the following result:
372
+ * [[0, 1],
373
+ * [0, 0],
374
+ * [1, 1],
375
+ * [0, 0]]
376
+ * The samples are stored as double-precision floating-point numbers; for computational basis
377
+ * measurements this might be overkill, but allows for expanding this operation in the future
378
+ * to sample arbitrary observables and produce eigenvalues as sample results.
379
+ *
380
+ * The result of this operation must be written into the `samples` argument buffer.
381
+ *
382
+ * @param samples The pre-allocated buffer for the measurement samples.
383
+ */
384
+ virtual void Sample(DataView<double, 2> &samples)
385
+ {
386
+ RT_FAIL("Sample is unsupported by device");
387
+ }
388
+
389
+ /**
390
+ * @brief (Optional) Compute raw samples for a quantum subsystem.
391
+ *
392
+ * Like `Sample`, but for a subset of currently allocated qubits.
393
+ *
394
+ * @param samples The pre-allocated buffer for the measurement samples.
395
+ * @param wires Qubits to compute samples for.
396
+ */
397
+ virtual void PartialSample(DataView<double, 2> &samples, const std::vector<QubitIdType> &wires)
398
+ {
399
+ RT_FAIL("PartialSample is unsupported by device");
400
+ }
401
+
402
+ /**
403
+ * @brief (Optional) Compute the sample counts on all qubits.
404
+ *
405
+ * Perform measurement sampling in the computational basis and sum up the occurrence of each
406
+ * outcome. All currently allocated qubits should be sampled. The number of samples to take
407
+ * is the current active shot count in the device (see also `SetDeviceShots`). When possible,
408
+ * the result should be produced without affecting the internal quantum state.
409
+ *
410
+ * The potential measurement outcomes are returned as `eigvals`; currently the only
411
+ * supported mode uses computational basis states whose bitstring representation "01010110..."
412
+ * is first taken as an integer value, and then converted to a double-precision floating-point
413
+ * number. This effectively limits the number of qubits supported by this operation to 53, after
414
+ * which the basis states cannot be accurately represented by the floating-point format. In the
415
+ * future, this operation may be expanded to support eigenvalues of arbitrary observables as
416
+ * possible measurement outcomes, hence the floating-point datatype.
417
+ *
418
+ * The number of times of each measurement outcome is sampled is stored as a 64-bit integer in
419
+ * the same location in the `counts` array as the corresponding basis state in the `eigvals`
420
+ * array. The entries need not be in any particular order, since the measured states are
421
+ * returned alongside the counts.
422
+ *
423
+ * In effect, the result of this operation is a dictionary from measurement outcomes to counts,
424
+ * stored as a pair of dense 1D arrays, one for the keys and one for the values. It's important
425
+ * that all 2^n elements of the `eigvals` and `counts` arrays are written to, where n is the
426
+ * number of qubits, else the result will contain uninitialized data.
427
+ *
428
+ * The results of this operation must be written into the `eigvals` and `counts` argument
429
+ * buffers.
430
+ *
431
+ * @param eigvals The pre-allocated buffer for all measured states.
432
+ * @param counts The pre-allocated buffer for all measured counts.
433
+ */
434
+ virtual void Counts(DataView<double, 1> &eigvals, DataView<int64_t, 1> &counts)
435
+ {
436
+ RT_FAIL("Counts is unsupported by device");
437
+ }
438
+
439
+ /**
440
+ * @brief (Optional) Compute the sample counts for a quantum subsystem.
441
+ *
442
+ * Like `Counts`, but for a subset of currently allocated qubits.
443
+ *
444
+ * @param eigvals The pre-allocated buffer for all measured states.
445
+ * @param counts The pre-allocated buffer for all measured counts.
446
+ * @param wires Qubits to compute sample counts for.
447
+ */
448
+ virtual void PartialCounts(DataView<double, 1> &eigvals, DataView<int64_t, 1> &counts,
449
+ const std::vector<QubitIdType> &wires)
450
+ {
451
+ RT_FAIL("PartialCounts is unsupported by device");
452
+ }
453
+
454
+ /**
455
+ * @brief (Optional) Compute measurement probabilities on all qubits.
456
+ *
457
+ * The output of this operation is the probability distribution across computational basis
458
+ * states for all currently allocated qubits. When possible, the result should be produced
459
+ * without affecting the internal quantum state.
460
+ *
461
+ * The result of this operation must be written into the `probs` argument buffer.
462
+ *
463
+ * @param probs The pre-allocated buffer for the probabilities.
464
+ */
465
+ virtual void Probs(DataView<double, 1> &probs) { RT_FAIL("Probs is unsupported by device"); }
466
+
467
+ /**
468
+ * @brief (Optional) Compute measurement probabilities for a quantum subsystem.
469
+ *
470
+ * Like `Probs`, but for a subset of currently allocated qubits.
471
+ *
472
+ * @param probs The pre-allocated buffer for the probabilities.
473
+ * @param wires Qubits to compute probabilities for.
474
+ */
475
+ virtual void PartialProbs(DataView<double, 1> &probs, const std::vector<QubitIdType> &wires)
476
+ {
477
+ RT_FAIL("PartialProbs is unsupported by device");
478
+ }
479
+ /**
480
+ * @brief (Optional) Compute the expected value of an observable.
481
+ *
482
+ * The output of this operation is expectation value ⟨O⟩ of an observable O with respect to the
483
+ * current quantum state. When possible, the result should be produced without affecting the
484
+ * internal quantum state.
485
+ *
486
+ * See also `Observable` for observable semantics.
487
+ *
488
+ * @param obsKey The ID of the constructed observable.
489
+ *
490
+ * @return `double` The expectation value of the observable.
491
+ */
492
+ virtual auto Expval(ObsIdType obsKey) -> double { RT_FAIL("Expval is unsupported by device"); }
493
+
494
+ /**
495
+ * @brief (Optional) Compute the variance of an observable.
496
+ *
497
+ * The output of this operation is variance ⟨O²⟩−⟨O⟩² of an observable O with respect to the
498
+ * current quantum state. When possible, the result should be produced without affecting the
499
+ * internal quantum state.
500
+ *
501
+ * See also `Observable` for observable semantics.
502
+ *
503
+ * @param obsKey The ID of the constructed observable.
504
+ *
505
+ * @return `double` The variance of the observable.
506
+ */
507
+ virtual auto Var(ObsIdType obsKey) -> double { RT_FAIL("Var is unsupported by device"); }
508
+
509
+ /**
510
+ * @brief (Optional) Get the full quantum state of all qubits.
511
+ *
512
+ * Typically for devices with statevector simulation capabilities.
513
+ *
514
+ * The output of this operation is the quantum statevector in the computational basis
515
+ * on all currently allocated qubits. When possible, the result should be produced without
516
+ * affecting the internal quantum state.
517
+ *
518
+ * The result of this operation must be written into the `state` argument buffer.
519
+ *
520
+ * @param state Pre-allocated buffer for the quantum state.
521
+ */
522
+ virtual void State(DataView<std::complex<double>, 1> &state)
523
+ {
524
+ RT_FAIL("State is unsupported by device");
525
+ }
526
+
527
+ // ----------------------------------------
528
+ // QUANTUM DERIVATIVES
529
+ // ----------------------------------------
530
+
531
+ /**
532
+ * @brief (Optional) Compute the gradient/Jacobian of a quantum circuit.
533
+ *
534
+ * For devices with internal differentiation capabilities (e.g. simulator with reverse-mode AD).
535
+ *
536
+ * Performing differentiation inside a device can be advantageous for performance compared to
537
+ * device-agnostic methods like the Parameter-Shift technique. If supported, Catalyst will
538
+ * communicate the start and end of the "forward pass" via the `StartTapeRecording` and
539
+ * `StopTapeRecording` functions. The `Gradient` call then requests the "reverse pass" with
540
+ * respect to the recorded circuit and observables. A sample implementation of circuit
541
+ * recording is available in `CacheManager.hpp`.
542
+ *
543
+ * The output is an array of gradients (i.e. the Jacobian), one for each expectation value /
544
+ * observable in the recorded program. The length of the gradient is determined by the number
545
+ * of gate parameters in the recorded circuit, or by the optional `trainParams` argument. If
546
+ * provided, `trainParams` can customize which parameter values participate in differentiation.
547
+ *
548
+ * The result of this operation must be written into the `gradients` argument buffer.
549
+ *
550
+ * @param gradients The vector of pre-allocated `DataView<double, 1>*`
551
+ * to store the flattened Jacobian (one gradient per cached observable).
552
+ * @param trainParams The vector of trainable parameters; if empty, all parameters
553
+ * would be assumed trainable.
554
+ */
555
+ virtual void Gradient(std::vector<DataView<double, 1>> &gradients,
556
+ const std::vector<size_t> &trainParams)
557
+ {
558
+ RT_FAIL("Differentiation is unsupported by device");
559
+ }
560
+
561
+ /**
562
+ * @brief (Optional) Start recording a quantum tape if provided.
563
+ *
564
+ * See `Gradient` for additional information.
565
+ */
566
+ virtual void StartTapeRecording() { RT_FAIL("Differentiation is unsupported by device"); }
567
+
568
+ /**
569
+ * @brief (Optional) Stop recording a quantum tape if provided.
570
+ *
571
+ * See `Gradient` for additional information.
572
+ */
573
+ virtual void StopTapeRecording() { RT_FAIL("Differentiation is unsupported by device"); }
574
+ };
575
+
576
+ } // namespace Catalyst::Runtime