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.
Files changed (86) hide show
  1. tequila/__init__.py +29 -14
  2. tequila/apps/__init__.py +14 -5
  3. tequila/apps/_unary_state_prep_impl.py +145 -112
  4. tequila/apps/adapt/__init__.py +9 -1
  5. tequila/apps/adapt/adapt.py +154 -113
  6. tequila/apps/krylov/__init__.py +1 -1
  7. tequila/apps/krylov/krylov.py +23 -21
  8. tequila/apps/robustness/helpers.py +10 -6
  9. tequila/apps/robustness/interval.py +238 -156
  10. tequila/apps/unary_state_prep.py +29 -23
  11. tequila/autograd_imports.py +8 -5
  12. tequila/circuit/__init__.py +2 -1
  13. tequila/circuit/_gates_impl.py +135 -67
  14. tequila/circuit/circuit.py +163 -79
  15. tequila/circuit/compiler.py +114 -105
  16. tequila/circuit/gates.py +288 -120
  17. tequila/circuit/gradient.py +35 -23
  18. tequila/circuit/noise.py +83 -74
  19. tequila/circuit/postselection.py +120 -0
  20. tequila/circuit/pyzx.py +10 -6
  21. tequila/circuit/qasm.py +201 -83
  22. tequila/circuit/qpic.py +63 -61
  23. tequila/grouping/binary_rep.py +148 -146
  24. tequila/grouping/binary_utils.py +84 -75
  25. tequila/grouping/compile_groups.py +334 -230
  26. tequila/grouping/ev_utils.py +77 -41
  27. tequila/grouping/fermionic_functions.py +383 -308
  28. tequila/grouping/fermionic_methods.py +170 -123
  29. tequila/grouping/overlapping_methods.py +69 -52
  30. tequila/hamiltonian/paulis.py +12 -13
  31. tequila/hamiltonian/paulistring.py +1 -1
  32. tequila/hamiltonian/qubit_hamiltonian.py +45 -35
  33. tequila/ml/__init__.py +1 -0
  34. tequila/ml/interface_torch.py +19 -16
  35. tequila/ml/ml_api.py +11 -10
  36. tequila/ml/utils_ml.py +12 -11
  37. tequila/objective/__init__.py +8 -3
  38. tequila/objective/braket.py +55 -47
  39. tequila/objective/objective.py +87 -55
  40. tequila/objective/qtensor.py +36 -27
  41. tequila/optimizers/__init__.py +31 -23
  42. tequila/optimizers/_containers.py +11 -7
  43. tequila/optimizers/optimizer_base.py +111 -83
  44. tequila/optimizers/optimizer_gd.py +258 -231
  45. tequila/optimizers/optimizer_gpyopt.py +56 -42
  46. tequila/optimizers/optimizer_scipy.py +157 -112
  47. tequila/quantumchemistry/__init__.py +66 -38
  48. tequila/quantumchemistry/chemistry_tools.py +393 -209
  49. tequila/quantumchemistry/encodings.py +121 -13
  50. tequila/quantumchemistry/madness_interface.py +170 -96
  51. tequila/quantumchemistry/orbital_optimizer.py +86 -41
  52. tequila/quantumchemistry/psi4_interface.py +166 -97
  53. tequila/quantumchemistry/pyscf_interface.py +70 -23
  54. tequila/quantumchemistry/qc_base.py +866 -414
  55. tequila/simulators/__init__.py +0 -3
  56. tequila/simulators/simulator_api.py +247 -105
  57. tequila/simulators/simulator_aqt.py +102 -0
  58. tequila/simulators/simulator_base.py +147 -53
  59. tequila/simulators/simulator_cirq.py +58 -42
  60. tequila/simulators/simulator_cudaq.py +600 -0
  61. tequila/simulators/simulator_ddsim.py +390 -0
  62. tequila/simulators/simulator_mqp.py +30 -0
  63. tequila/simulators/simulator_pyquil.py +190 -171
  64. tequila/simulators/simulator_qibo.py +95 -87
  65. tequila/simulators/simulator_qiskit.py +119 -107
  66. tequila/simulators/simulator_qlm.py +52 -26
  67. tequila/simulators/simulator_qulacs.py +74 -52
  68. tequila/simulators/simulator_spex.py +95 -60
  69. tequila/simulators/simulator_symbolic.py +6 -5
  70. tequila/simulators/test_spex_simulator.py +8 -11
  71. tequila/tools/convenience.py +4 -4
  72. tequila/tools/qng.py +72 -64
  73. tequila/tools/random_generators.py +38 -34
  74. tequila/utils/bitstrings.py +7 -7
  75. tequila/utils/exceptions.py +19 -5
  76. tequila/utils/joined_transformation.py +8 -10
  77. tequila/utils/keymap.py +0 -5
  78. tequila/utils/misc.py +6 -4
  79. tequila/version.py +1 -1
  80. tequila/wavefunction/qubit_wavefunction.py +47 -28
  81. {tequila_basic-1.9.9.dist-info → tequila_basic-1.9.10.dist-info}/METADATA +13 -16
  82. tequila_basic-1.9.10.dist-info/RECORD +93 -0
  83. {tequila_basic-1.9.9.dist-info → tequila_basic-1.9.10.dist-info}/WHEEL +1 -1
  84. tequila_basic-1.9.9.dist-info/RECORD +0 -88
  85. {tequila_basic-1.9.9.dist-info → tequila_basic-1.9.10.dist-info}/licenses/LICENSE +0 -0
  86. {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, numpy
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, # needed for gates depending on variables
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
- 'I': qulacs.gate.Identity,
87
- 'X': qulacs.gate.X,
88
- 'Y': qulacs.gate.Y,
89
- 'Z': qulacs.gate.Z,
90
- 'H': qulacs.gate.H,
91
- 'Rx': (lambda c: c.add_parametric_RX_gate, qulacs.gate.RX),
92
- 'Ry': (lambda c: c.add_parametric_RY_gate, qulacs.gate.RY),
93
- 'Rz': (lambda c: c.add_parametric_RZ_gate, qulacs.gate.RZ),
94
- 'SWAP': qulacs.gate.SWAP,
95
- 'Measure': qulacs.gate.Measurement,
96
- 'Exp-Pauli': None
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
- warnings.warn("Warning: noise in qulacs module will be dropped. Currently only works for qulacs version 0.5 or lower", TequilaWarning)
105
-
106
- self.has_noise=True
111
+ self.has_noise = True
107
112
  self.noise_lookup = {
108
- 'bit flip': [qulacs.gate.BitFlipNoise],
109
- 'phase flip': [lambda target, prob: qulacs.gate.Probabilistic([prob],[qulacs.gate.Z(target)])],
110
- 'phase damp': [lambda target, prob: qulacs.gate.DephasingNoise(target,(1/2)*(1-numpy.sqrt(1-prob)))],
111
- 'amplitude damp': [qulacs.gate.AmplitudeDampingNoise],
112
- 'phase-amplitude damp': [qulacs.gate.AmplitudeDampingNoise,
113
- lambda target, prob: qulacs.gate.DephasingNoise(target,(1/2)*(1-numpy.sqrt(1-prob)))
114
- ],
115
- 'depolarizing': [lambda target,prob: qulacs.gate.DepolarizingNoise(target,3*prob/4)]
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(self, n_qubits: int = None, initial_state: Union[int, QubitWaveFunction] = None) -> qulacs.QuantumState:
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 = {'x': 1, 'y': 2, 'z': 3}
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(qind, pind,
278
- -gate.parameter(variables) * gate.paulistring.coeff)
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 == 'Exp-Pauli':
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("Gates which depend on variables can not be controlled! Gate was:\n{}".format(gate))
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("qulacs: optimized circuit depth from {} to {} with max_block_size {}".format(old,
419
- circuit.calculate_depth(),
420
- max_block_size))
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) # those are accumulated unit strings, e.g 0.1*X(3) in wfn on qubits 0,1
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.abstract_circuit.qubits):
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(self, variables, samples, initial_state: Union[int, QubitWaveFunction] = 0, *args, **kwargs) -> numpy.array:
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 self._reduced_hamiltonians: # those are the hamiltonians which where non-used qubits are already traced out
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 len(bc.gates) > 0: # otherwise there is no basis change (empty qulacs circuit does not work out)
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 *= (-2.0 * measured + 1.0) # 0 becomes 1 and 1 becomes -1
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('utf-8'))
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('utf-8'))
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__(self,
80
- abstract_circuit=None,
81
- variables=None,
82
- num_threads=-1,
83
- amplitude_threshold=1e-14,
84
- angle_threshold=1e-14,
85
- compress_qubits=True,
86
- *args, **kwargs):
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 self.circuit:
187
+ for term in new_circuit:
156
188
  used_qubits.update(term.pauli_map.keys())
157
- for ham in self.hamiltonians:
158
- for term, _ in ham:
159
- 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())
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 self.circuit:
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 self.hamiltonians is not None:
172
- for ham in self.hamiltonians:
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(f"Unsupported gate object type: {type(gate)}. "
247
- "All gates should be compiled to exponential pauli or rotation gates.")
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(f"Unsupported gate type: {type(gate)}. "
286
- "Only Exponential Pauli and Rotation gates are allowed after compilation.")
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__(self, *args,
338
- num_threads=-1,
339
- amplitude_threshold=1e-14,
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 and self.U.n_qubits_compressed is not None and self.U.n_qubits_compressed > 0:
395
- n_qubits = self.U.n_qubits_compressed
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
- self.U.circuit = [t for t in self.U.circuit if abs(t.angle) >= self.U.angle_threshold]
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(self.U.circuit, state, threshold, n_qubits)
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
- for H_terms in self.H:
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 = (sympy.Integer(-1) ** qt * sympy.sqrt(sympy.Rational(1 / 2)))
74
- fac2 = (sympy.sqrt(sympy.Rational(1 / 2)))
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