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.
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 +177 -88
  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 +91 -56
  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 +394 -203
  49. tequila/quantumchemistry/encodings.py +121 -13
  50. tequila/quantumchemistry/madness_interface.py +170 -96
  51. tequila/quantumchemistry/orbital_optimizer.py +86 -40
  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 +258 -106
  57. tequila/simulators/simulator_aqt.py +102 -0
  58. tequila/simulators/simulator_base.py +156 -55
  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 +124 -114
  66. tequila/simulators/simulator_qlm.py +52 -26
  67. tequila/simulators/simulator_qulacs.py +85 -59
  68. tequila/simulators/simulator_spex.py +464 -0
  69. tequila/simulators/simulator_symbolic.py +6 -5
  70. tequila/simulators/test_spex_simulator.py +208 -0
  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 +13 -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 +52 -30
  81. {tequila_basic-1.9.8.dist-info → tequila_basic-1.9.10.dist-info}/METADATA +23 -17
  82. tequila_basic-1.9.10.dist-info/RECORD +93 -0
  83. {tequila_basic-1.9.8.dist-info → tequila_basic-1.9.10.dist-info}/WHEEL +1 -1
  84. tequila_basic-1.9.8.dist-info/RECORD +0 -86
  85. {tequila_basic-1.9.8.dist-info → tequila_basic-1.9.10.dist-info/licenses}/LICENSE +0 -0
  86. {tequila_basic-1.9.8.dist-info → tequila_basic-1.9.10.dist-info}/top_level.txt +0 -0
@@ -1,6 +1,12 @@
1
1
  from tequila.circuit.compiler import CircuitCompiler
2
- from tequila.objective.objective import Objective, ExpectationValueImpl, Variable, \
3
- assign_variable, identity, FixedVariable
2
+ from tequila.objective.objective import (
3
+ Objective,
4
+ ExpectationValueImpl,
5
+ Variable,
6
+ assign_variable,
7
+ identity,
8
+ FixedVariable,
9
+ )
4
10
  from tequila import TequilaException
5
11
  from tequila.objective import QTensor
6
12
  from tequila.simulators.simulator_api import compile
@@ -10,13 +16,13 @@ from tequila.autograd_imports import jax, __AUTOGRAD__BACKEND__
10
16
 
11
17
 
12
18
  def grad(objective: typing.Union[Objective, QTensor], variable: Variable = None, no_compile=False, *args, **kwargs):
