pyqrack 1.70.0__py3-none-macosx_13_0_x86_64.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.
@@ -0,0 +1,262 @@
1
+ # (C) Daniel Strano and the Qrack contributors 2017-2025. All rights reserved.
2
+ #
3
+ # Use of this source code is governed by an MIT-style license that can be
4
+ # found in the LICENSE file or at https://opensource.org/licenses/MIT.
5
+
6
+ import ctypes
7
+ import sys
8
+
9
+ from .qrack_system import Qrack
10
+ from .neuron_activation_fn import NeuronActivationFn
11
+
12
+
13
+ class QrackNeuron:
14
+ """Class that exposes the QNeuron class of Qrack
15
+
16
+ This model of a "quantum neuron" is based on the concept of a "uniformly controlled"
17
+ rotation of a single output qubit around the Pauli Y axis, and has been developed by
18
+ others. In our case, the primary relevant gate could also be called a
19
+ single-qubit-target multiplexer.
20
+
21
+ (See https://arxiv.org/abs/quant-ph/0407010 for an introduction to "uniformly controlled
22
+ gates.)
23
+
24
+ QrackNeuron is meant to be interchangeable with a single classical neuron, as in
25
+ conventional neural net software. It differs from classical neurons in conventional
26
+ neural nets, in that the "synaptic cleft" is modelled as a single qubit. Hence, this
27
+ neuron can train and predict in superposition.
28
+
29
+ Attributes:
30
+ nid(int): Qrack ID of this neuron
31
+ simulator(QrackSimulator): Simulator instance for all synaptic clefts of the neuron
32
+ controls(list(int)): Indices of all "control" qubits, for neuron input
33
+ target(int): Index of "target" qubit, for neuron output
34
+ tolerance(double): Rounding tolerance
35
+ """
36
+
37
+ def _get_error(self):
38
+ return Qrack.qrack_lib.get_error(self.simulator.sid)
39
+
40
+ def _throw_if_error(self):
41
+ if self._get_error() != 0:
42
+ raise RuntimeError("QrackNeuron C++ library raised exception.")
43
+
44
+ def __init__(
45
+ self,
46
+ simulator,
47
+ controls,
48
+ target,
49
+ activation_fn=NeuronActivationFn.Sigmoid,
50
+ alpha=1.0,
51
+ tolerance=sys.float_info.epsilon,
52
+ _init=True,
53
+ ):
54
+ self.simulator = simulator
55
+ self.controls = controls
56
+ self.target = target
57
+ self.activation_fn = activation_fn
58
+ self.alpha = alpha
59
+ self.tolerance = tolerance
60
+
61
+ if not _init:
62
+ return
63
+
64
+ self.nid = Qrack.qrack_lib.init_qneuron(
65
+ simulator.sid,
66
+ len(controls),
67
+ self._ulonglong_byref(controls),
68
+ target,
69
+ activation_fn,
70
+ alpha,
71
+ tolerance,
72
+ )
73
+
74
+ self._throw_if_error()
75
+
76
+ def __del__(self):
77
+ if self.nid is not None:
78
+ Qrack.qrack_lib.destroy_qneuron(self.nid)
79
+ self.nid = None
80
+
81
+ def clone(self):
82
+ """Clones this neuron.
83
+
84
+ Create a new, independent neuron instance with identical angles,
85
+ inputs, output, and tolerance, for the same QrackSimulator.
86
+
87
+ Raises:
88
+ RuntimeError: QrackNeuron C++ library raised an exception.
89
+ """
90
+ result = QrackNeuron(
91
+ self.simulator,
92
+ self.controls,
93
+ self.target,
94
+ self.activation_fn,
95
+ self.alpha,
96
+ self.tolerance,
97
+ )
98
+ self.nid = Qrack.qrack_lib.clone_qneuron(self.simulator.sid)
99
+ self._throw_if_error()
100
+ return result
101
+
102
+ def _ulonglong_byref(self, a):
103
+ return (ctypes.c_ulonglong * len(a))(*a)
104
+
105
+ def _real1_byref(self, a):
106
+ # This needs to be c_double, if PyQrack is built with fp64.
107
+ if Qrack.fppow < 6:
108
+ return (ctypes.c_float * len(a))(*a)
109
+ return (ctypes.c_double * len(a))(*a)
110
+
111
+ def set_angles(self, a):
112
+ """Directly sets the neuron parameters.
113
+
114
+ Set all synaptic parameters of the neuron directly, by a list
115
+ enumerated over the integer permutations of input qubits.
116
+
117
+ Args:
118
+ a(list(double)): List of input permutation angles
119
+
120
+ Raises:
121
+ ValueError: Angles 'a' in QrackNeuron.set_angles() must contain at least (2 ** len(self.controls)) elements.
122
+ RuntimeError: QrackSimulator raised an exception.
123
+ """
124
+ if len(a) < (1 << len(self.controls)):
125
+ raise ValueError(
126
+ "Angles 'a' in QrackNeuron.set_angles() must contain at least (2 ** len(self.controls)) elements."
127
+ )
128
+ Qrack.qrack_lib.set_qneuron_angles(self.nid, self._real1_byref(a))
129
+ self._throw_if_error()
130
+
131
+ def get_angles(self):
132
+ """Directly gets the neuron parameters.
133
+
134
+ Get all synaptic parameters of the neuron directly, as a list
135
+ enumerated over the integer permutations of input qubits.
136
+
137
+ Raises:
138
+ RuntimeError: QrackNeuron C++ library raised an exception.
139
+ """
140
+ ket = self._real1_byref([0.0] * (1 << len(self.controls)))
141
+ Qrack.qrack_lib.get_qneuron_angles(self.nid, ket)
142
+ self._throw_if_error()
143
+ return list(ket)
144
+
145
+ def set_alpha(self, a):
146
+ """Set the neuron 'alpha' parameter.
147
+
148
+ To enable nonlinear activation, `QrackNeuron` has an 'alpha'
149
+ parameter that is applied as a power to its angles, before
150
+ learning and prediction. This makes the activation function
151
+ sharper (or less sharp).
152
+
153
+ Raises:
154
+ RuntimeError: QrackNeuron C++ library raised an exception.
155
+ """
156
+ self.alpha = a
157
+ Qrack.qrack_lib.set_qneuron_alpha(self.nid, a)
158
+ self._throw_if_error()
159
+
160
+ def set_activation_fn(self, f):
161
+ """Sets the activation function of this QrackNeuron
162
+
163
+ Nonlinear activation functions can be important to neural net
164
+ applications, like DNN. The available activation functions are
165
+ enumerated in `NeuronActivationFn`.
166
+
167
+ Raises:
168
+ RuntimeError: QrackNeuron C++ library raised an exception.
169
+ """
170
+ self.activation_fn = f
171
+ Qrack.qrack_lib.set_qneuron_activation_fn(self.nid, f)
172
+ self._throw_if_error()
173
+
174
+ def predict(self, e=True, r=True):
175
+ """Predict based on training
176
+
177
+ "Predict" the anticipated output, based on input and training.
178
+ By default, "predict()" will initialize the output qubit as by
179
+ resetting to |0> and then acting a Hadamard gate. From that
180
+ state, the method amends the output qubit upon the basis of
181
+ the state of its input qubits, applying a rotation around
182
+ Pauli Y axis according to the angle learned for the input.
183
+
184
+ Args:
185
+ e(bool): If False, predict the opposite
186
+ r(bool): If True, start by resetting the output to 50/50
187
+
188
+ Raises:
189
+ RuntimeError: QrackNeuron C++ library raised an exception.
190
+ """
191
+ result = Qrack.qrack_lib.qneuron_predict(self.nid, e, r)
192
+ self._throw_if_error()
193
+ return result
194
+
195
+ def unpredict(self, e=True):
196
+ """Uncompute a prediction
197
+
198
+ Uncompute a 'prediction' of the anticipated output, based on
199
+ input and training.
200
+
201
+ Args:
202
+ e(bool): If False, unpredict the opposite
203
+
204
+ Raises:
205
+ RuntimeError: QrackNeuron C++ library raised an exception.
206
+ """
207
+ result = Qrack.qrack_lib.qneuron_unpredict(self.nid, e)
208
+ self._throw_if_error()
209
+ return result
210
+
211
+ def learn_cycle(self, e=True):
212
+ """Run a learning cycle
213
+
214
+ A learning cycle consists of predicting a result, saving the
215
+ classical outcome, and uncomputing the prediction.
216
+
217
+ Args:
218
+ e(bool): If False, predict the opposite
219
+
220
+ Raises:
221
+ RuntimeError: QrackNeuron C++ library raised an exception.
222
+ """
223
+ Qrack.qrack_lib.qneuron_learn_cycle(self.nid, e)
224
+ self._throw_if_error()
225
+
226
+ def learn(self, eta, e=True, r=True):
227
+ """Learn from current qubit state
228
+
229
+ "Learn" to associate current inputs with output. Based on
230
+ input qubit states and volatility 'eta,' the input state
231
+ synaptic parameter is updated to prefer the "e" ("expected")
232
+ output.
233
+
234
+ Args:
235
+ eta(double): Training volatility, 0 to 1
236
+ e(bool): If False, predict the opposite
237
+ r(bool): If True, start by resetting the output to 50/50
238
+
239
+ Raises:
240
+ RuntimeError: QrackNeuron C++ library raised an exception.
241
+ """
242
+ Qrack.qrack_lib.qneuron_learn(self.nid, eta, e, r)
243
+ self._throw_if_error()
244
+
245
+ def learn_permutation(self, eta, e=True, r=True):
246
+ """Learn from current classical state
247
+
248
+ Learn to associate current inputs with output, under the
249
+ assumption that the inputs and outputs are "classical."
250
+ Based on input qubit states and volatility 'eta,' the input
251
+ state angle is updated to prefer the "e" ("expected") output.
252
+
253
+ Args:
254
+ eta(double): Training volatility, 0 to 1
255
+ e(bool): If False, predict the opposite
256
+ r(bool): If True, start by resetting the output to 50/50
257
+
258
+ Raises:
259
+ RuntimeError: QrackNeuron C++ library raised an exception.
260
+ """
261
+ Qrack.qrack_lib.qneuron_learn_permutation(self.nid, eta, e, r)
262
+ self._throw_if_error()
@@ -0,0 +1,170 @@
1
+ # (C) Daniel Strano and the Qrack contributors 2017-2025. All rights reserved.
2
+ #
3
+ # Initial draft by Elara (OpenAI custom GPT)
4
+ # Refined and architecturally clarified by Dan Strano
5
+ #
6
+ # Use of this source code is governed by an MIT-style license that can be
7
+ # found in the LICENSE file or at https://opensource.org/licenses/MIT.
8
+
9
+ _IS_TORCH_AVAILABLE = True
10
+ try:
11
+ import torch
12
+ import torch.nn as nn
13
+ from torch.autograd import Function
14
+ except ImportError:
15
+ _IS_TORCH_AVAILABLE = False
16
+
17
+ from .qrack_neuron import QrackNeuron
18
+ from .neuron_activation_fn import NeuronActivationFn
19
+
20
+ from itertools import chain, combinations
21
+
22
+
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
+
29
+
30
+ class QrackTorchNeuron(nn.Module if _IS_TORCH_AVAILABLE else object):
31
+ """Torch wrapper for QrackNeuron
32
+
33
+ Attributes:
34
+ neuron(QrackNeuron): QrackNeuron backing this torch wrapper
35
+ """
36
+
37
+ def __init__(self, neuron: QrackNeuron):
38
+ super().__init__()
39
+ self.neuron = neuron
40
+
41
+ def forward(self, x):
42
+ neuron = self.neuron
43
+ neuron.predict(True, False)
44
+
45
+ return neuron.simulator.prob(neuron.target)
46
+
47
+
48
+ class QrackNeuronFunction(Function if _IS_TORCH_AVAILABLE else object):
49
+ """Static forward/backward/apply functions for QrackTorchNeuron"""
50
+
51
+ @staticmethod
52
+ def forward(ctx, neuron):
53
+ # Save for backward
54
+ ctx.neuron = neuron
55
+
56
+ init_prob = neuron.simulator.prob(neuron.target)
57
+ neuron.predict(True, False)
58
+ final_prob = neuron.simulator.prob(neuron.target)
59
+ ctx.delta = final_prob - init_prob
60
+
61
+ return (
62
+ torch.tensor([ctx.delta], dtype=torch.float32)
63
+ if _IS_TORCH_AVAILABLE
64
+ else ctx.delta
65
+ )
66
+
67
+ @staticmethod
68
+ def backward(ctx, grad_output):
69
+ neuron = ctx.neuron
70
+
71
+ pre_unpredict = neuron.simulator.prob(neuron.output_id)
72
+ neuron.unpredict()
73
+ post_unpredict = neuron.simulator.prob(neuron.output_id)
74
+ reverse_delta = pre_unpredict - post_unpredict
75
+
76
+ grad = reverse_delta - ctx.delta
77
+
78
+ return (
79
+ torch.tensor([grad], dtype=torch.float32) if _IS_TORCH_AVAILABLE else grad
80
+ )
81
+
82
+
83
+ class QrackNeuronTorchLayer(nn.Module if _IS_TORCH_AVAILABLE else object):
84
+ """Torch layer wrapper for QrackNeuron (with power set of neurons between inputs and outputs)"""
85
+
86
+ def __init__(
87
+ self,
88
+ simulator,
89
+ input_indices,
90
+ output_indices,
91
+ activation=int(NeuronActivationFn.Generalized_Logistic),
92
+ parameters=None,
93
+ ):
94
+ """
95
+ Initialize a QrackNeuron layer for PyTorch with a power set of neurons connecting inputs to outputs.
96
+ The inputs and outputs must take the form of discrete, binary features (loaded manually into the backing QrackSimulator)
97
+
98
+ Args:
99
+ sim (QrackSimulator): Simulator into which predictor features are loaded
100
+ input_indices (list[int]): List of input bits
101
+ output_indices (list[int]): List of output bits
102
+ 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 power set of input indices, repeated for each output index (with empty set being constant bias)
104
+ """
105
+ super(QrackNeuronTorchLayer, self).__init__()
106
+ self.simulator = simulator
107
+ self.input_indices = input_indices
108
+ self.output_indices = output_indices
109
+ self.activation = NeuronActivationFn(activation)
110
+ self.fn = (
111
+ QrackNeuronFunction.apply
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
+ )
126
+
127
+ # Set Qrack's internal parameters:
128
+ param_count = 0
129
+ for neuron_wrapper in self.neurons:
130
+ neuron = neuron_wrapper.neuron
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)
136
+ )
137
+ param_count += p_count
138
+
139
+ self.weights = nn.ParameterList()
140
+ for pid in range(param_count):
141
+ self.weights.append(
142
+ nn.Parameter(torch.tensor(parameters[pid] if parameters else 0.0))
143
+ )
144
+
145
+ def forward(self, _):
146
+ # Assume quantum outputs should overwrite the simulator state
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)
151
+
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
+
163
+ # Assume quantum inputs already loaded into simulator state
164
+ for neuron_wrapper in self.neurons:
165
+ self.fn(neuron_wrapper.neuron)
166
+
167
+ # These are classical views over quantum state; simulator still maintains full coherence
168
+ outputs = [self.simulator.prob(output_id) for output_id in self.output_indices]
169
+
170
+ return torch.tensor(outputs, dtype=torch.float32)