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
tequila/circuit/gradient.py
CHANGED
@@ -1,6 +1,12 @@
|
|
1
1
|
from tequila.circuit.compiler import CircuitCompiler
|
2
|
-
from tequila.objective.objective import
|
3
|
-
|
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
|
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(
|
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(
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
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
|
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(
|
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
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
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=[
|
31
|
-
krausses=[
|
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
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
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(
|
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(
|
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+=
|
95
|
-
back+=
|
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(
|
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
|
-
|
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=
|
137
|
+
back = "NoiseModel with: \n"
|
133
138
|
for noise in self.noises:
|
134
139
|
back += str(noise)
|
135
|
-
back +=
|
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,
|
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,
|
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
|
-
|
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=
|
200
|
+
new = NoiseModel.wrap_noise(QuantumNoise(name="bit flip", probs=list_assignment(p), level=level))
|
195
201
|
return new
|
196
202
|
|
197
|
-
|
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=
|
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=
|
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=
|
272
|
+
new = NoiseModel.wrap_noise(QuantumNoise(name="amplitude damp", probs=list_assignment(p), level=level))
|
266
273
|
return new
|
267
274
|
|
268
|
-
|
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=
|
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
|
-
|
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=
|
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(
|
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.")
|