13
- '''
19
+ """
14
20
  wrapper function for getting the gradients of Objectives,ExpectationValues, Unitaries (including single gates), and Transforms.
15
21
  :param obj (QCircuit,ParametrizedGateImpl,Objective,ExpectationValue,Transform,Variable): structure to be differentiated
16
22
  :param variables (list of Variable): parameter with respect to which obj should be differentiated.
17
23
  default None: total gradient.
18
24
  return: dictionary of Objectives, if called on gate, circuit, exp.value, or objective; if Variable or Transform, returns number.
19
- '''
25
+ """
20
26
 
21
27
  if variable is None:
22
28
  # None means that all components are created
@@ -27,13 +33,12 @@ def grad(objective: typing.Union[Objective, QTensor], variable: Variable = None,
27
33
  raise TequilaException("Error in gradient: Objective has no variables")
28
34
 
29
35
  for k in variables:
30
- assert (k is not None)
36
+ assert k is not None
31
37
  result[k] = grad(objective, k, no_compile=no_compile)
32
38
  return result
33
39
  else:
34
40
  variable = assign_variable(variable)
35
41
 
36
-
37
42
  if isinstance(objective, QTensor):
38
43
  f = lambda x: grad(objective=x, variable=variable, *args, **kwargs)
39
44
  ff = vectorize(f)
@@ -46,20 +51,25 @@ def grad(objective: typing.Union[Objective, QTensor], variable: Variable = None,
46
51
  # if the objective was already translated to a backend
47
52
  # we need to reverse that here
48
53
  if objective.is_translated():
49
- raise TequilaException("\n\ngradient of:{}\ncan not form gradient that was already compiled to a quantum backend\ntq.grad neds to be applied to the abstract - non compiled objective\nE.g. for the (compiled) objective E1 \n\tE1 = tq.compile(E0)\ninstead of doing\n\tdE = tq.grad(E1)\ndo\n\tdE = tq.grad(E0)\nand compile dE afterwards (if wanted) with\n\tdE = tq.compile(dE)\n".format(str(objective)))
50
-
54
+ raise TequilaException(
55
+ "\n\ngradient of:{}\ncan not form gradient that was already compiled to a quantum backend\ntq.grad neds to be applied to the abstract - non compiled objective\nE.g. for the (compiled) objective E1 \n\tE1 = tq.compile(E0)\ninstead of doing\n\tdE = tq.grad(E1)\ndo\n\tdE = tq.grad(E0)\nand compile dE afterwards (if wanted) with\n\tdE = tq.compile(dE)\n".format(
56
+ str(objective)
57
+ )
58
+ )
51
59
 
52
60
  # circuit compilation
53
61
  if no_compile:
54
62
  compiled = objective
55
63
  else:
56
- compiler = CircuitCompiler(multitarget=True,
57
- trotterized=True,
58
- hadamard_power=True,
59
- power=True,
60
- controlled_phase=True,
61
- controlled_rotation=True,
62
- gradient_mode=True)
64
+ compiler = CircuitCompiler(
65
+ multitarget=True,
66
+ trotterized=True,
67
+ hadamard_power=True,
68
+ power=True,
69
+ controlled_phase=True,
70
+ controlled_rotation=True,
71
+ gradient_mode=True,
72
+ )
63
73
 
64
74
  compiled = compiler(objective, variables=[variable])
65
75
 
@@ -174,15 +184,15 @@ def __grad_objective(objective: Objective, variable: Variable):
174
184
 
175
185
 
176
186
  def __grad_inner(arg, variable):
177
- '''
187
+ """
178
188
  a modified loop over __grad_objective, which gets derivatives
179
189
  all the way down to variables, return 1 or 0 when a variable is (isnt) identical to var.
180
190
  :param arg: a transform or variable object, to be differentiated
181
191
  :param variable: the Variable with respect to which par should be differentiated.
182
192
  :ivar var: the string representation of variable
183
- '''
193
+ """
184
194
 
185
- assert (isinstance(variable, Variable))
195
+ assert isinstance(variable, Variable)
186
196
  if isinstance(arg, Variable):
187
197
  if arg == variable:
188
198
  return 1.0
@@ -196,17 +206,19 @@ def __grad_inner(arg, variable):
196
206
  E = arg.abstract_expectationvalue
197
207
  dE = __grad_expectationvalue(E, variable=variable)
198
208
  return compile(dE, **arg._input_args)
209
+ elif hasattr(arg, "grad"):
210
+ return arg.grad(variable)
199
211
  else:
200
212
  return __grad_objective(objective=arg, variable=variable)
201
213
 
202
214
 
203
215
  def __grad_expectationvalue(E: ExpectationValueImpl, variable: Variable):
204
- '''
216
+ """
205
217
  implements the analytic partial derivative of a unitary as it would appear in an expectation value. See the paper.
206
218
  :param unitary: the unitary whose gradient should be obtained
207
219
  :param variables (list, dict, str): the variables with respect to which differentiation should be performed.
208
220
  :return: vector (as dict) of dU/dpi as Objective (without hamiltonian)
209
- '''
221
+ """
210
222
 
211
223
  hamiltonian = E.H
212
224
  unitary = E.U
@@ -230,7 +242,7 @@ def __grad_expectationvalue(E: ExpectationValueImpl, variable: Variable):
230
242
 
231
243
 
232
244
  def __grad_shift_rule(unitary, g, i, variable, hamiltonian):
