pyqrack-cpu-complex128 1.71.1__py3-none-win_amd64.whl → 1.78.3__py3-none-win_amd64.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.
- pyqrack/__init__.py +3 -2
- pyqrack/qrack_ace_backend.py +28 -24
- pyqrack/qrack_circuit.py +49 -34
- pyqrack/qrack_neuron.py +93 -5
- pyqrack/qrack_neuron_torch_layer.py +294 -104
- pyqrack/qrack_simulator.py +232 -183
- pyqrack/qrack_system/qrack_lib/qrack_pinvoke.dll +0 -0
- pyqrack/qrack_system/qrack_system.py +6 -0
- {pyqrack_cpu_complex128-1.71.1.dist-info → pyqrack_cpu_complex128-1.78.3.dist-info}/METADATA +2 -4
- pyqrack_cpu_complex128-1.78.3.dist-info/RECORD +21 -0
- pyqrack_cpu_complex128-1.71.1.dist-info/RECORD +0 -21
- {pyqrack_cpu_complex128-1.71.1.dist-info → pyqrack_cpu_complex128-1.78.3.dist-info}/LICENSE +0 -0
- {pyqrack_cpu_complex128-1.71.1.dist-info → pyqrack_cpu_complex128-1.78.3.dist-info}/WHEEL +0 -0
- {pyqrack_cpu_complex128-1.71.1.dist-info → pyqrack_cpu_complex128-1.78.3.dist-info}/top_level.txt +0 -0
|
@@ -6,6 +6,10 @@
|
|
|
6
6
|
# Use of this source code is governed by an MIT-style license that can be
|
|
7
7
|
# found in the LICENSE file or at https://opensource.org/licenses/MIT.
|
|
8
8
|
|
|
9
|
+
import itertools
|
|
10
|
+
import math
|
|
11
|
+
import sys
|
|
12
|
+
|
|
9
13
|
_IS_TORCH_AVAILABLE = True
|
|
10
14
|
try:
|
|
11
15
|
import torch
|
|
@@ -14,80 +18,143 @@ try:
|
|
|
14
18
|
except ImportError:
|
|
15
19
|
_IS_TORCH_AVAILABLE = False
|
|
16
20
|
|
|
21
|
+
from .pauli import Pauli
|
|
17
22
|
from .qrack_neuron import QrackNeuron
|
|
23
|
+
from .qrack_simulator import QrackSimulator
|
|
18
24
|
from .neuron_activation_fn import NeuronActivationFn
|
|
19
25
|
|
|
20
|
-
from itertools import chain, combinations
|
|
21
26
|
|
|
27
|
+
# Should be safe for 16-bit
|
|
28
|
+
angle_eps = math.pi * (2 ** -8)
|
|
22
29
|
|
|
23
|
-
# From https://stackoverflow.com/questions/1482308/how-to-get-all-subsets-of-a-set-powerset#answer-1482316
|
|
24
|
-
def powerset(iterable):
|
|
25
|
-
"powerset([1,2,3]) --> () (1,) (2,) (3,) (1,2) (1,3) (2,3,) (1,2,3)"
|
|
26
|
-
s = list(iterable)
|
|
27
|
-
return chain.from_iterable(combinations(s, r) for r in range(len(s) + 1))
|
|
28
30
|
|
|
31
|
+
if not _IS_TORCH_AVAILABLE:
|
|
32
|
+
class TorchContextMock(object):
|
|
33
|
+
def __init__(self):
|
|
34
|
+
pass
|
|
29
35
|
|
|
30
|
-
|
|
31
|
-
|
|
36
|
+
def save_for_backward(self, *args):
|
|
37
|
+
self.saved_tensors = args
|
|
32
38
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
"""
|
|
39
|
+
class QrackNeuronTorchFunction(Function if _IS_TORCH_AVAILABLE else object):
|
|
40
|
+
"""Static forward/backward/apply functions for QrackNeuronTorch"""
|
|
36
41
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
42
|
+
if not _IS_TORCH_AVAILABLE:
|
|
43
|
+
@staticmethod
|
|
44
|
+
def apply(x, neuron_wrapper):
|
|
45
|
+
return forward(TorchContextMock(), x, neuron_wrapper)
|
|
40
46
|
|
|
41
|
-
|
|
42
|
-
|
|
47
|
+
@staticmethod
|
|
48
|
+
def forward(ctx, x, neuron_wrapper):
|
|
49
|
+
ctx.neuron_wrapper = neuron_wrapper
|
|
50
|
+
ctx.save_for_backward(x)
|
|
51
|
+
neuron = neuron_wrapper.neuron
|
|
52
|
+
|
|
53
|
+
angles = (x.detach().cpu().numpy() if x.requires_grad else x.numpy()) if _IS_TORCH_AVAILABLE else x
|
|
54
|
+
neuron.set_angles(angles)
|
|
43
55
|
neuron.predict(True, False)
|
|
56
|
+
post_prob = neuron.simulator.prob(neuron.target)
|
|
57
|
+
if _IS_TORCH_AVAILABLE:
|
|
58
|
+
post_prob = torch.tensor([post_prob], dtype=torch.float32, device=x.device)
|
|
44
59
|
|
|
45
|
-
return
|
|
60
|
+
return post_prob
|
|
46
61
|
|
|
62
|
+
@staticmethod
|
|
63
|
+
def _backward(x, neuron_wrapper):
|
|
64
|
+
neuron = neuron_wrapper.neuron
|
|
65
|
+
angles = (x.detach().cpu().numpy() if x.requires_grad else x.numpy()) if _IS_TORCH_AVAILABLE else x
|
|
47
66
|
|
|
48
|
-
|
|
49
|
-
|
|
67
|
+
# Uncompute
|
|
68
|
+
neuron.set_angles(angles)
|
|
69
|
+
neuron.unpredict()
|
|
70
|
+
pre_sim = neuron.simulator
|
|
71
|
+
pre_prob = pre_sim.prob(neuron.target)
|
|
50
72
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
73
|
+
param_count = 1 << len(neuron.controls)
|
|
74
|
+
delta = [0.0] * param_count
|
|
75
|
+
for param in range(param_count):
|
|
76
|
+
angle = angles[param]
|
|
55
77
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
78
|
+
# x + angle_eps
|
|
79
|
+
angles[param] = angle + angle_eps
|
|
80
|
+
neuron.set_angles(angles)
|
|
81
|
+
neuron.simulator = pre_sim.clone()
|
|
82
|
+
neuron.predict(True, False)
|
|
83
|
+
p_plus = neuron.simulator.prob(neuron.target)
|
|
84
|
+
|
|
85
|
+
# x - angle_eps
|
|
86
|
+
angles[param] = angle - angle_eps
|
|
87
|
+
neuron.set_angles(angles)
|
|
88
|
+
neuron.simulator = pre_sim.clone()
|
|
89
|
+
neuron.predict(True, False)
|
|
90
|
+
p_minus = neuron.simulator.prob(neuron.target)
|
|
91
|
+
|
|
92
|
+
# Central difference
|
|
93
|
+
delta[param] = (p_plus - p_minus) / (2 * angle_eps)
|
|
60
94
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
95
|
+
angles[param] = angle
|
|
96
|
+
|
|
97
|
+
neuron.simulator = pre_sim
|
|
98
|
+
|
|
99
|
+
if _IS_TORCH_AVAILABLE:
|
|
100
|
+
delta = torch.tensor(delta, dtype=torch.float32, device=x.device)
|
|
101
|
+
|
|
102
|
+
return delta
|
|
66
103
|
|
|
67
104
|
@staticmethod
|
|
68
105
|
def backward(ctx, grad_output):
|
|
69
|
-
|
|
106
|
+
(x,) = ctx.saved_tensors
|
|
107
|
+
neuron_wrapper = ctx.neuron_wrapper
|
|
108
|
+
delta = _backward(x, neuron_wrapper, grad_output)
|
|
109
|
+
if _IS_TORCH_AVAILABLE:
|
|
110
|
+
# grad_output: (O,)
|
|
111
|
+
# delta: (O, I)
|
|
112
|
+
grad_input = torch.matmul(grad_output, delta) # result: (I,)
|
|
113
|
+
else:
|
|
114
|
+
grad_input = [
|
|
115
|
+
sum(o * d for o, d in zip(grad_output, col))
|
|
116
|
+
for col in zip(*delta)
|
|
117
|
+
]
|
|
70
118
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
119
|
+
return grad_input, None
|
|
120
|
+
|
|
121
|
+
class QrackNeuronTorch(nn.Module if _IS_TORCH_AVAILABLE else object):
|
|
122
|
+
"""Torch wrapper for QrackNeuron
|
|
75
123
|
|
|
76
|
-
|
|
124
|
+
Attributes:
|
|
125
|
+
neuron(QrackNeuron): QrackNeuron backing this torch wrapper
|
|
126
|
+
"""
|
|
77
127
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
128
|
+
def __init__(self, neuron):
|
|
129
|
+
super().__init__()
|
|
130
|
+
self.neuron = neuron
|
|
131
|
+
|
|
132
|
+
def forward(self, x):
|
|
133
|
+
return QrackNeuronTorchFunction.apply(x, self.neuron)
|
|
81
134
|
|
|
82
135
|
|
|
83
136
|
class QrackNeuronTorchLayer(nn.Module if _IS_TORCH_AVAILABLE else object):
|
|
84
|
-
"""Torch layer wrapper for QrackNeuron (with
|
|
137
|
+
"""Torch layer wrapper for QrackNeuron (with maximally expressive set of neurons between inputs and outputs)
|
|
138
|
+
|
|
139
|
+
Attributes:
|
|
140
|
+
simulator (QrackSimulator): Prototype simulator that batching copies to use with QrackNeuron instances
|
|
141
|
+
simulators (list[QrackSimulator]): In-flight copies of prototype simulator corresponding to batch count
|
|
142
|
+
input_indices (list[int], read-only): simulator qubit indices used as QrackNeuron inputs
|
|
143
|
+
output_indices (list[int], read-only): simulator qubit indices used as QrackNeuron outputs
|
|
144
|
+
hidden_indices (list[int], read-only): simulator qubit indices used as QrackNeuron hidden inputs (in maximal superposition)
|
|
145
|
+
neurons (ModuleList[QrackNeuronTorch]): QrackNeuronTorch wrappers (for PyQrack QrackNeurons) in this layer, corresponding to weights
|
|
146
|
+
weights (ParameterList): List of tensors corresponding one-to-one with weights of list of neurons
|
|
147
|
+
apply_fn (Callable[Tensor, QrackNeuronTorch]): Corresponds to QrackNeuronTorchFunction.apply(x, neuron_wrapper) (or override with a custom implementation)
|
|
148
|
+
backward_fn (Callable[Tensor, Tensor]): Corresponds to QrackNeuronTorchFunction._backward(x, neuron_wrapper) (or override with a custom implementation)
|
|
149
|
+
"""
|
|
85
150
|
|
|
86
151
|
def __init__(
|
|
87
152
|
self,
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
153
|
+
input_qubits,
|
|
154
|
+
output_qubits,
|
|
155
|
+
hidden_qubits=None,
|
|
156
|
+
lowest_combo_count=0,
|
|
157
|
+
highest_combo_count=2,
|
|
91
158
|
activation=int(NeuronActivationFn.Generalized_Logistic),
|
|
92
159
|
parameters=None,
|
|
93
160
|
):
|
|
@@ -97,74 +164,197 @@ class QrackNeuronTorchLayer(nn.Module if _IS_TORCH_AVAILABLE else object):
|
|
|
97
164
|
|
|
98
165
|
Args:
|
|
99
166
|
sim (QrackSimulator): Simulator into which predictor features are loaded
|
|
100
|
-
|
|
101
|
-
|
|
167
|
+
input_qubits (int): Count of inputs (1 per qubit)
|
|
168
|
+
output_qubits (int): Count of outputs (1 per qubit)
|
|
169
|
+
hidden_qubits (int): Count of "hidden" inputs (1 per qubit, always initialized to |+>, suggested to be same a highest_combo_count)
|
|
170
|
+
lowest_combo_count (int): Lowest combination count of input qubits iterated (0 is bias)
|
|
171
|
+
highest_combo_count (int): Highest combination count of input qubits iterated
|
|
102
172
|
activation (int): Integer corresponding to choice of activation function from NeuronActivationFn
|
|
103
|
-
parameters (list[float]): (Optional) Flat list of initial neuron parameters, corresponding to little-endian basis states of
|
|
173
|
+
parameters (list[float]): (Optional) Flat list of initial neuron parameters, corresponding to little-endian basis states of input + hidden qubits, repeated for ascending combo count, repeated for each output index
|
|
104
174
|
"""
|
|
105
175
|
super(QrackNeuronTorchLayer, self).__init__()
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
self.
|
|
176
|
+
if hidden_qubits is None:
|
|
177
|
+
hidden_qubits = highest_combo_count
|
|
178
|
+
self.simulator = QrackSimulator(input_qubits + hidden_qubits + output_qubits)
|
|
179
|
+
self.simulators = []
|
|
180
|
+
self.input_indices = list(range(input_qubits))
|
|
181
|
+
self.hidden_indices = list(range(input_qubits, input_qubits + hidden_qubits))
|
|
182
|
+
self.output_indices = list(range(input_qubits + hidden_qubits, input_qubits + hidden_qubits + output_qubits))
|
|
109
183
|
self.activation = NeuronActivationFn(activation)
|
|
110
|
-
self.
|
|
111
|
-
|
|
112
|
-
if _IS_TORCH_AVAILABLE
|
|
113
|
-
else lambda x: QrackNeuronFunction.forward(object(), x)
|
|
114
|
-
)
|
|
115
|
-
|
|
116
|
-
# Create neurons from all powerset input combinations, projecting to coherent output qubits
|
|
117
|
-
self.neurons = nn.ModuleList(
|
|
118
|
-
[
|
|
119
|
-
QrackTorchNeuron(
|
|
120
|
-
QrackNeuron(simulator, list(input_subset), output_id, activation)
|
|
121
|
-
)
|
|
122
|
-
for input_subset in powerset(input_indices)
|
|
123
|
-
for output_id in output_indices
|
|
124
|
-
]
|
|
125
|
-
)
|
|
184
|
+
self.apply_fn = QrackNeuronTorchFunction.apply
|
|
185
|
+
self.backward_fn = QrackNeuronTorchFunction._backward
|
|
126
186
|
|
|
127
|
-
#
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
p_count = 1 << len(neuron.controls)
|
|
132
|
-
neuron.set_angles(
|
|
133
|
-
parameters[param_count : (param_count + p_count + 1)]
|
|
134
|
-
if parameters
|
|
135
|
-
else ([0.0] * p_count)
|
|
187
|
+
# Create neurons from all input combinations, projecting to coherent output qubits
|
|
188
|
+
neurons = [
|
|
189
|
+
QrackNeuronTorch(
|
|
190
|
+
QrackNeuron(self.simulator, input_subset, output_id, activation)
|
|
136
191
|
)
|
|
137
|
-
|
|
192
|
+
for output_id in self.output_indices
|
|
193
|
+
for k in range(lowest_combo_count, highest_combo_count + 1)
|
|
194
|
+
for input_subset in itertools.combinations(self.input_indices + self.hidden_indices, k)
|
|
195
|
+
]
|
|
196
|
+
self.neurons = nn.ModuleList(neurons) if _IS_TORCH_AVAILABLE else neurons
|
|
138
197
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
198
|
+
# Set Qrack's internal parameters:
|
|
199
|
+
if parameters:
|
|
200
|
+
param_count = 0
|
|
201
|
+
self.weights = nn.ParameterList() if _IS_TORCH_AVAILABLE else []
|
|
202
|
+
for neuron_wrapper in self.neurons:
|
|
203
|
+
neuron = neuron_wrapper.neuron
|
|
204
|
+
p_count = 1 << len(neuron.controls)
|
|
205
|
+
neuron.set_angles(parameters[param_count : (param_count + p_count)])
|
|
206
|
+
self.weights.append(
|
|
207
|
+
nn.Parameter(torch.tensor(parameters[param_count : (param_count + p_count)]))
|
|
208
|
+
if _IS_TORCH_AVAILABLE else parameters[param_count : (param_count + p_count)]
|
|
209
|
+
)
|
|
210
|
+
param_count += p_count
|
|
211
|
+
else:
|
|
212
|
+
self.weights = nn.ParameterList() if _IS_TORCH_AVAILABLE else []
|
|
213
|
+
for neuron_wrapper in self.neurons:
|
|
214
|
+
neuron = neuron_wrapper.neuron
|
|
215
|
+
p_count = 1 << len(neuron.controls)
|
|
216
|
+
self.weights.append(nn.Parameter(torch.zeros(p_count)) if _IS_TORCH_AVAILABLE else ([0.0] * p_count))
|
|
144
217
|
|
|
145
|
-
def forward(self,
|
|
146
|
-
|
|
147
|
-
for output_id in self.output_indices:
|
|
148
|
-
if self.simulator.m(output_id):
|
|
149
|
-
self.simulator.x(output_id)
|
|
150
|
-
self.simulator.h(output_id)
|
|
218
|
+
def forward(self, x):
|
|
219
|
+
return QrackNeuronTorchLayerFunction.apply(x, self)
|
|
151
220
|
|
|
152
|
-
# Set Qrack's internal parameters:
|
|
153
|
-
param_count = 0
|
|
154
|
-
for neuron_wrapper in self.neurons:
|
|
155
|
-
neuron = neuron_wrapper.neuron
|
|
156
|
-
p_count = 1 << len(neuron.controls)
|
|
157
|
-
angles = [
|
|
158
|
-
w.item() for w in self.weights[param_count : (param_count + p_count)]
|
|
159
|
-
]
|
|
160
|
-
neuron.set_angles(angles)
|
|
161
|
-
param_count += p_count
|
|
162
221
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
self.fn(neuron_wrapper.neuron)
|
|
222
|
+
class QrackNeuronTorchLayerFunction(Function if _IS_TORCH_AVAILABLE else object):
|
|
223
|
+
"""Static forward/backward/apply functions for QrackNeuronTorch"""
|
|
166
224
|
|
|
167
|
-
|
|
168
|
-
|
|
225
|
+
@staticmethod
|
|
226
|
+
def forward(ctx, x, neuron_layer):
|
|
227
|
+
# Save for backward
|
|
228
|
+
ctx.save_for_backward(x)
|
|
229
|
+
ctx.neuron_layer = neuron_layer
|
|
230
|
+
|
|
231
|
+
input_indices = neuron_layer.input_indices
|
|
232
|
+
hidden_indices = neuron_layer.hidden_indices
|
|
233
|
+
output_indices = neuron_layer.output_indices
|
|
234
|
+
simulators = neuron_layer.simulators
|
|
235
|
+
weights = neuron_layer.weights
|
|
236
|
+
|
|
237
|
+
if _IS_TORCH_AVAILABLE:
|
|
238
|
+
B = x.shape[0]
|
|
239
|
+
x = x.view(B, -1)
|
|
240
|
+
else:
|
|
241
|
+
B = len(x)
|
|
242
|
+
|
|
243
|
+
simulators.clear()
|
|
244
|
+
if _IS_TORCH_AVAILABLE:
|
|
245
|
+
for b in range(B):
|
|
246
|
+
simulator = neuron_layer.simulator.clone()
|
|
247
|
+
simulators.append(simulator)
|
|
248
|
+
for q, input_id in enumerate(input_indices):
|
|
249
|
+
simulator.r(Pauli.PauliY, math.pi * x[b, q].item(), q)
|
|
250
|
+
else:
|
|
251
|
+
for b in range(B):
|
|
252
|
+
simulator = neuron_layer.simulator.clone()
|
|
253
|
+
simulators.append(simulator)
|
|
254
|
+
for q, input_id in enumerate(input_indices):
|
|
255
|
+
simulator.r(Pauli.PauliY, math.pi * x[b][q], q)
|
|
256
|
+
|
|
257
|
+
y = [([0.0] * len(output_indices)) for _ in range(B)]
|
|
258
|
+
for b in range(B):
|
|
259
|
+
simulator = simulators[b]
|
|
260
|
+
# Prepare a maximally uncertain output state.
|
|
261
|
+
for output_id in output_indices:
|
|
262
|
+
simulator.h(output_id)
|
|
263
|
+
# Prepare hidden predictors
|
|
264
|
+
for h in hidden_indices:
|
|
265
|
+
simulator.h(h)
|
|
266
|
+
|
|
267
|
+
# Set Qrack's internal parameters:
|
|
268
|
+
for idx, neuron_wrapper in enumerate(neuron_layer.neurons):
|
|
269
|
+
neuron_wrapper.neuron.simulator = simulator
|
|
270
|
+
neuron_layer.apply_fn(weights[idx], neuron_wrapper)
|
|
271
|
+
|
|
272
|
+
for q, output_id in enumerate(output_indices):
|
|
273
|
+
y[b][q] = simulator.prob(output_id)
|
|
274
|
+
|
|
275
|
+
if _IS_TORCH_AVAILABLE:
|
|
276
|
+
y = torch.tensor(y, dtype=torch.float32, device=x.device)
|
|
277
|
+
|
|
278
|
+
return y
|
|
169
279
|
|
|
170
|
-
|
|
280
|
+
@staticmethod
|
|
281
|
+
def backward(ctx, grad_output):
|
|
282
|
+
(x,) = ctx.saved_tensors
|
|
283
|
+
neuron_layer = ctx.neuron_layer
|
|
284
|
+
|
|
285
|
+
input_indices = neuron_layer.input_indices
|
|
286
|
+
hidden_indices = neuron_layer.hidden_indices
|
|
287
|
+
output_indices = neuron_layer.output_indices
|
|
288
|
+
simulators = neuron_layer.simulators
|
|
289
|
+
neurons = neuron_layer.neurons
|
|
290
|
+
backward_fn = neuron_layer.backward_fn
|
|
291
|
+
|
|
292
|
+
input_count = len(input_indices)
|
|
293
|
+
output_count = len(output_indices)
|
|
294
|
+
|
|
295
|
+
if _IS_TORCH_AVAILABLE:
|
|
296
|
+
B = x.shape[0]
|
|
297
|
+
x = x.view(B, -1)
|
|
298
|
+
else:
|
|
299
|
+
B = len(x)
|
|
300
|
+
|
|
301
|
+
# Uncompute prediction
|
|
302
|
+
if _IS_TORCH_AVAILABLE:
|
|
303
|
+
delta = torch.zeros((B, output_count, input_count), dtype=torch.float32, device=x.device)
|
|
304
|
+
for b in range(B):
|
|
305
|
+
simulator = simulators[b]
|
|
306
|
+
for neuron_wrapper in neurons:
|
|
307
|
+
neuron = neuron_wrapper.neuron
|
|
308
|
+
neuron.simulator = simulator
|
|
309
|
+
angles = torch.tensor(neuron.get_angles(), dtype=torch.float32, device=x.device, requires_grad=True)
|
|
310
|
+
o = output_indices.index(neuron.target)
|
|
311
|
+
neuron_grad = backward_fn(angles, neuron_wrapper)
|
|
312
|
+
for idx, c in enumerate(neuron.controls):
|
|
313
|
+
if c not in input_indices:
|
|
314
|
+
continue
|
|
315
|
+
i = input_indices.index(c)
|
|
316
|
+
delta[b, o, i] += neuron_grad[idx]
|
|
317
|
+
else:
|
|
318
|
+
delta = [[[0.0] * input_count for _ in range(output_count)] for _ in range(B)]
|
|
319
|
+
for b in range(B):
|
|
320
|
+
simulator = simulators[b]
|
|
321
|
+
for neuron_wrapper in neurons:
|
|
322
|
+
neuron = neuron_wrapper.neuron
|
|
323
|
+
neuron.simulator = simulator
|
|
324
|
+
angles = neuron.get_angles()
|
|
325
|
+
o = output_indices.index(neuron.target)
|
|
326
|
+
neuron_grad = backward_fn(angles, neuron_wrapper)
|
|
327
|
+
for idx, c in enumerate(neuron.controls):
|
|
328
|
+
if c not in input_indices:
|
|
329
|
+
continue
|
|
330
|
+
i = input_indices.index(c)
|
|
331
|
+
delta[b][o][i] += neuron_grad[idx]
|
|
332
|
+
|
|
333
|
+
# Uncompute output state prep
|
|
334
|
+
for simulator in simulators:
|
|
335
|
+
for output_id in output_indices:
|
|
336
|
+
simulator.h(output_id)
|
|
337
|
+
for h in hidden_indices:
|
|
338
|
+
simulator.h(output_id)
|
|
339
|
+
|
|
340
|
+
if _IS_TORCH_AVAILABLE:
|
|
341
|
+
for b in range(B):
|
|
342
|
+
simulator = simulators[b]
|
|
343
|
+
for q, input_id in enumerate(input_indices):
|
|
344
|
+
simulator.r(Pauli.PauliY, -math.pi * x[b, q].item(), q)
|
|
345
|
+
else:
|
|
346
|
+
for b in range(B):
|
|
347
|
+
simulator = simulators[b]
|
|
348
|
+
for q, input_id in enumerate(input_indices):
|
|
349
|
+
simulator.r(Pauli.PauliY, -math.pi * x[b][q].item(), q)
|
|
350
|
+
|
|
351
|
+
if _IS_TORCH_AVAILABLE:
|
|
352
|
+
grad_input = torch.matmul(grad_output.view(B, 1, -1), delta).view_as(x)
|
|
353
|
+
else:
|
|
354
|
+
grad_input = [[0.0] * output_count for _ in range(B)]
|
|
355
|
+
for b in range(B):
|
|
356
|
+
for o in range(output_indices):
|
|
357
|
+
for i in range(input_indices):
|
|
358
|
+
grad_input[b][o] += grad_output[b][o] * delta[b][o][i]
|
|
359
|
+
|
|
360
|
+
return grad_input, None
|