pyqrack 1.29.0__py3-none-win_amd64.whl → 1.72.5__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.
@@ -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)