233
- '''
245
+ """
234
246
  function for getting the gradients of directly differentiable gates. Expects precompiled circuits.
235
247
  :param unitary: QCircuit: the QCircuit object containing the gate to be differentiated
236
248
  :param g: a parametrized: the gate being differentiated
@@ -239,7 +251,7 @@ def __grad_shift_rule(unitary, g, i, variable, hamiltonian):
239
251
  :param hamiltonian: the hamiltonian with respect to which unitary is to be measured, in the case that unitary
240
252
  is contained within an ExpectationValue
241
253
  :return: an Objective, whose calculation yields the gradient of g w.r.t variable
242
- '''
254
+ """
243
255
 
244
256
  # possibility for overwride in custom gate construction
245
257
  if hasattr(g, "shifted_gates"):
@@ -254,4 +266,4 @@ def __grad_shift_rule(unitary, g, i, variable, hamiltonian):
254
266
  dOinc += wx * Ex
255
267
  return dOinc
256
268
  else:
257
- raise TequilaException('No shift found for gate {}\nWas the compiler called?'.format(g))
269
+ raise TequilaException("No shift found for gate {}\nWas the compiler called?".format(g))
tequila/circuit/noise.py CHANGED
@@ -4,31 +4,32 @@ from tequila.utils import TequilaException
4
4
  import copy
5
5
 
6
6
  names_dict = {
7
- 'x': 'x',
8
- 'y': 'y',
9
- 'z': 'z',
10
- 'h': 'h',
11
- 'rx': 'r',
12
- 'ry': 'r',
13
- 'rz': 'r',
14
- 'r': 'r',
15
- 'phase': 'r',
16
- 'single': 'single',
17
- 'swap': 'control',
18
- 'cx': 'control',
19
- 'cy': 'control',
20
- 'cz': 'control',
21
- 'crx': 'control',
22
- 'cry': 'control',
23
- 'crz': 'control',
24
- 'control': 'control',
25
- 'cnot': 'control',
26
- 'ccnot': 'multicontrol',
27
- 'multicontrol': 'multicontrol'
7
+ "x": "x",
8
+ "y": "y",
9
+ "z": "z",
10
+ "h": "h",
11
+ "rx": "r",
12
+ "ry": "r",
13
+ "rz": "r",
14
+ "r": "r",
15
+ "phase": "r",
16
+ "single": "single",
17
+ "swap": "control",
18
+ "cx": "control",
19
+ "cy": "control",
20
+ "cz": "control",
21
+ "crx": "control",
22
+ "cry": "control",
23
+ "crz": "control",
24
+ "control": "control",
25
+ "cnot": "control",
26
+ "ccnot": "multicontrol",
27
+ "multicontrol": "multicontrol",
28
28
  }
29
29
 
30
- noises_available=['bit flip','phase flip','phase damp','amplitude damp','phase-amplitude damp','depolarizing']
31
- krausses=['bit flip','phase flip','phase damp','amplitude damp','phase-amplitude damp','depolarizing']
30
+ noises_available = ["bit flip", "phase flip", "phase damp", "amplitude damp", "phase-amplitude damp", "depolarizing"]
31
+ krausses = ["bit flip", "phase flip", "phase damp", "amplitude damp", "phase-amplitude damp", "depolarizing"]
32
+
32
33
 
33
34
  class QuantumNoise:
34
35
  """
@@ -48,13 +49,14 @@ class QuantumNoise:
48
49
  from_dict:
49
50
  initialize from a dictionary.
50
51
  """
51
- prob_length={
52
- 'bit flip':1,
53
- 'phase flip':1,
54
- 'phase damp':1,
55
- 'amplitude damp':1,
56
- 'phase-amplitude damp':2,
57
- 'depolarizing':1
52
+
53
+ prob_length = {
54
+ "bit flip": 1,
55
+ "phase flip": 1,
56
+ "phase damp": 1,
57
+ "amplitude damp": 1,
58
+ "phase-amplitude damp": 2,
59
+ "depolarizing": 1,
58
60
  }
59
61
 
60
62
  @property
@@ -65,7 +67,7 @@ class QuantumNoise:
65
67
  def level(self):
66
68
  return self._level
67
69
 
68
- def __init__(self,name:str,probs:typing.List[float],level:int):
70
+ def __init__(self, name: str, probs: typing.List[float], level: int):
69
71
  """
70
72
 
