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
@@ -1,7 +1,8 @@
|
|
1
1
|
from typing import Union
|
2
2
|
|
3
3
|
import qulacs
|
4
|
-
import numbers
|
4
|
+
import numbers
|
5
|
+
import numpy
|
5
6
|
import warnings
|
6
7
|
|
7
8
|
from tequila import TequilaException, TequilaWarning
|
@@ -17,10 +18,12 @@ Developer Note:
|
|
17
18
|
The angles are scaled with -1.0 to keep things consistent with the rest of tequila
|
18
19
|
"""
|
19
20
|
|
21
|
+
|
20
22
|
class TequilaQulacsException(TequilaException):
|
21
23
|
def __str__(self):
|
22
24
|
return "Error in qulacs backend:" + self.message
|
23
25
|
|
26
|
+
|
24
27
|
class BackendCircuitQulacs(BackendCircuit):
|
25
28
|
"""
|
26
29
|
Class representing circuits compiled to qulacs.
|
@@ -49,7 +52,7 @@ class BackendCircuitQulacs(BackendCircuit):
|
|
49
52
|
"trotterized": True,
|
50
53
|
"swap": False,
|
51
54
|
"multitarget": True,
|
52
|
-
"controlled_rotation": True,
|
55
|
+
"controlled_rotation": True, # needed for gates depending on variables
|
53
56
|
"generalized_rotation": True,
|
54
57
|
"exponential_pauli": False,
|
55
58
|
"controlled_exponential_pauli": True,
|
@@ -60,7 +63,7 @@ class BackendCircuitQulacs(BackendCircuit):
|
|
60
63
|
"controlled_phase": True,
|
61
64
|
"toffoli": False,
|
62
65
|
"phase_to_z": True,
|
63
|
-
"cc_max": False
|
66
|
+
"cc_max": False,
|
64
67
|
}
|
65
68
|
|
66
69
|
numbering = BitNumbering.LSB
|
@@ -83,41 +86,48 @@ class BackendCircuitQulacs(BackendCircuit):
|
|
83
86
|
kwargs
|
84
87
|
"""
|
85
88
|
self.op_lookup = {
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
89
|
+
"I": qulacs.gate.Identity,
|
90
|
+
"X": qulacs.gate.X,
|
91
|
+
"Y": qulacs.gate.Y,
|
92
|
+
"Z": qulacs.gate.Z,
|
93
|
+
"H": qulacs.gate.H,
|
94
|
+
"Rx": (lambda c: c.add_parametric_RX_gate, qulacs.gate.RX),
|
95
|
+
"Ry": (lambda c: c.add_parametric_RY_gate, qulacs.gate.RY),
|
96
|
+
"Rz": (lambda c: c.add_parametric_RZ_gate, qulacs.gate.RZ),
|
97
|
+
"SWAP": qulacs.gate.SWAP,
|
98
|
+
"Measure": qulacs.gate.Measurement,
|
99
|
+
"Exp-Pauli": None,
|
97
100
|
}
|
98
101
|
self.measurements = None
|
99
102
|
self.variables = []
|
100
103
|
super().__init__(abstract_circuit=abstract_circuit, noise=noise, *args, **kwargs)
|
101
|
-
self.has_noise=False
|
104
|
+
self.has_noise = False
|
102
105
|
if noise is not None:
|
106
|
+
warnings.warn(
|
107
|
+
"Warning: noise in qulacs module will be dropped. Currently only works for qulacs version 0.5 or lower",
|
108
|
+
TequilaWarning,
|
109
|
+
)
|
103
110
|
|
104
|
-
|
105
|
-
|
106
|
-
self.has_noise=True
|
111
|
+
self.has_noise = True
|
107
112
|
self.noise_lookup = {
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
113
|
+
"bit flip": [qulacs.gate.BitFlipNoise],
|
114
|
+
"phase flip": [lambda target, prob: qulacs.gate.Probabilistic([prob], [qulacs.gate.Z(target)])],
|
115
|
+
"phase damp": [
|
116
|
+
lambda target, prob: qulacs.gate.DephasingNoise(target, (1 / 2) * (1 - numpy.sqrt(1 - prob)))
|
117
|
+
],
|
118
|
+
"amplitude damp": [qulacs.gate.AmplitudeDampingNoise],
|
119
|
+
"phase-amplitude damp": [
|
120
|
+
qulacs.gate.AmplitudeDampingNoise,
|
121
|
+
lambda target, prob: qulacs.gate.DephasingNoise(target, (1 / 2) * (1 - numpy.sqrt(1 - prob))),
|
122
|
+
],
|
123
|
+
"depolarizing": [lambda target, prob: qulacs.gate.DepolarizingNoise(target, 3 * prob / 4)],
|
116
124
|
}
|
117
125
|
|
118
|
-
self.circuit=self.add_noise_to_circuit(noise)
|
126
|
+
self.circuit = self.add_noise_to_circuit(noise)
|
119
127
|
|
120
|
-
def initialize_state(
|
128
|
+
def initialize_state(
|
129
|
+
self, n_qubits: int = None, initial_state: Union[int, QubitWaveFunction] = None
|
130
|
+
) -> qulacs.QuantumState:
|
121
131
|
if n_qubits is None:
|
122
132
|
n_qubits = self.n_qubits
|
123
133
|
|
@@ -269,13 +279,14 @@ class BackendCircuitQulacs(BackendCircuit):
|
|
269
279
|
None
|
270
280
|
"""
|
271
281
|
assert not gate.is_controlled()
|
272
|
-
convert = {
|
282
|
+
convert = {"x": 1, "y": 2, "z": 3}
|
273
283
|
pind = [convert[x.lower()] for x in gate.paulistring.values()]
|
274
284
|
qind = [self.qubit(x) for x in gate.paulistring.keys()]
|
275
285
|
if len(gate.extract_variables()) > 0:
|
276
286
|
self.variables.append(-gate.parameter * gate.paulistring.coeff)
|
277
|
-
circuit.add_parametric_multi_Pauli_rotation_gate(
|
278
|
-
|
287
|
+
circuit.add_parametric_multi_Pauli_rotation_gate(
|
288
|
+
qind, pind, -gate.parameter(variables) * gate.paulistring.coeff
|
289
|
+
)
|
279
290
|
else:
|
280
291
|
circuit.add_multi_Pauli_rotation_gate(qind, pind, -gate.parameter(variables) * gate.paulistring.coeff)
|
281
292
|
|
@@ -298,7 +309,7 @@ class BackendCircuitQulacs(BackendCircuit):
|
|
298
309
|
None
|
299
310
|
"""
|
300
311
|
op = self.op_lookup[gate.name]
|
301
|
-
if gate.name ==
|
312
|
+
if gate.name == "Exp-Pauli":
|
302
313
|
self.add_exponential_pauli_gate(gate, circuit, variables)
|
303
314
|
return
|
304
315
|
else:
|
@@ -307,7 +318,9 @@ class BackendCircuitQulacs(BackendCircuit):
|
|
307
318
|
self.variables.append(-gate.parameter)
|
308
319
|
op(circuit)(self.qubit(gate.target[0]), -gate.parameter(variables=variables))
|
309
320
|
if gate.is_controlled():
|
310
|
-
raise TequilaQulacsException(
|
321
|
+
raise TequilaQulacsException(
|
322
|
+
"Gates which depend on variables can not be controlled! Gate was:\n{}".format(gate)
|
323
|
+
)
|
311
324
|
return
|
312
325
|
else:
|
313
326
|
op = op[1]
|
@@ -362,8 +375,7 @@ class BackendCircuitQulacs(BackendCircuit):
|
|
362
375
|
self.measurements = sorted(target_qubits)
|
363
376
|
return circuit
|
364
377
|
|
365
|
-
|
366
|
-
def add_noise_to_circuit(self,noise_model):
|
378
|
+
def add_noise_to_circuit(self, noise_model):
|
367
379
|
"""
|
368
380
|
Apply noise from a NoiseModel to a circuit.
|
369
381
|
Parameters
|
@@ -376,19 +388,19 @@ class BackendCircuitQulacs(BackendCircuit):
|
|
376
388
|
qulacs.ParametrizedQuantumCircuit:
|
377
389
|
self.circuit, with noise added on.
|
378
390
|
"""
|
379
|
-
c=self.circuit
|
380
|
-
n=noise_model
|
381
|
-
g_count=c.get_gate_count()
|
382
|
-
new=self.initialize_circuit()
|
391
|
+
c = self.circuit
|
392
|
+
n = noise_model
|
393
|
+
g_count = c.get_gate_count()
|
394
|
+
new = self.initialize_circuit()
|
383
395
|
for i in range(g_count):
|
384
|
-
g=c.get_gate(i)
|
396
|
+
g = c.get_gate(i)
|
385
397
|
new.add_gate(g)
|
386
|
-
qubits=g.get_target_index_list() + g.get_control_index_list()
|
398
|
+
qubits = g.get_target_index_list() + g.get_control_index_list()
|
387
399
|
for noise in n.noises:
|
388
400
|
if len(qubits) == noise.level:
|
389
|
-
for j,channel in enumerate(self.noise_lookup[noise.name]):
|
401
|
+
for j, channel in enumerate(self.noise_lookup[noise.name]):
|
390
402
|
for q in qubits:
|
391
|
-
chan=channel(q,noise.probs[j])
|
403
|
+
chan = channel(q, noise.probs[j])
|
392
404
|
new.add_gate(chan)
|
393
405
|
return new
|
394
406
|
|
@@ -415,17 +427,21 @@ class BackendCircuitQulacs(BackendCircuit):
|
|
415
427
|
opt = qulacs.circuit.QuantumCircuitOptimizer()
|
416
428
|
opt.optimize(circuit, max_block_size)
|
417
429
|
if not silent:
|
418
|
-
print(
|
419
|
-
|
420
|
-
|
430
|
+
print(
|
431
|
+
"qulacs: optimized circuit depth from {} to {} with max_block_size {}".format(
|
432
|
+
old, circuit.calculate_depth(), max_block_size
|
433
|
+
)
|
434
|
+
)
|
421
435
|
return circuit
|
422
436
|
|
437
|
+
|
423
438
|
class BackendExpectationValueQulacs(BackendExpectationValue):
|
424
439
|
"""
|
425
440
|
Class representing Expectation Values compiled for Qulacs.
|
426
441
|
|
427
442
|
Ovverrides some methods of BackendExpectationValue, which should be seen for details.
|
428
443
|
"""
|
444
|
+
|
429
445
|
use_mapping = True
|
430
446
|
BackendCircuitType = BackendCircuitQulacs
|
431
447
|
|
@@ -460,7 +476,7 @@ class BackendExpectationValueQulacs(BackendExpectationValue):
|
|
460
476
|
result = []
|
461
477
|
for H in self.H:
|
462
478
|
if isinstance(H, numbers.Number):
|
463
|
-
result.append(H)
|
479
|
+
result.append(H) # those are accumulated unit strings, e.g 0.1*X(3) in wfn on qubits 0,1
|
464
480
|
else:
|
465
481
|
result.append(H.get_expectation_value(state))
|
466
482
|
|
@@ -483,7 +499,7 @@ class BackendExpectationValueQulacs(BackendExpectationValue):
|
|
483
499
|
|
484
500
|
# map the reduced operators to the potentially smaller qubit system
|
485
501
|
qubit_map = {}
|
486
|
-
for i,q in enumerate(self.U.
|
502
|
+
for i, q in enumerate(self.U.abstract_qubits):
|
487
503
|
qubit_map[q] = i
|
488
504
|
|
489
505
|
result = []
|
@@ -497,7 +513,9 @@ class BackendExpectationValueQulacs(BackendExpectationValue):
|
|
497
513
|
result.append(qulacs_H)
|
498
514
|
return result
|
499
515
|
|
500
|
-
def sample(
|
516
|
+
def sample(
|
517
|
+
self, variables, samples, initial_state: Union[int, QubitWaveFunction] = 0, *args, **kwargs
|
518
|
+
) -> numpy.array:
|
501
519
|
"""
|
502
520
|
Sample this Expectation Value.
|
503
521
|
Parameters
|
@@ -520,7 +538,9 @@ class BackendExpectationValueQulacs(BackendExpectationValue):
|
|
520
538
|
state = self.U.initialize_state(self.n_qubits, initial_state)
|
521
539
|
self.U.circuit.update_quantum_state(state)
|
522
540
|
result = []
|
523
|
-
for H in
|
541
|
+
for H in (
|
542
|
+
self._reduced_hamiltonians
|
543
|
+
): # those are the hamiltonians which where non-used qubits are already traced out
|
524
544
|
E = 0.0
|
525
545
|
if H.is_all_z() and not self.U.has_noise:
|
526
546
|
E = super().sample(samples=samples, variables=variables, initial_state=initial_state, *args, **kwargs)
|
@@ -540,7 +560,9 @@ class BackendExpectationValueQulacs(BackendExpectationValue):
|
|
540
560
|
state_tmp = state
|
541
561
|
else:
|
542
562
|
state_tmp = state.copy()
|
543
|
-
if
|
563
|
+
if (
|
564
|
+
len(bc.gates) > 0
|
565
|
+
): # otherwise there is no basis change (empty qulacs circuit does not work out)
|
544
566
|
qbc.update_quantum_state(state_tmp)
|
545
567
|
ps_measure = 1.0
|
546
568
|
for idx in ps.keys():
|
@@ -548,7 +570,7 @@ class BackendExpectationValueQulacs(BackendExpectationValue):
|
|
548
570
|
M = qulacs.gate.Measurement(self.U.qubit(idx), self.U.qubit(idx))
|
549
571
|
M.update_quantum_state(state_tmp)
|
550
572
|
measured = state_tmp.get_classical_value(self.U.qubit(idx))
|
551
|
-
ps_measure *=
|
573
|
+
ps_measure *= -2.0 * measured + 1.0 # 0 becomes 1 and 1 becomes -1
|
552
574
|
Esamples.append(ps_measure)
|
553
575
|
E += ps.coeff * sum(Esamples) / len(Esamples)
|
554
576
|
result.append(E)
|
@@ -15,10 +15,13 @@ import gc
|
|
15
15
|
|
16
16
|
numbering = BitNumbering.MSB
|
17
17
|
|
18
|
+
|
18
19
|
class TequilaSpexException(TequilaException):
|
19
20
|
"""Custom exception for SPEX simulator errors"""
|
21
|
+
|
20
22
|
pass
|
21
23
|
|
24
|
+
|
22
25
|
def extract_pauli_dict(ps):
|
23
26
|
"""
|
24
27
|
Extract qubit:operator mapping from PauliString/QubitHamiltonian
|
@@ -34,6 +37,7 @@ def extract_pauli_dict(ps):
|
|
34
37
|
return dict(ps.paulistrings[0].items())
|
35
38
|
raise TequilaSpexException("Unsupported generator type")
|
36
39
|
|
40
|
+
|
37
41
|
def circuit_hash(abstract_circuit, variables=None):
|
38
42
|
"""
|
39
43
|
Create MD5 hash for circuit caching
|
@@ -44,12 +48,23 @@ def circuit_hash(abstract_circuit, variables=None):
|
|
44
48
|
return None
|
45
49
|
for g in abstract_circuit.gates:
|
46
50
|
gate_str = f"{type(g).__name__}:{g.name}:{g.target}:{g.control}:{g.generator}:{getattr(g, 'parameter', None)}\n"
|
47
|
-
sha.update(gate_str.encode(
|
51
|
+
sha.update(gate_str.encode("utf-8"))
|
48
52
|
if variables:
|
49
53
|
for key, value in sorted(variables.items()):
|
50
|
-
sha.update(f"{key}:{value}\n".encode(
|
54
|
+
sha.update(f"{key}:{value}\n".encode("utf-8"))
|
51
55
|
return sha.hexdigest()
|
52
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
|
+
|
53
68
|
class BackendCircuitSpex(BackendCircuit):
|
54
69
|
"""SPEX circuit implementation using sparse state representation"""
|
55
70
|
|
@@ -73,18 +88,20 @@ class BackendCircuitSpex(BackendCircuit):
|
|
73
88
|
"cc_max": True,
|
74
89
|
"ry_gate": True,
|
75
90
|
"y_gate": True,
|
76
|
-
"ch_gate": True
|
91
|
+
"ch_gate": True,
|
77
92
|
}
|
78
93
|
|
79
|
-
def __init__(
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
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
|
+
):
|
88
105
|
# Circuit chaching
|
89
106
|
self.circuit_cache = {}
|
90
107
|
|
@@ -97,7 +114,7 @@ class BackendCircuitSpex(BackendCircuit):
|
|
97
114
|
self.compress_qubits = compress_qubits
|
98
115
|
self.n_qubits_compressed = None
|
99
116
|
self.hamiltonians = None
|
100
|
-
|
117
|
+
|
101
118
|
super().__init__(abstract_circuit=abstract_circuit, variables=variables, *args, **kwargs)
|
102
119
|
|
103
120
|
@property
|
@@ -107,7 +124,7 @@ class BackendCircuitSpex(BackendCircuit):
|
|
107
124
|
if hasattr(self, "circuit") and self.circuit:
|
108
125
|
for term in self.circuit:
|
109
126
|
used.update(term.pauli_map.keys())
|
110
|
-
|
127
|
+
|
111
128
|
if self.abstract_circuit is not None and hasattr(self.abstract_circuit, "gates"):
|
112
129
|
for gate in self.abstract_circuit.gates:
|
113
130
|
if hasattr(gate, "target"):
|
@@ -125,7 +142,7 @@ class BackendCircuitSpex(BackendCircuit):
|
|
125
142
|
|
126
143
|
def initialize_circuit(self, *args, **kwargs):
|
127
144
|
return []
|
128
|
-
|
145
|
+
|
129
146
|
def create_circuit(self, abstract_circuit=None, variables=None, *args, **kwargs):
|
130
147
|
"""Compile circuit with caching using MD5 hash"""
|
131
148
|
if abstract_circuit is None:
|
@@ -135,46 +152,65 @@ class BackendCircuitSpex(BackendCircuit):
|
|
135
152
|
|
136
153
|
if key in self.circuit_cache:
|
137
154
|
return self.circuit_cache[key]
|
138
|
-
|
155
|
+
|
139
156
|
circuit = super().create_circuit(abstract_circuit=abstract_circuit, variables=variables, *args, **kwargs)
|
140
157
|
|
141
158
|
self.circuit_cache[key] = circuit
|
142
159
|
|
143
160
|
return circuit
|
144
|
-
|
161
|
+
|
145
162
|
def compress_qubit_indices(self):
|
146
163
|
"""
|
147
164
|
Optimize qubit indices by mapping used qubits to contiguous range
|
148
165
|
Reduces memory usage by eliminating unused qubit dimensions
|
149
166
|
"""
|
150
167
|
if not self.compress_qubits or not (hasattr(self, "circuit") and self.circuit):
|
151
|
-
return
|
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)
|
152
184
|
|
153
185
|
# Collect all qubits used in circuit and Hamiltonians
|
154
186
|
used_qubits = set()
|
155
|
-
for term in
|
187
|
+
for term in new_circuit:
|
156
188
|
used_qubits.update(term.pauli_map.keys())
|
157
|
-
|
158
|
-
|
159
|
-
|
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())
|
160
194
|
|
161
195
|
if not used_qubits:
|
162
196
|
self.n_qubits_compressed = 0
|
163
|
-
return
|
197
|
+
return new_circuit, new_hamiltonians, 0
|
164
198
|
|
165
199
|
# Create qubit mapping and remap all terms
|
166
200
|
qubit_map = {old: new for new, old in enumerate(sorted(used_qubits))}
|
167
|
-
|
168
|
-
for term in
|
201
|
+
|
202
|
+
for term in new_circuit:
|
169
203
|
term.pauli_map = {qubit_map[old]: op for old, op in term.pauli_map.items()}
|
170
204
|
|
171
|
-
if
|
172
|
-
for ham in
|
205
|
+
if new_hamiltonians is not None:
|
206
|
+
for ham in new_hamiltonians:
|
173
207
|
for term, _ in ham:
|
174
208
|
term.pauli_map = {qubit_map[old]: op for old, op in term.pauli_map.items()}
|
175
209
|
|
176
210
|
self.n_qubits_compressed = len(used_qubits)
|
177
211
|
|
212
|
+
return new_circuit, new_hamiltonians, self.n_qubits_compressed
|
213
|
+
|
178
214
|
def update_variables(self, variables, *args, **kwargs):
|
179
215
|
if variables is None:
|
180
216
|
variables = {}
|
@@ -192,9 +228,8 @@ class BackendCircuitSpex(BackendCircuit):
|
|
192
228
|
if callable(param):
|
193
229
|
result = param(variables)
|
194
230
|
return float(result)
|
195
|
-
|
196
|
-
raise TequilaSpexException(f"Can't assign parameter '{param}'.")
|
197
231
|
|
232
|
+
raise TequilaSpexException(f"Can't assign parameter '{param}'.")
|
198
233
|
|
199
234
|
def add_basic_gate(self, gate, circuit, *args, **kwargs):
|
200
235
|
"""Convert Tequila gates to SPEX exponential Pauli terms"""
|
@@ -219,7 +254,7 @@ class BackendCircuitSpex(BackendCircuit):
|
|
219
254
|
self.add_basic_gate(sub_gate, circuit, *args, **kwargs)
|
220
255
|
|
221
256
|
elif isinstance(gate, QGateImpl):
|
222
|
-
if gate.name.lower() in ["x","y","z"]:
|
257
|
+
if gate.name.lower() in ["x", "y", "z"]:
|
223
258
|
# Convert standard gates to Pauli rotations
|
224
259
|
for ps in gate.make_generator(include_controls=True).paulistrings:
|
225
260
|
angle = numpy.pi * ps.coeff
|
@@ -230,7 +265,7 @@ class BackendCircuitSpex(BackendCircuit):
|
|
230
265
|
exp_term.angle = angle
|
231
266
|
circuit.append(exp_term)
|
232
267
|
elif gate.name.lower() in ["h", "hadamard"]:
|
233
|
-
assert len(gate.target)==1
|
268
|
+
assert len(gate.target) == 1
|
234
269
|
target = gate.target[0]
|
235
270
|
for ps in ["-0.25*Y({q})", "Z({q})", "0.25*Y({q})"]:
|
236
271
|
ps = QubitHamiltonian(ps.format(q=gate.target[0])).paulistrings[0]
|
@@ -243,10 +278,10 @@ class BackendCircuitSpex(BackendCircuit):
|
|
243
278
|
raise TequilaSpexException("{} not supported. Only x,y,z,h".format(gate.name.lower()))
|
244
279
|
|
245
280
|
else:
|
246
|
-
raise TequilaSpexException(
|
247
|
-
|
248
|
-
|
249
|
-
|
281
|
+
raise TequilaSpexException(
|
282
|
+
f"Unsupported gate object type: {type(gate)}. "
|
283
|
+
"All gates should be compiled to exponential pauli or rotation gates."
|
284
|
+
)
|
250
285
|
|
251
286
|
def add_parametrized_gate(self, gate, circuit, *args, **kwargs):
|
252
287
|
"""Convert Tequila parametrized gates to SPEX exponential Pauli terms"""
|
@@ -271,7 +306,7 @@ class BackendCircuitSpex(BackendCircuit):
|
|
271
306
|
compiled_gate = gate.compile(exponential_pauli=True)
|
272
307
|
for sub_gate in compiled_gate.abstract_circuit.gates:
|
273
308
|
self.add_parametrized_gate(sub_gate, circuit, *args, **kwargs)
|
274
|
-
|
309
|
+
|
275
310
|
elif isinstance(gate, QGateImpl):
|
276
311
|
for ps in gate.make_generator(include_controls=True).paulistrings:
|
277
312
|
if self.angle_threshold is not None and abs(gate.parameter) < self.angle_threshold:
|
@@ -282,9 +317,10 @@ class BackendCircuitSpex(BackendCircuit):
|
|
282
317
|
circuit.append(exp_term)
|
283
318
|
|
284
319
|
else:
|
285
|
-
raise TequilaSpexException(
|
286
|
-
|
287
|
-
|
320
|
+
raise TequilaSpexException(
|
321
|
+
f"Unsupported gate type: {type(gate)}. "
|
322
|
+
"Only Exponential Pauli and Rotation gates are allowed after compilation."
|
323
|
+
)
|
288
324
|
|
289
325
|
def do_simulate(self, variables, initial_state=0, *args, **kwargs) -> QubitWaveFunction:
|
290
326
|
"""
|
@@ -322,7 +358,7 @@ class BackendCircuitSpex(BackendCircuit):
|
|
322
358
|
gc.collect()
|
323
359
|
|
324
360
|
return wfn_MSB
|
325
|
-
|
361
|
+
|
326
362
|
def simulate(self, variables, initial_state=0, *args, **kwargs) -> QubitWaveFunction:
|
327
363
|
"""Override simulate to avoid automatic mapping by KeyMapSubregisterToRegister"""
|
328
364
|
self.update_variables(variables)
|
@@ -332,20 +368,18 @@ class BackendCircuitSpex(BackendCircuit):
|
|
332
368
|
|
333
369
|
class BackendExpectationValueSpex(BackendExpectationValue):
|
334
370
|
"""SPEX expectation value calculator using sparse simulations"""
|
371
|
+
|
335
372
|
BackendCircuitType = BackendCircuitSpex
|
336
373
|
|
337
|
-
def __init__(
|
338
|
-
|
339
|
-
|
340
|
-
angle_threshold=1e-14,
|
341
|
-
compress_qubits=True,
|
342
|
-
**kwargs):
|
374
|
+
def __init__(
|
375
|
+
self, *args, num_threads=-1, amplitude_threshold=1e-14, angle_threshold=1e-14, compress_qubits=True, **kwargs
|
376
|
+
):
|
343
377
|
super().__init__(*args, **kwargs)
|
344
378
|
|
345
379
|
self.num_threads = num_threads
|
346
380
|
self.amplitude_threshold = amplitude_threshold
|
347
381
|
self.angle_threshold = angle_threshold
|
348
|
-
|
382
|
+
|
349
383
|
# Configure circuit parameters
|
350
384
|
if isinstance(self.U, BackendCircuitSpex):
|
351
385
|
self.U.num_threads = num_threads
|
@@ -367,10 +401,10 @@ class BackendExpectationValueSpex(BackendExpectationValue):
|
|
367
401
|
terms = []
|
368
402
|
for ps in H.paulistrings:
|
369
403
|
# Construct Pauli string like "X(0)Y(1)"
|
370
|
-
pauli_map = dict(ps.items())
|
404
|
+
pauli_map = dict(ps.items())
|
371
405
|
term = spex_tequila.ExpPauliTerm()
|
372
|
-
term.pauli_map = pauli_map
|
373
|
-
terms.append((term, ps.coeff))
|
406
|
+
term.pauli_map = pauli_map
|
407
|
+
terms.append((term, ps.coeff))
|
374
408
|
converted.append(terms)
|
375
409
|
|
376
410
|
if isinstance(self.U, BackendCircuitSpex):
|
@@ -378,7 +412,6 @@ class BackendExpectationValueSpex(BackendExpectationValue):
|
|
378
412
|
|
379
413
|
return tuple(converted)
|
380
414
|
|
381
|
-
|
382
415
|
def simulate(self, variables, initial_state=0, *args, **kwargs):
|
383
416
|
"""
|
384
417
|
Calculate expectation value through sparse simulation
|
@@ -388,12 +421,12 @@ class BackendExpectationValueSpex(BackendExpectationValue):
|
|
388
421
|
|
389
422
|
# Prepare simulation
|
390
423
|
self.update_variables(variables)
|
391
|
-
if self.U.compress_qubits:
|
392
|
-
self.U.compress_qubit_indices()
|
393
424
|
|
394
|
-
if self.U.compress_qubits
|
395
|
-
n_qubits = self.U.
|
425
|
+
if self.U.compress_qubits:
|
426
|
+
circuit, comp_hams, n_qubits = self.U.compress_qubit_indices()
|
396
427
|
else:
|
428
|
+
circuit = self.U.circuit
|
429
|
+
comp_hams = self.H
|
397
430
|
n_qubits = self.U.n_qubits
|
398
431
|
|
399
432
|
# Prepare the initial state
|
@@ -406,10 +439,11 @@ class BackendExpectationValueSpex(BackendExpectationValue):
|
|
406
439
|
# initial_state is a QubitWaveFunction
|
407
440
|
state = {k: v for k, v in initial_state.raw_items()}
|
408
441
|
|
409
|
-
|
442
|
+
circuit = [t for t in circuit if abs(t.angle) >= self.U.angle_threshold]
|
410
443
|
|
411
444
|
threshold = self.amplitude_threshold if self.amplitude_threshold is not None else -1.0
|
412
|
-
final_state = spex_tequila.apply_U(
|
445
|
+
final_state = spex_tequila.apply_U(circuit, state, threshold, n_qubits)
|
446
|
+
|
413
447
|
del state
|
414
448
|
|
415
449
|
if "SPEX_NUM_THREADS" in os.environ:
|
@@ -419,11 +453,12 @@ class BackendExpectationValueSpex(BackendExpectationValue):
|
|
419
453
|
|
420
454
|
# Calculate the expectation value for each Hamiltonian
|
421
455
|
results = []
|
422
|
-
|
456
|
+
|
457
|
+
for H_terms in comp_hams:
|
423
458
|
val = spex_tequila.expectation_value_parallel(final_state, final_state, H_terms, n_qubits, num_threads=-1)
|
424
459
|
results.append(val.real)
|
425
|
-
|
460
|
+
|
426
461
|
del final_state
|
427
462
|
gc.collect()
|
428
|
-
|
463
|
+
|
429
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
|