tequila-basic 1.9.8__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 +177 -88
- 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 +91 -56
- 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 +394 -203
- tequila/quantumchemistry/encodings.py +121 -13
- tequila/quantumchemistry/madness_interface.py +170 -96
- tequila/quantumchemistry/orbital_optimizer.py +86 -40
- 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 +258 -106
- tequila/simulators/simulator_aqt.py +102 -0
- tequila/simulators/simulator_base.py +156 -55
- 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 +124 -114
- tequila/simulators/simulator_qlm.py +52 -26
- tequila/simulators/simulator_qulacs.py +85 -59
- tequila/simulators/simulator_spex.py +464 -0
- tequila/simulators/simulator_symbolic.py +6 -5
- tequila/simulators/test_spex_simulator.py +208 -0
- tequila/tools/convenience.py +4 -4
- tequila/tools/qng.py +72 -64
- tequila/tools/random_generators.py +38 -34
- tequila/utils/bitstrings.py +13 -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 +52 -30
- {tequila_basic-1.9.8.dist-info → tequila_basic-1.9.10.dist-info}/METADATA +23 -17
- tequila_basic-1.9.10.dist-info/RECORD +93 -0
- {tequila_basic-1.9.8.dist-info → tequila_basic-1.9.10.dist-info}/WHEEL +1 -1
- tequila_basic-1.9.8.dist-info/RECORD +0 -86
- {tequila_basic-1.9.8.dist-info → tequila_basic-1.9.10.dist-info/licenses}/LICENSE +0 -0
- {tequila_basic-1.9.8.dist-info → tequila_basic-1.9.10.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,464 @@
|
|
1
|
+
from tequila.simulators.simulator_base import BackendExpectationValue, BackendCircuit
|
2
|
+
from tequila.wavefunction.qubit_wavefunction import QubitWaveFunction
|
3
|
+
from tequila.utils import TequilaException
|
4
|
+
from tequila.hamiltonian import PauliString
|
5
|
+
from tequila.circuit._gates_impl import ExponentialPauliGateImpl, QGateImpl, RotationGateImpl, QubitHamiltonian
|
6
|
+
from tequila.circuit.gates import QubitExcitationImpl
|
7
|
+
from tequila import BitNumbering
|
8
|
+
|
9
|
+
|
10
|
+
import hashlib
|
11
|
+
import numpy
|
12
|
+
import os
|
13
|
+
import spex_tequila
|
14
|
+
import gc
|
15
|
+
|
16
|
+
numbering = BitNumbering.MSB
|
17
|
+
|
18
|
+
|
19
|
+
class TequilaSpexException(TequilaException):
|
20
|
+
"""Custom exception for SPEX simulator errors"""
|
21
|
+
|
22
|
+
pass
|
23
|
+
|
24
|
+
|
25
|
+
def extract_pauli_dict(ps):
|
26
|
+
"""
|
27
|
+
Extract qubit:operator mapping from PauliString/QubitHamiltonian
|
28
|
+
Args:
|
29
|
+
ps: PauliString or single-term QubitHamiltonian
|
30
|
+
Returns:
|
31
|
+
dict: {qubit: 'X'/'Y'/'Z'}
|
32
|
+
"""
|
33
|
+
|
34
|
+
if isinstance(ps, PauliString):
|
35
|
+
return dict(ps.items())
|
36
|
+
if isinstance(ps, QubitHamiltonian) and len(ps.paulistrings) == 1:
|
37
|
+
return dict(ps.paulistrings[0].items())
|
38
|
+
raise TequilaSpexException("Unsupported generator type")
|
39
|
+
|
40
|
+
|
41
|
+
def circuit_hash(abstract_circuit, variables=None):
|
42
|
+
"""
|
43
|
+
Create MD5 hash for circuit caching
|
44
|
+
Uses gate types, targets, controls and generators for uniqueness
|
45
|
+
"""
|
46
|
+
sha = hashlib.md5()
|
47
|
+
if abstract_circuit is None:
|
48
|
+
return None
|
49
|
+
for g in abstract_circuit.gates:
|
50
|
+
gate_str = f"{type(g).__name__}:{g.name}:{g.target}:{g.control}:{g.generator}:{getattr(g, 'parameter', None)}\n"
|
51
|
+
sha.update(gate_str.encode("utf-8"))
|
52
|
+
if variables:
|
53
|
+
for key, value in sorted(variables.items()):
|
54
|
+
sha.update(f"{key}:{value}\n".encode("utf-8"))
|
55
|
+
return sha.hexdigest()
|
56
|
+
|
57
|
+
|
58
|
+
def copy_exp_pauli_term(term):
|
59
|
+
"""Create a copy of a ExpPauliTerm."""
|
60
|
+
new_term = spex_tequila.ExpPauliTerm()
|
61
|
+
if hasattr(term, "pauli_map"):
|
62
|
+
new_term.pauli_map = dict(term.pauli_map)
|
63
|
+
if hasattr(term, "angle"):
|
64
|
+
new_term.angle = term.angle
|
65
|
+
return new_term
|
66
|
+
|
67
|
+
|
68
|
+
class BackendCircuitSpex(BackendCircuit):
|
69
|
+
"""SPEX circuit implementation using sparse state representation"""
|
70
|
+
|
71
|
+
# Circuit compilation configuration
|
72
|
+
compiler_arguments = {
|
73
|
+
"multitarget": True,
|
74
|
+
"multicontrol": True,
|
75
|
+
"trotterized": True,
|
76
|
+
"generalized_rotation": True,
|
77
|
+
"exponential_pauli": False,
|
78
|
+
"controlled_exponential_pauli": True,
|
79
|
+
"hadamard_power": True,
|
80
|
+
"controlled_power": True,
|
81
|
+
"power": True,
|
82
|
+
"toffoli": True,
|
83
|
+
"controlled_phase": True,
|
84
|
+
"phase": True,
|
85
|
+
"phase_to_z": True,
|
86
|
+
"controlled_rotation": True,
|
87
|
+
"swap": True,
|
88
|
+
"cc_max": True,
|
89
|
+
"ry_gate": True,
|
90
|
+
"y_gate": True,
|
91
|
+
"ch_gate": True,
|
92
|
+
}
|
93
|
+
|
94
|
+
def __init__(
|
95
|
+
self,
|
96
|
+
abstract_circuit=None,
|
97
|
+
variables=None,
|
98
|
+
num_threads=-1,
|
99
|
+
amplitude_threshold=1e-14,
|
100
|
+
angle_threshold=1e-14,
|
101
|
+
compress_qubits=True,
|
102
|
+
*args,
|
103
|
+
**kwargs,
|
104
|
+
):
|
105
|
+
# Circuit chaching
|
106
|
+
self.circuit_cache = {}
|
107
|
+
|
108
|
+
# Performance parameters
|
109
|
+
self.num_threads = num_threads
|
110
|
+
self.amplitude_threshold = amplitude_threshold
|
111
|
+
self.angle_threshold = angle_threshold
|
112
|
+
|
113
|
+
# State compression
|
114
|
+
self.compress_qubits = compress_qubits
|
115
|
+
self.n_qubits_compressed = None
|
116
|
+
self.hamiltonians = None
|
117
|
+
|
118
|
+
super().__init__(abstract_circuit=abstract_circuit, variables=variables, *args, **kwargs)
|
119
|
+
|
120
|
+
@property
|
121
|
+
def n_qubits(self):
|
122
|
+
"""Get number of qubits after compression (if enabled)"""
|
123
|
+
used = set()
|
124
|
+
if hasattr(self, "circuit") and self.circuit:
|
125
|
+
for term in self.circuit:
|
126
|
+
used.update(term.pauli_map.keys())
|
127
|
+
|
128
|
+
if self.abstract_circuit is not None and hasattr(self.abstract_circuit, "gates"):
|
129
|
+
for gate in self.abstract_circuit.gates:
|
130
|
+
if hasattr(gate, "target"):
|
131
|
+
if isinstance(gate.target, (list, tuple)):
|
132
|
+
used.update(gate.target)
|
133
|
+
else:
|
134
|
+
used.add(gate.target)
|
135
|
+
if hasattr(gate, "control") and gate.control:
|
136
|
+
if isinstance(gate.control, (list, tuple)):
|
137
|
+
used.update(gate.control)
|
138
|
+
else:
|
139
|
+
used.add(gate.control)
|
140
|
+
computed = max(used) + 1 if used else 0
|
141
|
+
return max(super().n_qubits, computed)
|
142
|
+
|
143
|
+
def initialize_circuit(self, *args, **kwargs):
|
144
|
+
return []
|
145
|
+
|
146
|
+
def create_circuit(self, abstract_circuit=None, variables=None, *args, **kwargs):
|
147
|
+
"""Compile circuit with caching using MD5 hash"""
|
148
|
+
if abstract_circuit is None:
|
149
|
+
abstract_circuit = self.abstract_circuit
|
150
|
+
|
151
|
+
key = circuit_hash(abstract_circuit, variables)
|
152
|
+
|
153
|
+
if key in self.circuit_cache:
|
154
|
+
return self.circuit_cache[key]
|
155
|
+
|
156
|
+
circuit = super().create_circuit(abstract_circuit=abstract_circuit, variables=variables, *args, **kwargs)
|
157
|
+
|
158
|
+
self.circuit_cache[key] = circuit
|
159
|
+
|
160
|
+
return circuit
|
161
|
+
|
162
|
+
def compress_qubit_indices(self):
|
163
|
+
"""
|
164
|
+
Optimize qubit indices by mapping used qubits to contiguous range
|
165
|
+
Reduces memory usage by eliminating unused qubit dimensions
|
166
|
+
"""
|
167
|
+
if not self.compress_qubits or not (hasattr(self, "circuit") and self.circuit):
|
168
|
+
return self.circuit, self.hamiltonians, self.n_qubits
|
169
|
+
|
170
|
+
new_circuit = [copy_exp_pauli_term(term) for term in self.circuit]
|
171
|
+
for term in new_circuit:
|
172
|
+
if hasattr(term, "pauli_map"):
|
173
|
+
term.pauli_map = dict(term.pauli_map)
|
174
|
+
|
175
|
+
new_hamiltonians = []
|
176
|
+
if self.hamiltonians is not None:
|
177
|
+
for ham in self.hamiltonians:
|
178
|
+
new_ham = []
|
179
|
+
for term, coeff in ham:
|
180
|
+
cloned = copy_exp_pauli_term(term)
|
181
|
+
cloned.pauli_map = dict(cloned.pauli_map)
|
182
|
+
new_ham.append((cloned, coeff))
|
183
|
+
new_hamiltonians.append(new_ham)
|
184
|
+
|
185
|
+
# Collect all qubits used in circuit and Hamiltonians
|
186
|
+
used_qubits = set()
|
187
|
+
for term in new_circuit:
|
188
|
+
used_qubits.update(term.pauli_map.keys())
|
189
|
+
|
190
|
+
if new_hamiltonians is not None:
|
191
|
+
for ham in new_hamiltonians:
|
192
|
+
for term, _ in ham:
|
193
|
+
used_qubits.update(term.pauli_map.keys())
|
194
|
+
|
195
|
+
if not used_qubits:
|
196
|
+
self.n_qubits_compressed = 0
|
197
|
+
return new_circuit, new_hamiltonians, 0
|
198
|
+
|
199
|
+
# Create qubit mapping and remap all terms
|
200
|
+
qubit_map = {old: new for new, old in enumerate(sorted(used_qubits))}
|
201
|
+
|
202
|
+
for term in new_circuit:
|
203
|
+
term.pauli_map = {qubit_map[old]: op for old, op in term.pauli_map.items()}
|
204
|
+
|
205
|
+
if new_hamiltonians is not None:
|
206
|
+
for ham in new_hamiltonians:
|
207
|
+
for term, _ in ham:
|
208
|
+
term.pauli_map = {qubit_map[old]: op for old, op in term.pauli_map.items()}
|
209
|
+
|
210
|
+
self.n_qubits_compressed = len(used_qubits)
|
211
|
+
|
212
|
+
return new_circuit, new_hamiltonians, self.n_qubits_compressed
|
213
|
+
|
214
|
+
def update_variables(self, variables, *args, **kwargs):
|
215
|
+
if variables is None:
|
216
|
+
variables = {}
|
217
|
+
super().update_variables(variables)
|
218
|
+
self.circuit = self.create_circuit(abstract_circuit=self.abstract_circuit, variables=variables)
|
219
|
+
|
220
|
+
def assign_parameter(self, param, variables):
|
221
|
+
if isinstance(param, (int, float, complex)):
|
222
|
+
return float(param)
|
223
|
+
if isinstance(param, str):
|
224
|
+
if param in variables:
|
225
|
+
return float(variables[param])
|
226
|
+
else:
|
227
|
+
raise TequilaSpexException(f"Variable '{param}' not found in variables")
|
228
|
+
if callable(param):
|
229
|
+
result = param(variables)
|
230
|
+
return float(result)
|
231
|
+
|
232
|
+
raise TequilaSpexException(f"Can't assign parameter '{param}'.")
|
233
|
+
|
234
|
+
def add_basic_gate(self, gate, circuit, *args, **kwargs):
|
235
|
+
"""Convert Tequila gates to SPEX exponential Pauli terms"""
|
236
|
+
exp_term = spex_tequila.ExpPauliTerm()
|
237
|
+
if isinstance(gate, ExponentialPauliGateImpl):
|
238
|
+
if self.angle_threshold is not None and abs(gate.parameter) < self.angle_threshold:
|
239
|
+
return
|
240
|
+
exp_term.pauli_map = extract_pauli_dict(gate.paulistring)
|
241
|
+
exp_term.angle = gate.parameter
|
242
|
+
circuit.append(exp_term)
|
243
|
+
|
244
|
+
elif isinstance(gate, RotationGateImpl):
|
245
|
+
if self.angle_threshold is not None and abs(gate.parameter) < self.angle_threshold:
|
246
|
+
return
|
247
|
+
exp_term.pauli_map = extract_pauli_dict(gate.generator)
|
248
|
+
exp_term.angle = gate.parameter
|
249
|
+
circuit.append(exp_term)
|
250
|
+
|
251
|
+
elif isinstance(gate, QubitExcitationImpl):
|
252
|
+
compiled_gate = gate.compile(exponential_pauli=True)
|
253
|
+
for sub_gate in compiled_gate.abstract_circuit.gates:
|
254
|
+
self.add_basic_gate(sub_gate, circuit, *args, **kwargs)
|
255
|
+
|
256
|
+
elif isinstance(gate, QGateImpl):
|
257
|
+
if gate.name.lower() in ["x", "y", "z"]:
|
258
|
+
# Convert standard gates to Pauli rotations
|
259
|
+
for ps in gate.make_generator(include_controls=True).paulistrings:
|
260
|
+
angle = numpy.pi * ps.coeff
|
261
|
+
if self.angle_threshold is not None and abs(angle) < self.angle_threshold:
|
262
|
+
continue
|
263
|
+
exp_term = spex_tequila.ExpPauliTerm()
|
264
|
+
exp_term.pauli_map = dict(ps.items())
|
265
|
+
exp_term.angle = angle
|
266
|
+
circuit.append(exp_term)
|
267
|
+
elif gate.name.lower() in ["h", "hadamard"]:
|
268
|
+
assert len(gate.target) == 1
|
269
|
+
target = gate.target[0]
|
270
|
+
for ps in ["-0.25*Y({q})", "Z({q})", "0.25*Y({q})"]:
|
271
|
+
ps = QubitHamiltonian(ps.format(q=gate.target[0])).paulistrings[0]
|
272
|
+
angle = numpy.pi * ps.coeff
|
273
|
+
exp_term = spex_tequila.ExpPauliTerm()
|
274
|
+
exp_term.pauli_map = dict(ps.items())
|
275
|
+
exp_term.angle = angle
|
276
|
+
circuit.append(exp_term)
|
277
|
+
else:
|
278
|
+
raise TequilaSpexException("{} not supported. Only x,y,z,h".format(gate.name.lower()))
|
279
|
+
|
280
|
+
else:
|
281
|
+
raise TequilaSpexException(
|
282
|
+
f"Unsupported gate object type: {type(gate)}. "
|
283
|
+
"All gates should be compiled to exponential pauli or rotation gates."
|
284
|
+
)
|
285
|
+
|
286
|
+
def add_parametrized_gate(self, gate, circuit, *args, **kwargs):
|
287
|
+
"""Convert Tequila parametrized gates to SPEX exponential Pauli terms"""
|
288
|
+
exp_term = spex_tequila.ExpPauliTerm()
|
289
|
+
if isinstance(gate, ExponentialPauliGateImpl):
|
290
|
+
angle = self.assign_parameter(gate.parameter, kwargs.get("variables", {}))
|
291
|
+
if self.angle_threshold is not None and abs(angle) < self.angle_threshold:
|
292
|
+
return
|
293
|
+
exp_term.pauli_map = extract_pauli_dict(gate.paulistring)
|
294
|
+
exp_term.angle = angle
|
295
|
+
circuit.append(exp_term)
|
296
|
+
|
297
|
+
elif isinstance(gate, RotationGateImpl):
|
298
|
+
angle = self.assign_parameter(gate.parameter, kwargs.get("variables", {}))
|
299
|
+
if self.angle_threshold is not None and abs(angle) < self.angle_threshold:
|
300
|
+
return
|
301
|
+
exp_term.pauli_map = extract_pauli_dict(gate.generator)
|
302
|
+
exp_term.angle = angle
|
303
|
+
circuit.append(exp_term)
|
304
|
+
|
305
|
+
elif isinstance(gate, QubitExcitationImpl):
|
306
|
+
compiled_gate = gate.compile(exponential_pauli=True)
|
307
|
+
for sub_gate in compiled_gate.abstract_circuit.gates:
|
308
|
+
self.add_parametrized_gate(sub_gate, circuit, *args, **kwargs)
|
309
|
+
|
310
|
+
elif isinstance(gate, QGateImpl):
|
311
|
+
for ps in gate.make_generator(include_controls=True).paulistrings:
|
312
|
+
if self.angle_threshold is not None and abs(gate.parameter) < self.angle_threshold:
|
313
|
+
continue
|
314
|
+
exp_term = spex_tequila.ExpPauliTerm()
|
315
|
+
exp_term.pauli_map = dict(ps.items())
|
316
|
+
exp_term.angle = gate.parameter
|
317
|
+
circuit.append(exp_term)
|
318
|
+
|
319
|
+
else:
|
320
|
+
raise TequilaSpexException(
|
321
|
+
f"Unsupported gate type: {type(gate)}. "
|
322
|
+
"Only Exponential Pauli and Rotation gates are allowed after compilation."
|
323
|
+
)
|
324
|
+
|
325
|
+
def do_simulate(self, variables, initial_state=0, *args, **kwargs) -> QubitWaveFunction:
|
326
|
+
"""
|
327
|
+
Simulate circuit and return final state
|
328
|
+
Args:
|
329
|
+
initial_state: Starting state (int or QubitWaveFunction)
|
330
|
+
Returns:
|
331
|
+
QubitWaveFunction: Sparse state representation
|
332
|
+
"""
|
333
|
+
|
334
|
+
if self.compress_qubits and self.n_qubits_compressed is not None and self.n_qubits_compressed > 0:
|
335
|
+
n_qubits = self.n_qubits_compressed
|
336
|
+
else:
|
337
|
+
n_qubits = self.n_qubits
|
338
|
+
|
339
|
+
# Initialize state
|
340
|
+
if isinstance(initial_state, (int, numpy.integer)):
|
341
|
+
if initial_state == 0:
|
342
|
+
state = spex_tequila.initialize_zero_state(n_qubits)
|
343
|
+
else:
|
344
|
+
state = {initial_state: 1.0 + 0j}
|
345
|
+
else:
|
346
|
+
# initial_state is already a QubitWaveFunction
|
347
|
+
state = {k: v for k, v in initial_state.raw_items()}
|
348
|
+
|
349
|
+
# Apply circuit with amplitude thresholding, -1.0 disables threshold in spex_tequila
|
350
|
+
threshold = self.amplitude_threshold if self.amplitude_threshold is not None else -1.0
|
351
|
+
final_state = spex_tequila.apply_U(self.circuit, state, threshold, n_qubits)
|
352
|
+
|
353
|
+
wfn_MSB = QubitWaveFunction(n_qubits=n_qubits, numbering=BitNumbering.MSB)
|
354
|
+
for state, amplitude in final_state.items():
|
355
|
+
wfn_MSB[state] = amplitude
|
356
|
+
|
357
|
+
del final_state
|
358
|
+
gc.collect()
|
359
|
+
|
360
|
+
return wfn_MSB
|
361
|
+
|
362
|
+
def simulate(self, variables, initial_state=0, *args, **kwargs) -> QubitWaveFunction:
|
363
|
+
"""Override simulate to avoid automatic mapping by KeyMapSubregisterToRegister"""
|
364
|
+
self.update_variables(variables)
|
365
|
+
result = self.do_simulate(variables=variables, initial_state=initial_state, *args, **kwargs)
|
366
|
+
return result
|
367
|
+
|
368
|
+
|
369
|
+
class BackendExpectationValueSpex(BackendExpectationValue):
|
370
|
+
"""SPEX expectation value calculator using sparse simulations"""
|
371
|
+
|
372
|
+
BackendCircuitType = BackendCircuitSpex
|
373
|
+
|
374
|
+
def __init__(
|
375
|
+
self, *args, num_threads=-1, amplitude_threshold=1e-14, angle_threshold=1e-14, compress_qubits=True, **kwargs
|
376
|
+
):
|
377
|
+
super().__init__(*args, **kwargs)
|
378
|
+
|
379
|
+
self.num_threads = num_threads
|
380
|
+
self.amplitude_threshold = amplitude_threshold
|
381
|
+
self.angle_threshold = angle_threshold
|
382
|
+
|
383
|
+
# Configure circuit parameters
|
384
|
+
if isinstance(self.U, BackendCircuitSpex):
|
385
|
+
self.U.num_threads = num_threads
|
386
|
+
self.U.amplitude_threshold = amplitude_threshold
|
387
|
+
self.U.angle_threshold = angle_threshold
|
388
|
+
self.U.compress_qubits = compress_qubits
|
389
|
+
|
390
|
+
def initialize_hamiltonian(self, hamiltonians):
|
391
|
+
"""
|
392
|
+
Initializes the Hamiltonian terms for the simulation.
|
393
|
+
Args:
|
394
|
+
hamiltonians: A list of Hamiltonian objects.
|
395
|
+
Returns:
|
396
|
+
tuple: A converted list of (pauli_string, coefficient) tuples.
|
397
|
+
"""
|
398
|
+
# Convert Tequila Hamiltonians into a list of (pauli_string, coeff) tuples for spex_tequila.
|
399
|
+
converted = []
|
400
|
+
for H in hamiltonians:
|
401
|
+
terms = []
|
402
|
+
for ps in H.paulistrings:
|
403
|
+
# Construct Pauli string like "X(0)Y(1)"
|
404
|
+
pauli_map = dict(ps.items())
|
405
|
+
term = spex_tequila.ExpPauliTerm()
|
406
|
+
term.pauli_map = pauli_map
|
407
|
+
terms.append((term, ps.coeff))
|
408
|
+
converted.append(terms)
|
409
|
+
|
410
|
+
if isinstance(self.U, BackendCircuitSpex):
|
411
|
+
self.U.hamiltonians = converted
|
412
|
+
|
413
|
+
return tuple(converted)
|
414
|
+
|
415
|
+
def simulate(self, variables, initial_state=0, *args, **kwargs):
|
416
|
+
"""
|
417
|
+
Calculate expectation value through sparse simulation
|
418
|
+
Returns:
|
419
|
+
numpy.ndarray: Expectation values for each Hamiltonian term
|
420
|
+
"""
|
421
|
+
|
422
|
+
# Prepare simulation
|
423
|
+
self.update_variables(variables)
|
424
|
+
|
425
|
+
if self.U.compress_qubits:
|
426
|
+
circuit, comp_hams, n_qubits = self.U.compress_qubit_indices()
|
427
|
+
else:
|
428
|
+
circuit = self.U.circuit
|
429
|
+
comp_hams = self.H
|
430
|
+
n_qubits = self.U.n_qubits
|
431
|
+
|
432
|
+
# Prepare the initial state
|
433
|
+
if isinstance(initial_state, int):
|
434
|
+
if initial_state == 0:
|
435
|
+
state = spex_tequila.initialize_zero_state(n_qubits)
|
436
|
+
else:
|
437
|
+
state = {initial_state: 1.0 + 0j}
|
438
|
+
else:
|
439
|
+
# initial_state is a QubitWaveFunction
|
440
|
+
state = {k: v for k, v in initial_state.raw_items()}
|
441
|
+
|
442
|
+
circuit = [t for t in circuit if abs(t.angle) >= self.U.angle_threshold]
|
443
|
+
|
444
|
+
threshold = self.amplitude_threshold if self.amplitude_threshold is not None else -1.0
|
445
|
+
final_state = spex_tequila.apply_U(circuit, state, threshold, n_qubits)
|
446
|
+
|
447
|
+
del state
|
448
|
+
|
449
|
+
if "SPEX_NUM_THREADS" in os.environ:
|
450
|
+
self.num_threads = int(os.environ["SPEX_NUM_THREADS"])
|
451
|
+
elif "OMP_NUM_THREADS" in os.environ:
|
452
|
+
self.num_threads = int(os.environ["OMP_NUM_THREADS"])
|
453
|
+
|
454
|
+
# Calculate the expectation value for each Hamiltonian
|
455
|
+
results = []
|
456
|
+
|
457
|
+
for H_terms in comp_hams:
|
458
|
+
val = spex_tequila.expectation_value_parallel(final_state, final_state, H_terms, n_qubits, num_threads=-1)
|
459
|
+
results.append(val.real)
|
460
|
+
|
461
|
+
del final_state
|
462
|
+
gc.collect()
|
463
|
+
|
464
|
+
return numpy.array(results)
|
@@ -11,8 +11,8 @@ import sympy
|
|
11
11
|
Simple Symbolic Simulator for debugging purposes
|
12
12
|
"""
|
13
13
|
|
14
|
-
class BackendCircuitSymbolic(BackendCircuit):
|
15
14
|
|
15
|
+
class BackendCircuitSymbolic(BackendCircuit):
|
16
16
|
# compiler instructions
|
17
17
|
compiler_arguments = {
|
18
18
|
"trotterized": True,
|
@@ -29,7 +29,7 @@ class BackendCircuitSymbolic(BackendCircuit):
|
|
29
29
|
"controlled_phase": True,
|
30
30
|
"toffoli": True,
|
31
31
|
"phase_to_z": True,
|
32
|
-
"cc_max": True
|
32
|
+
"cc_max": True,
|
33
33
|
}
|
34
34
|
|
35
35
|
convert_to_numpy = True
|
@@ -49,7 +49,7 @@ class BackendCircuitSymbolic(BackendCircuit):
|
|
49
49
|
return result
|
50
50
|
|
51
51
|
@classmethod
|
52
|
-
def apply_on_standard_basis(cls, gate: QGate, basis_state: BitString, qubits:dict, variables) -> QubitWaveFunction:
|
52
|
+
def apply_on_standard_basis(cls, gate: QGate, basis_state: BitString, qubits: dict, variables) -> QubitWaveFunction:
|
53
53
|
n_qubits = len(qubits.keys())
|
54
54
|
basis_array = basis_state.array
|
55
55
|
if gate.is_controlled() and not all(basis_array[qubits[c]] == 1 for c in gate.control):
|
@@ -70,8 +70,8 @@ class BackendCircuitSymbolic(BackendCircuit):
|
|
70
70
|
fac1 = None
|
71
71
|
fac2 = None
|
72
72
|
if gate.name == "H":
|
73
|
-
fac1 =
|
74
|
-
fac2 =
|
73
|
+
fac1 = sympy.Integer(-1) ** qt * sympy.sqrt(sympy.Rational(1 / 2))
|
74
|
+
fac2 = sympy.sqrt(sympy.Rational(1 / 2))
|
75
75
|
elif gate.name.upper() == "CNOT" or gate.name.upper() == "X":
|
76
76
|
fac2 = sympy.Integer(1)
|
77
77
|
elif gate.name.upper() == "Y":
|
@@ -129,5 +129,6 @@ class BackendCircuitSymbolic(BackendCircuit):
|
|
129
129
|
|
130
130
|
return wfn
|
131
131
|
|
132
|
+
|
132
133
|
class BackendExpectationValueSymbolic(BackendExpectationValue):
|
133
134
|
BackendCircuitType = BackendCircuitSymbolic
|