71
73
  Parameters
@@ -77,22 +79,24 @@ class QuantumNoise:
77
79
  level: int:
78
80
  the number of qubits in the gates this noise acts upon.
79
81
  """
80
- probs=list_assignment(probs)
82
+ probs = list_assignment(probs)
81
83
  if name not in noises_available:
82
- raise TequilaException('The name you asked for, {}, is not recognized'.format(name))
83
- self._name=name
84
- self._level=int(level)
84
+ raise TequilaException("The name you asked for, {}, is not recognized".format(name))
85
+ self._name = name
86
+ self._level = int(level)
85
87
 
86
88
  if len(probs) != self.prob_length[name]:
87
- raise TequilaException('{} noise requires {} probabilities; recieved {}'.format(name, self.prob_length[name], len(probs)))
89
+ raise TequilaException(
90
+ "{} noise requires {} probabilities; recieved {}".format(name, self.prob_length[name], len(probs))
91
+ )
88
92
  if name in krausses:
89
- assert sum(probs)<=1.
90
- self.probs=list_assignment(probs)
93
+ assert sum(probs) <= 1.0
94
+ self.probs = list_assignment(probs)
91
95
 
92
96
  def __str__(self):
93
- back=self.name
94
- back+=' on ' + str(self._level) + ' qubit gates'
95
- back+=', probs = ' +str(self.probs)
97
+ back = self.name
98
+ back += " on " + str(self._level) + " qubit gates"
99
+ back += ", probs = " + str(self.probs)
96
100
  return back
97
101
 
98
102
  @staticmethod
@@ -102,10 +106,10 @@ class QuantumNoise:
102
106
  elif type(d) is QuantumNoise:
103
107
  return d
104
108
  else:
105
- raise TequilaException('object provided is neither a dictionary nor a QuantumNoise.')
109
+ raise TequilaException("object provided is neither a dictionary nor a QuantumNoise.")
106
110
 
107
111
 
108
- class NoiseModel():
112
+ class NoiseModel:
109
113
  """
110
114
  class representing noises to apply to a quantum circuit during simulation.
111
115
 
@@ -122,34 +126,35 @@ class NoiseModel():
122
126
  remove all noise of a given type, I.E get rid of all bit flips.
123
127
 
124
128
  """
125
- def __init__(self, noises: typing.List[typing.Union[dict, QuantumNoise]]=None):
129
+
130
+ def __init__(self, noises: typing.List[typing.Union[dict, QuantumNoise]] = None):
126
131
  if noises is None:
127
132
  self.noises = []
128
133
  else:
129
- self.noises=[QuantumNoise.from_dict(d) for d in list_assignment(noises)]
134
+ self.noises = [QuantumNoise.from_dict(d) for d in list_assignment(noises)]
130
135
 
131
136
  def __str__(self):
132
- back='NoiseModel with: \n'
137
+ back = "NoiseModel with: \n"
133
138
  for noise in self.noises:
134
139
  back += str(noise)
135
- back += ',\n'
140
+ back += ",\n"
136
141
  return back
137
142
 
138
143
  def __add__(self, other):
139
- new=NoiseModel()
140
- new.noises+=self.noises
144
+ new = NoiseModel()
145
+ new.noises += self.noises
141
146
  if type(other) is dict:
142
147
  new.noises.append(QuantumNoise.from_dict(other))
143
148
  elif type(other) is QuantumNoise:
144
149
  new.noises.append(other)
145
- elif hasattr(other,'noises'):
150
+ elif hasattr(other, "noises"):
146
151
  new.noises.extend(copy.copy(other.noises))
147
152
  return new
148
153
 
149
154
  def __iadd__(self, other):
150
155
  if type(other) is dict:
151
- self.noises+=QuantumNoise.from_dict(other)
152
- elif hasattr(other,'noises'):
156
+ self.noises += QuantumNoise.from_dict(other)
157
+ elif hasattr(other, "noises"):
153
158
  self.noises.extend(copy.copy(other.noises))
154
159
  return self
155
160
 
@@ -175,7 +180,8 @@ class NoiseModel():
175
180
  def wrap_noise(other):
176
181
  return NoiseModel(noises=other)
177
182
 
178
- def BitFlip(p:float,level:int):
183
+
184
+ def BitFlip(p: float, level: int):
179
185
  """
