tequila-basic 1.9.9__py3-none-any.whl → 1.9.10__py3-none-any.whl
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.
- tequila/__init__.py +29 -14
- tequila/apps/__init__.py +14 -5
- tequila/apps/_unary_state_prep_impl.py +145 -112
- tequila/apps/adapt/__init__.py +9 -1
- tequila/apps/adapt/adapt.py +154 -113
- tequila/apps/krylov/__init__.py +1 -1
- tequila/apps/krylov/krylov.py +23 -21
- tequila/apps/robustness/helpers.py +10 -6
- tequila/apps/robustness/interval.py +238 -156
- tequila/apps/unary_state_prep.py +29 -23
- tequila/autograd_imports.py +8 -5
- tequila/circuit/__init__.py +2 -1
- tequila/circuit/_gates_impl.py +135 -67
- tequila/circuit/circuit.py +163 -79
- tequila/circuit/compiler.py +114 -105
- tequila/circuit/gates.py +288 -120
- tequila/circuit/gradient.py +35 -23
- tequila/circuit/noise.py +83 -74
- tequila/circuit/postselection.py +120 -0
- tequila/circuit/pyzx.py +10 -6
- tequila/circuit/qasm.py +201 -83
- tequila/circuit/qpic.py +63 -61
- tequila/grouping/binary_rep.py +148 -146
- tequila/grouping/binary_utils.py +84 -75
- tequila/grouping/compile_groups.py +334 -230
- tequila/grouping/ev_utils.py +77 -41
- tequila/grouping/fermionic_functions.py +383 -308
- tequila/grouping/fermionic_methods.py +170 -123
- tequila/grouping/overlapping_methods.py +69 -52
- tequila/hamiltonian/paulis.py +12 -13
- tequila/hamiltonian/paulistring.py +1 -1
- tequila/hamiltonian/qubit_hamiltonian.py +45 -35
- tequila/ml/__init__.py +1 -0
- tequila/ml/interface_torch.py +19 -16
- tequila/ml/ml_api.py +11 -10
- tequila/ml/utils_ml.py +12 -11
- tequila/objective/__init__.py +8 -3
- tequila/objective/braket.py +55 -47
- tequila/objective/objective.py +87 -55
- tequila/objective/qtensor.py +36 -27
- tequila/optimizers/__init__.py +31 -23
- tequila/optimizers/_containers.py +11 -7
- tequila/optimizers/optimizer_base.py +111 -83
- tequila/optimizers/optimizer_gd.py +258 -231
- tequila/optimizers/optimizer_gpyopt.py +56 -42
- tequila/optimizers/optimizer_scipy.py +157 -112
- tequila/quantumchemistry/__init__.py +66 -38
- tequila/quantumchemistry/chemistry_tools.py +393 -209
- tequila/quantumchemistry/encodings.py +121 -13
- tequila/quantumchemistry/madness_interface.py +170 -96
- tequila/quantumchemistry/orbital_optimizer.py +86 -41
- tequila/quantumchemistry/psi4_interface.py +166 -97
- tequila/quantumchemistry/pyscf_interface.py +70 -23
- tequila/quantumchemistry/qc_base.py +866 -414
- tequila/simulators/__init__.py +0 -3
- tequila/simulators/simulator_api.py +247 -105
- tequila/simulators/simulator_aqt.py +102 -0
- tequila/simulators/simulator_base.py +147 -53
- tequila/simulators/simulator_cirq.py +58 -42
- tequila/simulators/simulator_cudaq.py +600 -0
- tequila/simulators/simulator_ddsim.py +390 -0
- tequila/simulators/simulator_mqp.py +30 -0
- tequila/simulators/simulator_pyquil.py +190 -171
- tequila/simulators/simulator_qibo.py +95 -87
- tequila/simulators/simulator_qiskit.py +119 -107
- tequila/simulators/simulator_qlm.py +52 -26
- tequila/simulators/simulator_qulacs.py +74 -52
- tequila/simulators/simulator_spex.py +95 -60
- tequila/simulators/simulator_symbolic.py +6 -5
- tequila/simulators/test_spex_simulator.py +8 -11
- tequila/tools/convenience.py +4 -4
- tequila/tools/qng.py +72 -64
- tequila/tools/random_generators.py +38 -34
- tequila/utils/bitstrings.py +7 -7
- tequila/utils/exceptions.py +19 -5
- tequila/utils/joined_transformation.py +8 -10
- tequila/utils/keymap.py +0 -5
- tequila/utils/misc.py +6 -4
- tequila/version.py +1 -1
- tequila/wavefunction/qubit_wavefunction.py +47 -28
- {tequila_basic-1.9.9.dist-info → tequila_basic-1.9.10.dist-info}/METADATA +13 -16
- tequila_basic-1.9.10.dist-info/RECORD +93 -0
- {tequila_basic-1.9.9.dist-info → tequila_basic-1.9.10.dist-info}/WHEEL +1 -1
- tequila_basic-1.9.9.dist-info/RECORD +0 -88
- {tequila_basic-1.9.9.dist-info → tequila_basic-1.9.10.dist-info}/licenses/LICENSE +0 -0
- {tequila_basic-1.9.9.dist-info → tequila_basic-1.9.10.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,600 @@
|
|
1
|
+
import cudaq
|
2
|
+
import numpy as np
|
3
|
+
from cudaq import spin
|
4
|
+
import numbers
|
5
|
+
import numpy
|
6
|
+
from tequila import TequilaException
|
7
|
+
from tequila.utils.bitstrings import BitNumbering, reverse_int_bits
|
8
|
+
from tequila.wavefunction.qubit_wavefunction import QubitWaveFunction
|
9
|
+
from tequila.simulators.simulator_base import BackendCircuit, BackendExpectationValue
|
10
|
+
from typing import Union, Optional
|
11
|
+
|
12
|
+
"""
|
13
|
+
Developer Note:
|
14
|
+
Cudaq does not have objects for circuits and gates. Instead it uses quantum kernels. These only allow the input of
|
15
|
+
arguments of certain types (and are therefore quite picky), i.e. ints, floats or a list[int], list[float].
|
16
|
+
Due to this restriction the circuit is a list of these primitive types and the used gates are encoded.
|
17
|
+
"""
|
18
|
+
|
19
|
+
|
20
|
+
class TequilaCudaqException(TequilaException):
|
21
|
+
def __str__(self):
|
22
|
+
return "Error in cudaq (cuda-quantum) backend:" + self.message
|
23
|
+
|
24
|
+
|
25
|
+
class BackendCircuitCudaq(BackendCircuit):
|
26
|
+
"""
|
27
|
+
Class representing circuits compiled to cudaq (cuda-quantum of NVIDIA).
|
28
|
+
See BackendCircuit for documentation of features and methods inherited therefrom
|
29
|
+
|
30
|
+
Attributes
|
31
|
+
----------
|
32
|
+
counter:
|
33
|
+
counts how many distinct sympy.Symbol objects are employed in the circuit.
|
34
|
+
has_noise:
|
35
|
+
whether or not the circuit is noisy. needed by the expectationvalue to do sampling properly.
|
36
|
+
noise_lookup: dict:
|
37
|
+
dict mapping strings to lists of constructors for cirq noise channel objects.
|
38
|
+
op_lookup: dict:
|
39
|
+
dictionary mapping strings (tequila gate names) to cirq.ops objects.
|
40
|
+
variables: list:
|
41
|
+
a list of the qulacs variables of the circuit.
|
42
|
+
|
43
|
+
Methods
|
44
|
+
-------
|
45
|
+
add_noise_to_circuit:
|
46
|
+
apply a tequila NoiseModel to a qulacs circuit, by translating the NoiseModel's instructions into noise gates.
|
47
|
+
"""
|
48
|
+
|
49
|
+
compiler_arguments = {
|
50
|
+
"trotterized": True,
|
51
|
+
"swap": True,
|
52
|
+
"multitarget": True,
|
53
|
+
"controlled_rotation": True, # needed for gates depending on variables
|
54
|
+
"generalized_rotation": True,
|
55
|
+
"exponential_pauli": True,
|
56
|
+
"controlled_exponential_pauli": True,
|
57
|
+
"phase": True,
|
58
|
+
"power": True,
|
59
|
+
"hadamard_power": True,
|
60
|
+
"controlled_power": True,
|
61
|
+
"controlled_phase": True,
|
62
|
+
"toffoli": True,
|
63
|
+
"phase_to_z": True,
|
64
|
+
"cc_max": True,
|
65
|
+
}
|
66
|
+
numbering = BitNumbering.LSB
|
67
|
+
supports_generic_initialization = True
|
68
|
+
|
69
|
+
def __init__(self, abstract_circuit, noise=None, *args, **kwargs):
|
70
|
+
"""
|
71
|
+
|
72
|
+
Parameters
|
73
|
+
----------
|
74
|
+
abstract_circuit: QCircuit:
|
75
|
+
the circuit to compile to cudaq
|
76
|
+
noise: optional:
|
77
|
+
noise to apply to the circuit.
|
78
|
+
args
|
79
|
+
kwargs
|
80
|
+
"""
|
81
|
+
|
82
|
+
# gates encodings
|
83
|
+
self.op_lookup = {
|
84
|
+
# primitives
|
85
|
+
"I": None,
|
86
|
+
"X": 1,
|
87
|
+
"Y": 2,
|
88
|
+
"Z": 3,
|
89
|
+
"H": 4,
|
90
|
+
# simple 1-qubit rotations
|
91
|
+
"Rx": 5,
|
92
|
+
"Ry": 6,
|
93
|
+
"Rz": 7,
|
94
|
+
# phase gates
|
95
|
+
"S": 8,
|
96
|
+
"T": 9,
|
97
|
+
# controlled primitives
|
98
|
+
"CNOT": 10,
|
99
|
+
"Cx": 11,
|
100
|
+
"Cy": 12,
|
101
|
+
"Cz": 13,
|
102
|
+
# controlled rotations
|
103
|
+
"CRx": 14,
|
104
|
+
"CRy": 15,
|
105
|
+
"CRz": 16,
|
106
|
+
# other types of gates
|
107
|
+
"SWAP": 17,
|
108
|
+
"Measure": 18,
|
109
|
+
"Exp-Pauli": None,
|
110
|
+
}
|
111
|
+
|
112
|
+
# instantiate a cudaq circuit as a list
|
113
|
+
self.circuit = self.initialize_circuit()
|
114
|
+
self.measurements = None
|
115
|
+
self.variables = []
|
116
|
+
super().__init__(abstract_circuit=abstract_circuit, noise=noise, *args, **kwargs)
|
117
|
+
self.has_noise = False
|
118
|
+
|
119
|
+
@cudaq.kernel
|
120
|
+
def state_modifier_from_initial_state( ## create a state based on an EXISTING PREVIOUS STATE i.e. |1010010>
|
121
|
+
number_of_qubits: int,
|
122
|
+
gate_encodings: list[int],
|
123
|
+
target_qubits: list[int],
|
124
|
+
angles: list[float],
|
125
|
+
control_qubits: list[int],
|
126
|
+
iteration_length: int,
|
127
|
+
inital_state: cudaq.State,
|
128
|
+
):
|
129
|
+
"""
|
130
|
+
This function applies a circuit to an EXISTING QUANTUM STATE with a given number of qubits,
|
131
|
+
i.e. |10110>
|
132
|
+
- unlike "state_modifier" this function needs a special preperation of the gates to apply to the state
|
133
|
+
and therefore works with prepare_state_from_integer
|
134
|
+
|
135
|
+
The function collects the gates to apply, which are stored in a list (cudaq's circuit "object")
|
136
|
+
applies them in the given order to the state.
|
137
|
+
|
138
|
+
These circuits support:
|
139
|
+
1. single-qubit gates like: X, Y, Z, H, S, T as well as controlled variantes of them with up
|
140
|
+
to one control-qubit
|
141
|
+
2. parametrized single qubit gates like: Rx, Ry, Rz with a given angle
|
142
|
+
"""
|
143
|
+
|
144
|
+
# create a quantum state based on a given initial state
|
145
|
+
s = cudaq.qvector(inital_state)
|
146
|
+
|
147
|
+
for index in range(iteration_length):
|
148
|
+
encoding = gate_encodings[index]
|
149
|
+
target = target_qubits[index]
|
150
|
+
angle = angles[index]
|
151
|
+
control = control_qubits[index]
|
152
|
+
|
153
|
+
# x gate
|
154
|
+
if encoding == 1:
|
155
|
+
if control != -1:
|
156
|
+
x.ctrl(s[control], s[target])
|
157
|
+
else:
|
158
|
+
x(s[target])
|
159
|
+
# y gate
|
160
|
+
elif encoding == 2:
|
161
|
+
if control != -1:
|
162
|
+
y.ctrl(s[control], s[target])
|
163
|
+
else:
|
164
|
+
y(s[target])
|
165
|
+
# z gate
|
166
|
+
elif encoding == 3:
|
167
|
+
if control != -1:
|
168
|
+
z.ctrl(s[control], s[target])
|
169
|
+
else:
|
170
|
+
z(s[target])
|
171
|
+
# h gate
|
172
|
+
elif encoding == 4:
|
173
|
+
if control != -1:
|
174
|
+
h.ctrl(s[control], s[target])
|
175
|
+
else:
|
176
|
+
h(s[target])
|
177
|
+
# Rx gate
|
178
|
+
elif encoding == 5:
|
179
|
+
if control != -1:
|
180
|
+
pass
|
181
|
+
else:
|
182
|
+
rx(angle, s[target])
|
183
|
+
# Rx gate
|
184
|
+
elif encoding == 6:
|
185
|
+
if control != -1:
|
186
|
+
pass
|
187
|
+
else:
|
188
|
+
ry(angle, s[target])
|
189
|
+
# Rx gate
|
190
|
+
elif encoding == 7:
|
191
|
+
if control != -1:
|
192
|
+
pass
|
193
|
+
else:
|
194
|
+
rz(angle, s[target])
|
195
|
+
# S gate
|
196
|
+
elif encoding == 8:
|
197
|
+
if control != -1:
|
198
|
+
s.ctrl(s[control], s[target])
|
199
|
+
else:
|
200
|
+
s(s[target])
|
201
|
+
# T gate
|
202
|
+
elif encoding == 9:
|
203
|
+
if control != -1:
|
204
|
+
t.ctrl(s[control], s[target])
|
205
|
+
else:
|
206
|
+
t(s[target])
|
207
|
+
|
208
|
+
def prepare_circuit_for_state_modifier(self):
|
209
|
+
"""
|
210
|
+
this function decomposes the circuit elements for later usage in state_modifier, which uses the
|
211
|
+
cudaq annotation @cudaq.kernel.
|
212
|
+
|
213
|
+
Has to be done this way since other functions for expectation value for example like cudaq.observe have special syntax
|
214
|
+
and accept parameters and the state modifier itself
|
215
|
+
"""
|
216
|
+
|
217
|
+
# prepare parameters for usage in kernal since "self. " access doesnt work within kernels
|
218
|
+
number_of_qubits = self.n_qubits
|
219
|
+
|
220
|
+
circuit = None
|
221
|
+
if isinstance(self, BackendCircuitCudaq):
|
222
|
+
circuit = self.circuit
|
223
|
+
elif isinstance(self, BackendExpectationValueCudaq):
|
224
|
+
circuit = self.U.circuit
|
225
|
+
|
226
|
+
if circuit is None:
|
227
|
+
raise ValueError("wrong attribute access in function - prepare_circuit_for_state_modifier")
|
228
|
+
|
229
|
+
# get single lists from dict
|
230
|
+
gate_encodings = circuit["gate_encodings"]
|
231
|
+
target_qubits = circuit["target_qubits"]
|
232
|
+
# Cast from FixedVariable to float to avoid CudaQ MLIR type conversion problems
|
233
|
+
angles = [float(angle) for angle in circuit["angles"]]
|
234
|
+
control_qubits = circuit["control_qubits"]
|
235
|
+
|
236
|
+
iteration_length = None
|
237
|
+
|
238
|
+
if len(gate_encodings) == len(target_qubits) == len(angles) == len(control_qubits):
|
239
|
+
iteration_length = len(gate_encodings)
|
240
|
+
else:
|
241
|
+
raise ValueError("length of params lists in prepare_circuit_for_modifier has sto match")
|
242
|
+
|
243
|
+
if iteration_length is None:
|
244
|
+
raise ValueError("iter length from prepare_circuit shall not be None")
|
245
|
+
|
246
|
+
return (number_of_qubits, gate_encodings, target_qubits, angles, control_qubits, iteration_length)
|
247
|
+
|
248
|
+
def do_simulate(self, variables, initial_state, *args, **kwargs):
|
249
|
+
"""
|
250
|
+
Helper function to perform simulation.
|
251
|
+
|
252
|
+
- performs simulation in the following order:
|
253
|
+
1. create a quantum state based on a given input integer
|
254
|
+
2. apply a given quantum circuit on this state
|
255
|
+
3. gate the amplitudes of the resulting wave function (wfn)
|
256
|
+
after application of the circuit
|
257
|
+
|
258
|
+
|
259
|
+
Parameters
|
260
|
+
----------
|
261
|
+
variables: dict:
|
262
|
+
variables to supply to the circuit.
|
263
|
+
initial_state:
|
264
|
+
information indicating the initial state on which the circuit should act.
|
265
|
+
args
|
266
|
+
kwargs
|
267
|
+
|
268
|
+
Returns
|
269
|
+
-------
|
270
|
+
QubitWaveFunction:
|
271
|
+
QubitWaveFunction representing result of the simulation.
|
272
|
+
"""
|
273
|
+
|
274
|
+
# prepare the circuit to apply onto the state made from an integer
|
275
|
+
(number_of_qubits, gate_encodings, target_qubits, angles, control_qubits, iteration_length) = (
|
276
|
+
BackendCircuitCudaq.prepare_circuit_for_state_modifier(self)
|
277
|
+
)
|
278
|
+
|
279
|
+
# apply state modifier (circuit) onto state and get amplitudes
|
280
|
+
vector = cudaq.get_state(
|
281
|
+
self.state_modifier_from_initial_state,
|
282
|
+
number_of_qubits,
|
283
|
+
gate_encodings,
|
284
|
+
target_qubits,
|
285
|
+
angles,
|
286
|
+
control_qubits,
|
287
|
+
iteration_length,
|
288
|
+
initialize_state(initial_state, n_qubits=self.n_qubits),
|
289
|
+
)
|
290
|
+
|
291
|
+
wfn = QubitWaveFunction.from_array(array=np.array(vector), numbering=self.numbering)
|
292
|
+
|
293
|
+
return wfn
|
294
|
+
|
295
|
+
def initialize_circuit(self, *args, **kwargs):
|
296
|
+
"""
|
297
|
+
return an empty circuit.
|
298
|
+
for cudaq return an empty dict as the main data structure containing 4 key parameters:
|
299
|
+
1. encodings of gates as integers
|
300
|
+
2. indices of target qubits to which the gates are applied
|
301
|
+
3. angles in case of parametrized gates
|
302
|
+
4. index of a potential control qubit
|
303
|
+
|
304
|
+
Parameters
|
305
|
+
----------
|
306
|
+
args
|
307
|
+
kwargs
|
308
|
+
|
309
|
+
Returns
|
310
|
+
-------
|
311
|
+
"""
|
312
|
+
|
313
|
+
# as for now circuit supports only CNOT, single qubit gates and single rotations
|
314
|
+
circuit = {"gate_encodings": [], "target_qubits": [], "angles": [], "control_qubits": []}
|
315
|
+
|
316
|
+
return circuit
|
317
|
+
|
318
|
+
def add_parametrized_gate(self, gate, circuit, variables, *args, **kwargs):
|
319
|
+
"""
|
320
|
+
add a parametrized gate.
|
321
|
+
- for this fetch params like 1. angle 2. gate encoding 3. target qubits
|
322
|
+
and store them in the corresponding lists within the circuit (as a dict)
|
323
|
+
|
324
|
+
Parameters
|
325
|
+
----------
|
326
|
+
gate: QGateImpl:
|
327
|
+
the gate to add to the circuit.
|
328
|
+
circuit:
|
329
|
+
the circuit to which the gate is to be added
|
330
|
+
variables:
|
331
|
+
dict that tells values of variables; needed IFF the gate is an ExpPauli gate.
|
332
|
+
args
|
333
|
+
kwargs
|
334
|
+
|
335
|
+
Returns
|
336
|
+
-------
|
337
|
+
None
|
338
|
+
"""
|
339
|
+
gate_encoding = self.op_lookup[gate.name]
|
340
|
+
|
341
|
+
# target qubits as a list
|
342
|
+
target_qubits = [self.qubit(t) for t in gate.target]
|
343
|
+
|
344
|
+
if len(target_qubits) != 1:
|
345
|
+
raise ValueError(" at most 1 target qubits is supported, NOT MORE ")
|
346
|
+
|
347
|
+
# save all control qubits into a list
|
348
|
+
control_qubits = []
|
349
|
+
if gate.is_controlled():
|
350
|
+
for control in gate.control:
|
351
|
+
control_qubits.append(self.qubit(control))
|
352
|
+
|
353
|
+
# more than one control currently not supported
|
354
|
+
if len(control_qubits) > 1:
|
355
|
+
raise ValueError("at most 1 control qubit is supported for cudaq, NOT MORE ")
|
356
|
+
|
357
|
+
# extract information about angle - one per gate
|
358
|
+
angle = gate.parameter(variables)
|
359
|
+
|
360
|
+
# if the gate has one control qubit append its index to the list of controls
|
361
|
+
if len(control_qubits) == 1:
|
362
|
+
circuit["control_qubits"].append(control_qubits[0])
|
363
|
+
elif len(control_qubits) == 0:
|
364
|
+
circuit["control_qubits"].append(-1)
|
365
|
+
|
366
|
+
# append the angle of the gate
|
367
|
+
circuit["angles"].append(angle)
|
368
|
+
circuit["gate_encodings"].append(gate_encoding)
|
369
|
+
circuit["target_qubits"].append(target_qubits[0])
|
370
|
+
|
371
|
+
def add_basic_gate(self, gate, circuit, *args, **kwargs):
|
372
|
+
"""
|
373
|
+
add an unparametrized gate to the circuit.
|
374
|
+
Parameters
|
375
|
+
- add a gate with 1. control 2. target 3. encoding
|
376
|
+
since these gates are not parametrized save an angle of 0.0 into the angles list
|
377
|
+
|
378
|
+
----------
|
379
|
+
gate: QGateImpl:
|
380
|
+
the gate to be added to the circuit.
|
381
|
+
circuit:
|
382
|
+
the circuit, to which a gate is to be added.
|
383
|
+
args
|
384
|
+
kwargs
|
385
|
+
|
386
|
+
Returns
|
387
|
+
-------
|
388
|
+
None
|
389
|
+
"""
|
390
|
+
|
391
|
+
# fetch the gate encoding
|
392
|
+
gate_encoding = self.op_lookup[gate.name]
|
393
|
+
|
394
|
+
# target qubits as a list
|
395
|
+
target_qubits = [self.qubit(t) for t in gate.target]
|
396
|
+
|
397
|
+
if len(target_qubits) != 1:
|
398
|
+
raise ValueError(" at most 1 target qubits is supported, NOT MORE ")
|
399
|
+
|
400
|
+
# save all control qubits into a list
|
401
|
+
control_qubits = []
|
402
|
+
if gate.is_controlled():
|
403
|
+
for control in gate.control:
|
404
|
+
control_qubits.append(self.qubit(control))
|
405
|
+
|
406
|
+
# more than one control currently not supported
|
407
|
+
if len(control_qubits) > 1:
|
408
|
+
raise ValueError("at must 1 control qubit is supported, NOT MORE ")
|
409
|
+
|
410
|
+
# if the gate has one control qubit append its index to the list of the controls
|
411
|
+
if len(control_qubits) == 1:
|
412
|
+
circuit["control_qubits"].append(control_qubits[0])
|
413
|
+
elif len(control_qubits) == 0:
|
414
|
+
circuit["control_qubits"].append(-1)
|
415
|
+
|
416
|
+
circuit["target_qubits"].append(target_qubits[0])
|
417
|
+
circuit["gate_encodings"].append(gate_encoding)
|
418
|
+
|
419
|
+
# since primitive gates have to angle add zero to the list of angles
|
420
|
+
# so all 4 lists have the same length for iteration
|
421
|
+
circuit["angles"].append(0.0)
|
422
|
+
|
423
|
+
|
424
|
+
class BackendExpectationValueCudaq(BackendExpectationValue):
|
425
|
+
"""
|
426
|
+
Class representing Expectation Values compiled for Cudaq.
|
427
|
+
|
428
|
+
Overrides some methods of BackendExpectationValue, which should be seen for details.
|
429
|
+
"""
|
430
|
+
|
431
|
+
use_mapping = True
|
432
|
+
BackendCircuitType = BackendCircuitCudaq
|
433
|
+
|
434
|
+
def simulate(self, variables, initial_state: Union[int, QubitWaveFunction], *args, **kwargs) -> np.array:
|
435
|
+
"""
|
436
|
+
Perform simulation of this expectationvalue.
|
437
|
+
Parameters
|
438
|
+
----------
|
439
|
+
variables:
|
440
|
+
variables, to be supplied to the underlying circuit.
|
441
|
+
args
|
442
|
+
kwargs
|
443
|
+
|
444
|
+
Returns
|
445
|
+
-------
|
446
|
+
numpy.array:
|
447
|
+
the result of simulation as an array.
|
448
|
+
"""
|
449
|
+
# fast return if possible
|
450
|
+
if self.H is None:
|
451
|
+
return np.asarray([0.0])
|
452
|
+
elif len(self.H) == 0:
|
453
|
+
return np.asarray([0.0])
|
454
|
+
elif isinstance(self.H, numbers.Number):
|
455
|
+
return np.asarray[self.H]
|
456
|
+
|
457
|
+
# update the variables for correct expectation value evaluation
|
458
|
+
self.U.update_variables(variables)
|
459
|
+
|
460
|
+
# prepare circuit to apply onto state
|
461
|
+
(number_of_qubits, gate_encodings, target_qubits, angles, control_qubits, iteration_length) = (
|
462
|
+
BackendCircuitCudaq.prepare_circuit_for_state_modifier(self)
|
463
|
+
)
|
464
|
+
|
465
|
+
# array containing results of exp. value simulation
|
466
|
+
resulting_expectation_values = []
|
467
|
+
|
468
|
+
# go over all given hamiltonians
|
469
|
+
for hamiltonian in self.H:
|
470
|
+
# compute expectation value between hamiltonian and state
|
471
|
+
expectation_value = cudaq.observe(
|
472
|
+
BackendCircuitCudaq.state_modifier_from_initial_state,
|
473
|
+
hamiltonian,
|
474
|
+
number_of_qubits,
|
475
|
+
gate_encodings,
|
476
|
+
target_qubits,
|
477
|
+
angles,
|
478
|
+
control_qubits,
|
479
|
+
iteration_length,
|
480
|
+
initialize_state(initial_state, n_qubits=self.n_qubits),
|
481
|
+
).expectation()
|
482
|
+
|
483
|
+
# store exp. val in results array
|
484
|
+
resulting_expectation_values.append(expectation_value)
|
485
|
+
|
486
|
+
return np.asarray(resulting_expectation_values)
|
487
|
+
|
488
|
+
def XX__XX__initialize_hamiltonian_old_implementation(self, hamiltonians):
|
489
|
+
"""
|
490
|
+
Convert reduced hamiltonians to native Cudaq types for efficient expectation value evaluation.
|
491
|
+
|
492
|
+
Parameters
|
493
|
+
----------
|
494
|
+
hamiltonians:
|
495
|
+
an interable set of hamiltonian objects.
|
496
|
+
|
497
|
+
Returns
|
498
|
+
-------
|
499
|
+
list:
|
500
|
+
initialized hamiltonian objects.
|
501
|
+
|
502
|
+
"""
|
503
|
+
# Map logical qubit labels to consecutive indices (0,1,2,...)
|
504
|
+
# this turns cases like [0,2,5] into [0,1,2] for cudaq
|
505
|
+
qubit_map = {}
|
506
|
+
for i, q in enumerate(self.U.abstract_circuit.qubits):
|
507
|
+
qubit_map[q] = i
|
508
|
+
|
509
|
+
list_of_initialized_hamiltonians = []
|
510
|
+
# assemble hamiltonian with cudaq "spin" objects
|
511
|
+
for hamiltonian in hamiltonians:
|
512
|
+
hamiltonian_as_spin = 0 # Initialize per Hamiltonian
|
513
|
+
for paulistring in hamiltonian.paulistrings:
|
514
|
+
term = 1 # Start with identity
|
515
|
+
for qubit, gate in paulistring.items():
|
516
|
+
mapped_qubit = qubit_map[qubit]
|
517
|
+
if gate == "X":
|
518
|
+
term *= spin.x(mapped_qubit)
|
519
|
+
elif gate == "Y":
|
520
|
+
term *= spin.y(mapped_qubit)
|
521
|
+
elif gate == "Z":
|
522
|
+
term *= spin.z(mapped_qubit)
|
523
|
+
term *= paulistring._coeff # Apply coefficient
|
524
|
+
hamiltonian_as_spin += term # Accumulate terms
|
525
|
+
list_of_initialized_hamiltonians.append(hamiltonian_as_spin)
|
526
|
+
return list_of_initialized_hamiltonians
|
527
|
+
|
528
|
+
def initialize_hamiltonian(self, hamiltonians):
|
529
|
+
"""
|
530
|
+
Convert reduced hamiltonians to native Cudaq types for efficient expectation value evaluation.
|
531
|
+
|
532
|
+
Parameters
|
533
|
+
----------
|
534
|
+
hamiltonians:
|
535
|
+
an interable set of hamiltonian objects.
|
536
|
+
|
537
|
+
Returns
|
538
|
+
-------
|
539
|
+
list:
|
540
|
+
initialized hamiltonian objects.
|
541
|
+
|
542
|
+
"""
|
543
|
+
# Map logical qubit labels to consecutive indices
|
544
|
+
qubit_map = {q: i for i, q in enumerate(self.U.abstract_circuit.qubits)}
|
545
|
+
list_of_initialized_hamiltonians = []
|
546
|
+
|
547
|
+
# assemble hamiltonian with cudaq "spin" objects
|
548
|
+
for hamiltonian in hamiltonians:
|
549
|
+
hamiltonian_as_spin = None
|
550
|
+
for paulistring in hamiltonian.paulistrings:
|
551
|
+
# store the spin operators in a list
|
552
|
+
ops = []
|
553
|
+
for qubit, gate in paulistring.items():
|
554
|
+
mapped_qubit = qubit_map[qubit]
|
555
|
+
if gate == "X":
|
556
|
+
ops.append(spin.x(mapped_qubit))
|
557
|
+
elif gate == "Y":
|
558
|
+
ops.append(spin.y(mapped_qubit))
|
559
|
+
elif gate == "Z":
|
560
|
+
ops.append(spin.z(mapped_qubit))
|
561
|
+
# If no operators, skip to next paulistring
|
562
|
+
if ops:
|
563
|
+
# start with the first spin operator (instead of identity "1")
|
564
|
+
term = ops[0]
|
565
|
+
for op in ops[1:]:
|
566
|
+
term *= op
|
567
|
+
term *= paulistring._coeff
|
568
|
+
else:
|
569
|
+
# If no operators, create a "zero operator" (identity with zero coefficient)
|
570
|
+
term = spin.z(0) * 0.0 + paulistring._coeff
|
571
|
+
if hamiltonian_as_spin is None:
|
572
|
+
hamiltonian_as_spin = term
|
573
|
+
else:
|
574
|
+
hamiltonian_as_spin += term
|
575
|
+
# If no paulistrings, set to "zero operator" (not float)
|
576
|
+
if hamiltonian_as_spin is None:
|
577
|
+
hamiltonian_as_spin = spin.z(0) * 0.0
|
578
|
+
list_of_initialized_hamiltonians.append(hamiltonian_as_spin)
|
579
|
+
return list_of_initialized_hamiltonians
|
580
|
+
|
581
|
+
|
582
|
+
def initialize_state(
|
583
|
+
state: Union[int, QubitWaveFunction], n_qubits: Optional[int] = None, numbering: BitNumbering = BitNumbering.LSB
|
584
|
+
) -> cudaq.State:
|
585
|
+
if isinstance(state, int):
|
586
|
+
if n_qubits is None:
|
587
|
+
raise ValueError("Need to specify n_qubits for initializing CudaQ basis state")
|
588
|
+
amplitudes = np.zeros(2**n_qubits, dtype=cudaq.complex())
|
589
|
+
# Reverse because Tequila uses MSB bit ordering by default, while CudaQ uses LSB
|
590
|
+
# TODO: It would be better to handle this somewhere else, e.g. by passing a BitString object
|
591
|
+
# in simulator_api (which stores its ordering), and then convert to the backend specific
|
592
|
+
# bit-ordering before passing this to the backends.
|
593
|
+
if numbering == BitNumbering.LSB:
|
594
|
+
index = reverse_int_bits(state, n_qubits)
|
595
|
+
amplitudes[index] = 1
|
596
|
+
return cudaq.State.from_data(amplitudes)
|
597
|
+
elif isinstance(state, QubitWaveFunction):
|
598
|
+
return cudaq.State.from_data(state.to_array(out_numbering=numbering).astype(cudaq.complex()))
|
599
|
+
else:
|
600
|
+
raise TypeError("State must be an int or QubitWaveFunction")
|