Qrack  9.13
General classical-emulating-quantum development framework
qneuron.hpp
Go to the documentation of this file.
1 //
3 // (C) Daniel Strano and the Qrack contributors 2017-2023. All rights reserved.
4 //
5 // This is a multithreaded, universal quantum register simulation, allowing
6 // (nonphysical) register cloning and direct measurement of probability and
7 // phase, to leverage what advantages classical emulation of qubits can have.
8 //
9 // Licensed under the GNU Lesser General Public License V3.
10 // See LICENSE.md in the project root or https://www.gnu.org/licenses/lgpl-3.0.en.html
11 // for details.
12 
13 #pragma once
14 
16 #include "qinterface.hpp"
17 
18 #include <algorithm>
19 
20 namespace Qrack {
21 
22 class QNeuron;
23 typedef std::shared_ptr<QNeuron> QNeuronPtr;
24 
25 class QNeuron {
26 protected:
32  std::vector<bitLenInt> inputIndices;
33  std::unique_ptr<real1[]> angles;
35 
36  static real1_f applyRelu(real1_f angle) { return std::max((real1_f)ZERO_R1_F, (real1_f)angle); }
37 
38  static real1_f negApplyRelu(real1_f angle) { return -std::max((real1_f)ZERO_R1_F, (real1_f)angle); }
39 
40  static real1_f applyGelu(real1_f angle) { return angle * (1 + erf((real1_s)(angle * SQRT1_2_R1))); }
41 
42  static real1_f negApplyGelu(real1_f angle) { return -angle * (1 + erf((real1_s)(angle * SQRT1_2_R1))); }
43 
45  {
46  real1_f toRet = ZERO_R1;
47  if (angle > PI_R1) {
48  angle -= PI_R1;
49  toRet = PI_R1;
50  } else if (angle <= -PI_R1) {
51  angle += PI_R1;
52  toRet = -PI_R1;
53  }
54 
55  return toRet + (pow((2 * abs(angle) / PI_R1), alpha) * (PI_R1 / 2) * ((angle < 0) ? -1 : 1));
56  }
57 
58  static real1_f applyLeakyRelu(real1_f angle, real1_f alpha) { return std::max(alpha * angle, angle); }
59 
60  static real1_f clampAngle(real1_f angle)
61  {
62  // From Tiama, (OpenAI ChatGPT instance)
63  angle = fmod(angle, 4 * PI_R1);
64  if (angle <= -2 * PI_R1) {
65  angle += 4 * PI_R1;
66  } else if (angle > 2 * PI_R1) {
67  angle -= 4 * PI_R1;
68  }
69 
70  return angle;
71  }
72 
73 public:
84  QNeuron(QInterfacePtr reg, const std::vector<bitLenInt>& inputIndcs, bitLenInt outputIndx,
86  : inputPower(pow2Ocl(inputIndcs.size()))
87  , outputIndex(outputIndx)
89  , alpha(alpha)
90  , tolerance(tol)
91  , inputIndices(inputIndcs)
92  , angles(new real1[inputPower]())
93  , qReg(reg)
94  {
95  }
96 
98  QNeuron(const QNeuron& toCopy)
99  : QNeuron(toCopy.qReg, toCopy.inputIndices, toCopy.outputIndex, toCopy.activationFn, (real1_f)toCopy.alpha,
100  (real1_f)toCopy.tolerance)
101  {
102  std::copy(toCopy.angles.get(), toCopy.angles.get() + toCopy.inputPower, angles.get());
103  }
104 
105  QNeuron& operator=(const QNeuron& toCopy)
106  {
107  qReg = toCopy.qReg;
108  inputIndices = toCopy.inputIndices;
109  std::copy(toCopy.angles.get(), toCopy.angles.get() + toCopy.inputPower, angles.get());
110  outputIndex = toCopy.outputIndex;
111  activationFn = toCopy.activationFn;
112  alpha = toCopy.alpha;
113  tolerance = toCopy.tolerance;
114 
115  return *this;
116  }
117 
119  void SetAlpha(real1_f a) { alpha = a; }
120 
122  real1_f GetAlpha() { return alpha; }
123 
126 
129 
131  void SetAngles(real1* nAngles) { std::copy(nAngles, nAngles + inputPower, angles.get()); }
132 
134  void GetAngles(real1* oAngles) { std::copy(angles.get(), angles.get() + inputPower, oAngles); }
135 
136  bitLenInt GetInputCount() { return inputIndices.size(); }
137 
139 
146  real1_f Predict(bool expected = true, bool resetInit = true)
147  {
148  if (resetInit) {
149  qReg->SetBit(outputIndex, false);
150  qReg->RY((real1_f)(PI_R1 / 2), outputIndex);
151  }
152 
153  if (inputIndices.empty()) {
154  // If there are no controls, this "neuron" is actually just a bias.
155  switch (activationFn) {
156  case ReLU:
157  qReg->RY((real1_f)(applyRelu(angles.get()[0U])), outputIndex);
158  break;
159  case GeLU:
160  qReg->RY((real1_f)(applyGelu(angles.get()[0U])), outputIndex);
161  break;
163  qReg->RY((real1_f)(applyAlpha(angles.get()[0U], alpha)), outputIndex);
164  break;
165  case Leaky_ReLU:
166  qReg->RY((real1_f)(applyLeakyRelu(angles.get()[0U], alpha)), outputIndex);
167  break;
168  case Sigmoid:
169  default:
170  qReg->RY((real1_f)(angles.get()[0U]), outputIndex);
171  }
172  } else if (activationFn == Sigmoid) {
173  qReg->UniformlyControlledRY(inputIndices, outputIndex, angles.get());
174  } else {
175  std::unique_ptr<real1[]> nAngles(new real1[inputPower]);
176  switch (activationFn) {
177  case ReLU:
178  std::transform(angles.get(), angles.get() + inputPower, nAngles.get(), applyRelu);
179  break;
180  case GeLU:
181  std::transform(angles.get(), angles.get() + inputPower, nAngles.get(), applyGelu);
182  break;
184  std::transform(angles.get(), angles.get() + inputPower, nAngles.get(),
185  [this](real1 a) { return applyAlpha(a, alpha); });
186  break;
187  case Leaky_ReLU:
188  std::transform(angles.get(), angles.get() + inputPower, nAngles.get(),
189  [this](real1 a) { return applyLeakyRelu(a, alpha); });
190  break;
191  case Sigmoid:
192  default:
193  break;
194  }
195  qReg->UniformlyControlledRY(inputIndices, outputIndex, nAngles.get());
196  }
197  real1_f prob = qReg->Prob(outputIndex);
198  if (!expected) {
199  prob = ONE_R1_F - prob;
200  }
201  return prob;
202  }
203 
205  real1_f Unpredict(bool expected = true)
206  {
207  if (inputIndices.empty()) {
208  // If there are no controls, this "neuron" is actually just a bias.
209  switch (activationFn) {
210  case ReLU:
211  qReg->RY((real1_f)(negApplyRelu(angles.get()[0U])), outputIndex);
212  break;
213  case GeLU:
214  qReg->RY((real1_f)(negApplyGelu(angles.get()[0U])), outputIndex);
215  break;
217  qReg->RY((real1_f)(-applyAlpha(angles.get()[0U], alpha)), outputIndex);
218  break;
219  case Leaky_ReLU:
220  qReg->RY((real1_f)(-applyLeakyRelu(angles.get()[0U], alpha)), outputIndex);
221  break;
222  case Sigmoid:
223  default:
224  qReg->RY((real1_f)(-angles.get()[0U]), outputIndex);
225  }
226  } else {
227  std::unique_ptr<real1[]> nAngles(new real1[inputPower]);
228  switch (activationFn) {
229  case ReLU:
230  std::transform(angles.get(), angles.get() + inputPower, nAngles.get(), negApplyRelu);
231  qReg->UniformlyControlledRY(inputIndices, outputIndex, nAngles.get());
232  break;
233  case GeLU:
234  std::transform(angles.get(), angles.get() + inputPower, nAngles.get(), negApplyGelu);
235  qReg->UniformlyControlledRY(inputIndices, outputIndex, nAngles.get());
236  break;
238  std::transform(angles.get(), angles.get() + inputPower, nAngles.get(),
239  [this](real1 a) { return -applyAlpha(a, alpha); });
240  qReg->UniformlyControlledRY(inputIndices, outputIndex, nAngles.get());
241  break;
242  case Leaky_ReLU:
243  std::transform(angles.get(), angles.get() + inputPower, nAngles.get(),
244  [this](real1 a) { return -applyLeakyRelu(a, alpha); });
245  qReg->UniformlyControlledRY(inputIndices, outputIndex, nAngles.get());
246  break;
247  case Sigmoid:
248  default:
249  std::transform(angles.get(), angles.get() + inputPower, nAngles.get(), [](real1 a) { return -a; });
250  qReg->UniformlyControlledRY(inputIndices, outputIndex, nAngles.get());
251  }
252  }
253  real1_f prob = qReg->Prob(outputIndex);
254  if (!expected) {
255  prob = ONE_R1_F - prob;
256  }
257  return prob;
258  }
259 
260  real1_f LearnCycle(bool expected = true)
261  {
262  const real1_f result = Predict(expected, false);
263  Unpredict(expected);
264  return result;
265  }
266 
275  void Learn(real1_f eta, bool expected = true, bool resetInit = true)
276  {
277  real1_f startProb = Predict(expected, resetInit);
278  Unpredict(expected);
279  if ((ONE_R1 - startProb) <= tolerance) {
280  return;
281  }
282 
283  for (bitCapIntOcl perm = 0U; perm < inputPower; ++perm) {
284  startProb = LearnInternal(expected, eta, perm, startProb);
285  if (0 > startProb) {
286  break;
287  }
288  }
289  }
290 
300  void LearnPermutation(real1_f eta, bool expected = true, bool resetInit = true)
301  {
302  real1_f startProb = Predict(expected, resetInit);
303  Unpredict(expected);
304  if ((ONE_R1 - startProb) <= tolerance) {
305  return;
306  }
307 
308  bitCapIntOcl perm = 0U;
309  for (size_t i = 0U; i < inputIndices.size(); ++i) {
310  if (qReg->M(inputIndices[i])) {
311  perm |= pow2Ocl(i);
312  }
313  }
314 
315  LearnInternal(expected, eta, perm, startProb);
316  }
317 
318 protected:
319  real1_f LearnInternal(bool expected, real1_f eta, bitCapIntOcl permOcl, real1_f startProb)
320  {
321  const real1 origAngle = angles.get()[permOcl];
322  real1& angle = angles.get()[permOcl];
323 
324  // Try positive angle increment:
325  angle += eta * PI_R1;
326  const real1_f plusProb = LearnCycle(expected);
327  if ((ONE_R1_F - plusProb) <= tolerance) {
328  angle = clampAngle(angle);
329  return -ONE_R1_F;
330  }
331 
332  // If positive angle increment is not an improvement,
333  // try negative angle increment:
334  angle = origAngle - eta * PI_R1;
335  const real1_f minusProb = LearnCycle(expected);
336  if ((ONE_R1_F - minusProb) <= tolerance) {
337  angle = clampAngle(angle);
338  return -ONE_R1_F;
339  }
340 
341  if ((startProb >= plusProb) && (startProb >= minusProb)) {
342  // If neither increment is an improvement,
343  // restore the original variational parameter.
344  angle = origAngle;
345  return startProb;
346  }
347 
348  if (plusProb > minusProb) {
349  angle = origAngle + eta * PI_R1;
350  return plusProb;
351  }
352 
353  return minusProb;
354  }
355 };
356 } // namespace Qrack
Definition: qneuron.hpp:25
void Learn(real1_f eta, bool expected=true, bool resetInit=true)
Perform one learning iteration, training all parameters.
Definition: qneuron.hpp:275
QNeuronActivationFn GetActivationFn()
Get activation function enum.
Definition: qneuron.hpp:128
bitLenInt GetInputCount()
Definition: qneuron.hpp:136
static real1_f clampAngle(real1_f angle)
Definition: qneuron.hpp:60
static real1_f negApplyGelu(real1_f angle)
Definition: qneuron.hpp:42
real1_f GetAlpha()
Get the "alpha" sharpness parameter of this QNeuron.
Definition: qneuron.hpp:122
QInterfacePtr qReg
Definition: qneuron.hpp:34
static real1_f negApplyRelu(real1_f angle)
Definition: qneuron.hpp:38
bitLenInt outputIndex
Definition: qneuron.hpp:28
bitCapIntOcl inputPower
Definition: qneuron.hpp:27
void GetAngles(real1 *oAngles)
Get the angles of this QNeuron.
Definition: qneuron.hpp:134
QNeuron(const QNeuron &toCopy)
Create a new QNeuron which is an exact duplicate of another, including its learned state.
Definition: qneuron.hpp:98
real1_f Predict(bool expected=true, bool resetInit=true)
Predict a binary classification.
Definition: qneuron.hpp:146
QNeuron & operator=(const QNeuron &toCopy)
Definition: qneuron.hpp:105
real1_f Unpredict(bool expected=true)
"Uncompute" the Predict() method
Definition: qneuron.hpp:205
static real1_f applyAlpha(real1_f angle, real1_f alpha)
Definition: qneuron.hpp:44
void SetAngles(real1 *nAngles)
Set the angles of this QNeuron.
Definition: qneuron.hpp:131
std::vector< bitLenInt > inputIndices
Definition: qneuron.hpp:32
static real1_f applyGelu(real1_f angle)
Definition: qneuron.hpp:40
void SetAlpha(real1_f a)
Set the "alpha" sharpness parameter of this QNeuron.
Definition: qneuron.hpp:119
real1_f LearnCycle(bool expected=true)
Definition: qneuron.hpp:260
static real1_f applyLeakyRelu(real1_f angle, real1_f alpha)
Definition: qneuron.hpp:58
bitCapIntOcl GetInputPower()
Definition: qneuron.hpp:138
QNeuron(QInterfacePtr reg, const std::vector< bitLenInt > &inputIndcs, bitLenInt outputIndx, QNeuronActivationFn activationFn=Sigmoid, real1_f alpha=ONE_R1_F, real1_f tol=FP_NORM_EPSILON/2)
"QNeuron" is a "Quantum neuron" or "quantum perceptron" class that can learn and predict in superposi...
Definition: qneuron.hpp:84
real1_f LearnInternal(bool expected, real1_f eta, bitCapIntOcl permOcl, real1_f startProb)
Definition: qneuron.hpp:319
real1_f tolerance
Definition: qneuron.hpp:31
real1_f alpha
Definition: qneuron.hpp:30
QNeuronActivationFn activationFn
Definition: qneuron.hpp:29
static real1_f applyRelu(real1_f angle)
Definition: qneuron.hpp:36
void SetActivationFn(QNeuronActivationFn f)
Sets activation function enum.
Definition: qneuron.hpp:125
void LearnPermutation(real1_f eta, bool expected=true, bool resetInit=true)
Perform one learning iteration, measuring the entire QInterface and training the resulting permutatio...
Definition: qneuron.hpp:300
std::unique_ptr< real1[]> angles
Definition: qneuron.hpp:33
Half-precision floating-point type.
Definition: half.hpp:2222
GLOSSARY: bitLenInt - "bit-length integer" - unsigned integer ID of qubit position in register bitCap...
Definition: complex16x2simd.hpp:25
QRACK_CONST real1 SQRT1_2_R1
Definition: qrack_types.hpp:180
std::shared_ptr< QInterface > QInterfacePtr
Definition: qinterface.hpp:29
void U(quid sid, bitLenInt q, real1_f theta, real1_f phi, real1_f lambda)
(External API) 3-parameter unitary gate
Definition: wasm_api.cpp:1143
QRACK_CONST real1 FP_NORM_EPSILON
Definition: qrack_types.hpp:258
QRACK_CONST real1 ONE_R1
Definition: qrack_types.hpp:185
QRACK_CONST real1 ZERO_R1
Definition: qrack_types.hpp:183
float real1_f
Definition: qrack_types.hpp:95
float real1_s
Definition: qrack_types.hpp:96
std::shared_ptr< QNeuron > QNeuronPtr
Definition: qneuron.hpp:22
QRACK_CONST real1 PI_R1
Definition: qrack_types.hpp:178
QNeuronActivationFn
Enumerated list of activation functions.
Definition: qneuron_activation_function.hpp:19
@ Sigmoid
Default.
Definition: qneuron_activation_function.hpp:21
@ ReLU
Rectified linear.
Definition: qneuron_activation_function.hpp:23
@ Generalized_Logistic
Version of (default) "Sigmoid" with tunable sharpness.
Definition: qneuron_activation_function.hpp:27
@ GeLU
Gaussian linear.
Definition: qneuron_activation_function.hpp:25
@ Leaky_ReLU
Leaky rectified linear.
Definition: qneuron_activation_function.hpp:29
bitCapIntOcl pow2Ocl(const bitLenInt &p)
Definition: qrack_functions.hpp:137
unsigned int erf(unsigned int arg)
Error function and postprocessing.
Definition: half.hpp:2092
HALF_CONSTEXPR half abs(half arg)
Absolute value.
Definition: half.hpp:2975
half fmod(half x, half y)
Remainder of division.
Definition: half.hpp:2983
half pow(half x, half y)
Power function.
Definition: half.hpp:3738
#define bitLenInt
Definition: qrack_types.hpp:38
#define ZERO_R1_F
Definition: qrack_types.hpp:160
#define bitCapIntOcl
Definition: qrack_types.hpp:50
#define ONE_R1_F
Definition: qrack_types.hpp:163