180
186
  Returns a NoiseModel with one QuantumNoise, having a kraus map corresponding to applying pauli X with likelihood p.
181
187
 
@@ -191,11 +197,12 @@ def BitFlip(p:float,level:int):
191
197
  NoiseModel
192
198
  """
193
199
 
194
- new=NoiseModel.wrap_noise(QuantumNoise(name='bit flip', probs=list_assignment(p), level=level))
200
+ new = NoiseModel.wrap_noise(QuantumNoise(name="bit flip", probs=list_assignment(p), level=level))
195
201
  return new
196
202
 
197
- def PhaseFlip(p:float,level:int):
198
- '''
203
+
204
+ def PhaseFlip(p: float, level: int):
205
+ """
199
206
  Returns a NoiseModel of one QuantumNoise, having a kraus map corresponding to applying pauli Z with likelihood p.
200
207
 
201
208
  Parameters
@@ -208,14 +215,14 @@ def PhaseFlip(p:float,level:int):
208
215
  Returns
209
216
  -------
210
217
  NoiseModel
211
- '''
218
+ """
212
219
 
213
- new=NoiseModel.wrap_noise(QuantumNoise(name='phase flip', probs=list_assignment(p), level=level))
220
+ new = NoiseModel.wrap_noise(QuantumNoise(name="phase flip", probs=list_assignment(p), level=level))
214
221
  return new
215
222
 
216
223
 
217
- def PhaseDamp(p:float,level:int):
218
- '''
224
+ def PhaseDamp(p: float, level: int):
225
+ """
219
226
  Returns a NoiseModel of one QuantumNoise, having a kraus map corresponding to phase damping;
220
227
  Krauss map is defined following Nielsen and Chuang;
221
228
  E_0= [[1,0],
@@ -233,14 +240,14 @@ def PhaseDamp(p:float,level:int):
233
240
  Returns
234
241
  -------
235
242
  NoiseModel
236
- '''
243
+ """
237
244
 
238
- new=NoiseModel.wrap_noise(QuantumNoise(name='phase damp', probs=list_assignment(p), level=level))
245
+ new = NoiseModel.wrap_noise(QuantumNoise(name="phase damp", probs=list_assignment(p), level=level))
239
246
  return new
240
247
 
241
248
 
242
- def AmplitudeDamp(p:float,level:int):
243
- '''
249
+ def AmplitudeDamp(p: float, level: int):
250
+ """
244
251
  Returns a NoiseModel one QuantumNoise, corresponding to amplitude damping.
245
252
  this channel takes 1 to 0, but leaves 0 unaffected.
246
253
  kraus maps:
@@ -260,13 +267,14 @@ def AmplitudeDamp(p:float,level:int):
260
267
  Returns
261
268
  -------
262
269
  NoiseModel
263
- '''
270
+ """
264
271
 
265
- new=NoiseModel.wrap_noise(QuantumNoise(name='amplitude damp', probs=list_assignment(p), level=level))
272
+ new = NoiseModel.wrap_noise(QuantumNoise(name="amplitude damp", probs=list_assignment(p), level=level))
266
273
  return new
267
274
 
268
- def PhaseAmplitudeDamp(p1:float,p2:float,level:int):
269
- '''
275
+
276
+ def PhaseAmplitudeDamp(p1: float, p2: float, level: int):
277
+ """
270
278
  Returns a NoiseModel with one QuantumNoise, having a kraus map corresponding to phase and amplitude damping.
271
279
 
272
280
  Parameters
@@ -281,12 +289,13 @@ def PhaseAmplitudeDamp(p1:float,p2:float,level:int):
281
289
  Returns
282
290
  -------
283
291
  NoiseModel
284
- '''
285
- new=NoiseModel.wrap_noise(QuantumNoise(name='phase-amplitude damp', probs=list_assignment([p1, p2]), level=level))
292
+ """
293
+ new = NoiseModel.wrap_noise(QuantumNoise(name="phase-amplitude damp", probs=list_assignment([p1, p2]), level=level))
286
294
  return new
287
295
 
288
- def DepolarizingError(p:float,level:int):
289
- '''
296
+
297
+ def DepolarizingError(p: float, level: int):
298
+ """
290
299
  Returns a NoiseModel with one QuantumNoise, having a kraus map corresponding to equal
291
300
  probabilities of each of the three pauli matrices being applied.
292
301
 
@@ -300,6 +309,6 @@ def DepolarizingError(p:float,level:int):
300
309
  Returns
301
310
  -------
302
311
  NoiseModel
303
- '''
304
- new = NoiseModel.wrap_noise(QuantumNoise(name='depolarizing', probs=list_assignment(p), level=level))
312
+ """
313
+ new = NoiseModel.wrap_noise(QuantumNoise(name="depolarizing", probs=list_assignment(p), level=level))
305
314
  return new
@@ -0,0 +1,120 @@
1
+ from copy import deepcopy
2
+ from typing import Union
3
+ import numpy as np
4
+ import tequila as tq
5
+ from tequila import QCircuit, QubitWaveFunction, BitNumbering
6
+
7
+
8
+ class Postselection:
9
+ """
10
+ A class representing a postselection operation on a set of qubits,
11
+ i.e. a projection of the wavefunction onto the subspace where all
12
+ selected qubits are in the state |0>.
13
+ """
14
+
15
+ def __init__(self, qubits: list[int]):
16
+ self._qubits = qubits
17
+
18
+ @property
19
+ def qubits(self):
20
+ return self._qubits
21
+
22
+ @qubits.setter
23
+ def qubits(self, qubits: list[int]):
24
+ self._qubits = qubits
25
+
26
+ def mask(self, nbits: int, numbering: BitNumbering) -> int:
27
+ """
28
+ Returns a bitmask for the postselected qubits.
29
+ """
30
+ mask = 0
31
+ for qubit in self.qubits:
32
+ if numbering == BitNumbering.LSB:
33
+ mask |= 1 << qubit
34
+ else:
35
+ mask |= 1 << (nbits - qubit - 1)
36
+ return mask
37
+
38
+
39
+ class PostselectionCircuit:
40
+ """
41
+ An extended circuit class that supports Postselection operations
42
+ which project the wavefunction onto the subspace where a specified
43
+ set of qubits are all in the state |0>.
44
+
45
+ This works by storing a list of fragments which can be either QCircuits
46
+ or Postselection objects. These fragments are processed in order by
47
+ passing the result from the previous fragment as the initial state of its
48
+ successor. When a postselection is encountered, the bitmask is used to
49
+ check which amplitudes belong to the projected subspace, and the rest
50
+ is set to 0.
51
+ """
52
+
53
+ def __init__(self, circuit: tq.QCircuit = None):
54
+ self._fragments: list[Union[QCircuit, Postselection]] = []
55
+ if circuit is not None:
56
+ self._fragments = [circuit]
57
+
58
+ def simulate(
59
+ self,
60
+ backend: str = None,
61
+ initial_wfn: Union[QubitWaveFunction, int] = 0,
62
+ repetitions: int = 1,
63
+ optimize_circuit: bool = True,
64
+ ):
65
+ backend = tq.pick_backend(backend=backend)
66
+ numbering = tq.INSTALLED_SIMULATORS[tq.pick_backend(backend)].CircType.numbering
67
+
68
+ compiled = {}
69
+ for i, fragment in enumerate(self._fragments):
70
+ if isinstance(fragment, QCircuit):
71
+ # TODO: Handle empty qubits properly instead of doing this
72
+ for j in range(self.n_qubits):
73
+ fragment += tq.gates.X(target=j)
74
+ fragment += tq.gates.X(target=j)
75
+ compiled[i] = tq.compile(fragment, backend=backend, optimize_circuit=optimize_circuit)
76
+
77
+ wfn = initial_wfn
78
+ for _ in range(repetitions):
79
+ for i, fragment in enumerate(self._fragments):
80
+ if isinstance(fragment, QCircuit):
81
+ wfn = compiled[i](initial_state=wfn)
82
+ elif isinstance(fragment, Postselection):
83
+ amplitudes = wfn.to_array(numbering, copy=False)
84
+ mask = fragment.mask(self.n_qubits, numbering)
85
+ indices = np.arange(2**self.n_qubits) & mask != 0
86
+ amplitudes[indices] = 0
87
+ wfn = QubitWaveFunction.from_array(amplitudes, numbering, copy=False)
88
+ norm = np.linalg.norm(wfn.to_array(numbering))
89
+ # TODO: Reconsider how to handle norm == 0.0
90
+ normalized_wfn = (1.0 / norm) * wfn if norm != 0.0 else 0.0 * wfn
91
+ return normalized_wfn, norm
92
+
93
+ def __iadd__(self, other: Union[QCircuit, Postselection, "PostselectionCircuit"]):
94
+ if isinstance(other, QCircuit) or isinstance(other, Postselection):
95
+ self.add_fragment(other)
96
+ elif isinstance(other, PostselectionCircuit):
97
+ for fragment in other._fragments:
98
+ self.add_fragment(fragment)
99
+ return self
100
+
101
+ def __add__(self, other: Union[QCircuit, Postselection, "PostselectionCircuit"]):
102
+ result = deepcopy(self)
103
+ result += other
104
+ return result
105
+
106
+ def add_fragment(self, fragment: Union[QCircuit, Postselection]):
107
+ if self._fragments and isinstance(self._fragments[-1], QCircuit) and isinstance(fragment, QCircuit):
108
+ self._fragments[-1] += fragment
109
+ elif self._fragments and isinstance(self._fragments[-1], Postselection) and isinstance(fragment, Postselection):
110
+ self._fragments[-1].qubits += fragment.qubits
111
+ else:
112
+ self._fragments.append(fragment)
113
+ return self
114
+
115
+ @property
116
+ def n_qubits(self):
117
+ if self._fragments:
118
+ return self._fragments[0].n_qubits
119
+ else:
120
+ return 0
tequila/circuit/pyzx.py CHANGED
@@ -4,17 +4,19 @@ Add to tequila the ability to make ZX-Calculus
4
4
  Using the pyzx library: https://github.com/Quantomatic/pyzx
5
5
  """
6
6
 
7
+ from tequila import TequilaException
8
+ from tequila import export_open_qasm, import_open_qasm
9
+ from tequila.circuit import QCircuit
10
+
11
+
7
12
  HAS_PYZX = True
8
13
  try:
9
14
  import pyzx
15
+
10
16
  HAS_PYZX = True
11
17
  except ImportError:
12
18
  HAS_PYZX = False
13
19
 
14
- from tequila import TequilaException
15
- from tequila import export_open_qasm, import_open_qasm
16
- from tequila.circuit import QCircuit
17
-
18
20
 
19
21
  def convert_to_pyzx(circuit: QCircuit, variables=None):
20
22
  """
@@ -28,7 +30,9 @@ def convert_to_pyzx(circuit: QCircuit, variables=None):
28
30
  pyzx.circuit.Circuit: pyzx circuit
29
31
  """
30
32
  if HAS_PYZX:
31
- return pyzx.circuit.Circuit.from_qasm(export_open_qasm(circuit=circuit, variables=variables, version="2.0", zx_calculus=True))
33
+ return pyzx.circuit.Circuit.from_qasm(
34
+ export_open_qasm(circuit=circuit, variables=variables, version="2.0", zx_calculus=True)
35
+ )
32
36
  else:
33
37
  raise TequilaException("Pyzx package not installed.")
34
38
 
@@ -49,4 +53,4 @@ def convert_from_pyzx(circuit) -> QCircuit:
49
53
  else:
50
54
  raise TequilaException("Circuit provided must be of type pyzx.circuit.Circuit")
51
55
  else:
52
- raise TequilaException("Pyzx package not installed.")
56
+ raise TequilaException("Pyzx package not installed.")