Qrack  9.0
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 
15 #include "qinterface.hpp"
16 
17 #include <algorithm>
18 
19 namespace Qrack {
20 
26  Sigmoid = 0,
28  ReLU = 1,
30  GeLU = 2,
34  Leaky_ReLU = 4
35 };
36 
37 class QNeuron;
38 typedef std::shared_ptr<QNeuron> QNeuronPtr;
39 
40 class QNeuron {
41 protected:
47  std::vector<bitLenInt> inputIndices;
48  std::unique_ptr<real1[]> angles;
50 
51  static real1_f applyRelu(real1_f angle) { return std::max((real1_f)ZERO_R1_F, (real1_f)angle); }
52 
53  static real1_f negApplyRelu(real1_f angle) { return -std::max((real1_f)ZERO_R1_F, (real1_f)angle); }
54 
55  static real1_f applyGelu(real1_f angle) { return angle * (1 + erf((real1_s)(angle * SQRT1_2_R1))); }
56 
57  static real1_f negApplyGelu(real1_f angle) { return -angle * (1 + erf((real1_s)(angle * SQRT1_2_R1))); }
58 
60  {
61  real1_f toRet = ZERO_R1;
62  if (angle > PI_R1) {
63  angle -= PI_R1;
64  toRet = PI_R1;
65  } else if (angle <= -PI_R1) {
66  angle += PI_R1;
67  toRet = -PI_R1;
68  }
69 
70  return toRet + (pow((2 * abs(angle) / PI_R1), alpha) * (PI_R1 / 2) * ((angle < 0) ? -1 : 1));
71  }
72 
73  static real1_f applyLeakyRelu(real1_f angle, real1_f alpha) { return std::max(alpha * angle, angle); }
74 
75  static real1_f clampAngle(real1_f angle)
76  {
77  // From Tiama, (OpenAI ChatGPT instance)
78  angle = fmod(angle, 4 * PI_R1);
79  if (angle <= -2 * PI_R1) {
80  angle += 4 * PI_R1;
81  } else if (angle > 2 * PI_R1) {
82  angle -= 4 * PI_R1;
83  }
84 
85  return angle;
86  }
87 
88 public:
99  QNeuron(QInterfacePtr reg, const std::vector<bitLenInt>& inputIndcs, bitLenInt outputIndx,
101  : inputPower(pow2Ocl(inputIndcs.size()))
102  , outputIndex(outputIndx)
104  , alpha(alpha)
105  , tolerance(tol)
106  , inputIndices(inputIndcs)
107  , angles(new real1[inputPower]())
108  , qReg(reg)
109  {
110  }
111 
113  QNeuron(const QNeuron& toCopy)
114  : QNeuron(toCopy.qReg, toCopy.inputIndices, toCopy.outputIndex, toCopy.activationFn, (real1_f)toCopy.alpha,
115  (real1_f)toCopy.tolerance)
116  {
117  std::copy(toCopy.angles.get(), toCopy.angles.get() + toCopy.inputPower, angles.get());
118  }
119 
120  QNeuron& operator=(const QNeuron& toCopy)
121  {
122  qReg = toCopy.qReg;
123  inputIndices = toCopy.inputIndices;
124  std::copy(toCopy.angles.get(), toCopy.angles.get() + toCopy.inputPower, angles.get());
125  outputIndex = toCopy.outputIndex;
126  activationFn = toCopy.activationFn;
127  alpha = toCopy.alpha;
128  tolerance = toCopy.tolerance;
129 
130  return *this;
131  }
132 
134  void SetAlpha(real1_f a) { alpha = a; }
135 
137  real1_f GetAlpha() { return alpha; }
138 
141 
144 
146  void SetAngles(real1* nAngles) { std::copy(nAngles, nAngles + inputPower, angles.get()); }
147 
149  void GetAngles(real1* oAngles) { std::copy(angles.get(), angles.get() + inputPower, oAngles); }
150 
151  bitLenInt GetInputCount() { return inputIndices.size(); }
152 
154 
161  real1_f Predict(bool expected = true, bool resetInit = true)
162  {
163  if (resetInit) {
164  qReg->SetBit(outputIndex, false);
165  qReg->RY((real1_f)(PI_R1 / 2), outputIndex);
166  }
167 
168  if (!inputIndices.size()) {
169  // If there are no controls, this "neuron" is actually just a bias.
170  switch (activationFn) {
171  case ReLU:
172  qReg->RY((real1_f)(applyRelu(angles.get()[0U])), outputIndex);
173  break;
174  case GeLU:
175  qReg->RY((real1_f)(applyGelu(angles.get()[0U])), outputIndex);
176  break;
178  qReg->RY((real1_f)(applyAlpha(angles.get()[0U], alpha)), outputIndex);
179  break;
180  case Leaky_ReLU:
181  qReg->RY((real1_f)(applyLeakyRelu(angles.get()[0U], alpha)), outputIndex);
182  break;
183  case Sigmoid:
184  default:
185  qReg->RY((real1_f)(angles.get()[0U]), outputIndex);
186  }
187  } else if (activationFn == Sigmoid) {
188  qReg->UniformlyControlledRY(inputIndices, outputIndex, angles.get());
189  } else {
190  std::unique_ptr<real1[]> nAngles(new real1[inputPower]);
191  switch (activationFn) {
192  case ReLU:
193  std::transform(angles.get(), angles.get() + inputPower, nAngles.get(), applyRelu);
194  break;
195  case GeLU:
196  std::transform(angles.get(), angles.get() + inputPower, nAngles.get(), applyGelu);
197  break;
199  std::transform(angles.get(), angles.get() + inputPower, nAngles.get(),
200  [this](real1 a) { return applyAlpha(a, alpha); });
201  break;
202  case Leaky_ReLU:
203  std::transform(angles.get(), angles.get() + inputPower, nAngles.get(),
204  [this](real1 a) { return applyLeakyRelu(a, alpha); });
205  break;
206  case Sigmoid:
207  default:
208  break;
209  }
210  qReg->UniformlyControlledRY(inputIndices, outputIndex, nAngles.get());
211  }
212  real1_f prob = qReg->Prob(outputIndex);
213  if (!expected) {
214  prob = ONE_R1_F - prob;
215  }
216  return prob;
217  }
218 
220  real1_f Unpredict(bool expected = true)
221  {
222  if (!inputIndices.size()) {
223  // If there are no controls, this "neuron" is actually just a bias.
224  switch (activationFn) {
225  case ReLU:
226  qReg->RY((real1_f)(negApplyRelu(angles.get()[0U])), outputIndex);
227  break;
228  case GeLU:
229  qReg->RY((real1_f)(negApplyGelu(angles.get()[0U])), outputIndex);
230  break;
232  qReg->RY((real1_f)(-applyAlpha(angles.get()[0U], alpha)), outputIndex);
233  break;
234  case Leaky_ReLU:
235  qReg->RY((real1_f)(-applyLeakyRelu(angles.get()[0U], alpha)), outputIndex);
236  break;
237  case Sigmoid:
238  default:
239  qReg->RY((real1_f)(-angles.get()[0U]), outputIndex);
240  }
241  } else {
242  std::unique_ptr<real1[]> nAngles(new real1[inputPower]);
243  switch (activationFn) {
244  case ReLU:
245  std::transform(angles.get(), angles.get() + inputPower, nAngles.get(), negApplyRelu);
246  qReg->UniformlyControlledRY(inputIndices, outputIndex, nAngles.get());
247  break;
248  case GeLU:
249  std::transform(angles.get(), angles.get() + inputPower, nAngles.get(), negApplyGelu);
250  qReg->UniformlyControlledRY(inputIndices, outputIndex, nAngles.get());
251  break;
253  std::transform(angles.get(), angles.get() + inputPower, nAngles.get(),
254  [this](real1 a) { return -applyAlpha(a, alpha); });
255  qReg->UniformlyControlledRY(inputIndices, outputIndex, nAngles.get());
256  break;
257  case Leaky_ReLU:
258  std::transform(angles.get(), angles.get() + inputPower, nAngles.get(),
259  [this](real1 a) { return -applyLeakyRelu(a, alpha); });
260  qReg->UniformlyControlledRY(inputIndices, outputIndex, nAngles.get());
261  break;
262  case Sigmoid:
263  default:
264  std::transform(angles.get(), angles.get() + inputPower, nAngles.get(), [](real1 a) { return -a; });
265  qReg->UniformlyControlledRY(inputIndices, outputIndex, nAngles.get());
266  }
267  }
268  real1_f prob = qReg->Prob(outputIndex);
269  if (!expected) {
270  prob = ONE_R1_F - prob;
271  }
272  return prob;
273  }
274 
275  real1_f LearnCycle(bool expected = true)
276  {
277  const real1_f result = Predict(expected, false);
278  Unpredict(expected);
279  return result;
280  }
281 
290  void Learn(real1_f eta, bool expected = true, bool resetInit = true)
291  {
292  real1_f startProb = Predict(expected, resetInit);
293  Unpredict(expected);
294  if ((ONE_R1 - startProb) <= tolerance) {
295  return;
296  }
297 
298  for (bitCapInt perm = 0U; perm < inputPower; ++perm) {
299  startProb = LearnInternal(expected, eta, perm, startProb);
300  if (0 > startProb) {
301  break;
302  }
303  }
304  }
305 
315  void LearnPermutation(real1_f eta, bool expected = true, bool resetInit = true)
316  {
317  real1_f startProb = Predict(expected, resetInit);
318  Unpredict(expected);
319  if ((ONE_R1 - startProb) <= tolerance) {
320  return;
321  }
322 
323  bitCapInt perm = 0U;
324  for (size_t i = 0U; i < inputIndices.size(); ++i) {
325  perm |= qReg->M(inputIndices[i]) ? pow2(i) : 0U;
326  }
327 
328  LearnInternal(expected, eta, perm, startProb);
329  }
330 
331 protected:
332  real1_f LearnInternal(bool expected, real1_f eta, bitCapInt perm, real1_f startProb)
333  {
334  bitCapIntOcl permOcl = (bitCapIntOcl)perm;
335  const real1 origAngle = angles.get()[permOcl];
336  real1& angle = angles.get()[permOcl];
337 
338  // Try positive angle increment:
339  angle += eta * PI_R1;
340  const real1_f plusProb = LearnCycle(expected);
341  if ((ONE_R1_F - plusProb) <= tolerance) {
342  angle = clampAngle(angle);
343  return -ONE_R1_F;
344  }
345 
346  // If positive angle increment is not an improvement,
347  // try negative angle increment:
348  angle = origAngle - eta * PI_R1;
349  const real1_f minusProb = LearnCycle(expected);
350  if ((ONE_R1_F - minusProb) <= tolerance) {
351  angle = clampAngle(angle);
352  return -ONE_R1_F;
353  }
354 
355  if ((startProb >= plusProb) && (startProb >= minusProb)) {
356  // If neither increment is an improvement,
357  // restore the original variational parameter.
358  angle = origAngle;
359  return startProb;
360  }
361 
362  if (plusProb > minusProb) {
363  angle = origAngle + eta * PI_R1;
364  return plusProb;
365  }
366 
367  return minusProb;
368  }
369 };
370 } // namespace Qrack
Definition: qneuron.hpp:40
real1_f LearnInternal(bool expected, real1_f eta, bitCapInt perm, real1_f startProb)
Definition: qneuron.hpp:332
void Learn(real1_f eta, bool expected=true, bool resetInit=true)
Perform one learning iteration, training all parameters.
Definition: qneuron.hpp:290
QNeuronActivationFn GetActivationFn()
Get activation function enum.
Definition: qneuron.hpp:143
bitLenInt GetInputCount()
Definition: qneuron.hpp:151
static real1_f clampAngle(real1_f angle)
Definition: qneuron.hpp:75
static real1_f negApplyGelu(real1_f angle)
Definition: qneuron.hpp:57
real1_f GetAlpha()
Get the "alpha" sharpness parameter of this QNeuron.
Definition: qneuron.hpp:137
QInterfacePtr qReg
Definition: qneuron.hpp:49
static real1_f negApplyRelu(real1_f angle)
Definition: qneuron.hpp:53
bitLenInt outputIndex
Definition: qneuron.hpp:43
bitCapIntOcl inputPower
Definition: qneuron.hpp:42
void GetAngles(real1 *oAngles)
Get the angles of this QNeuron.
Definition: qneuron.hpp:149
QNeuron(const QNeuron &toCopy)
Create a new QNeuron which is an exact duplicate of another, including its learned state.
Definition: qneuron.hpp:113
real1_f Predict(bool expected=true, bool resetInit=true)
Predict a binary classification.
Definition: qneuron.hpp:161
QNeuron & operator=(const QNeuron &toCopy)
Definition: qneuron.hpp:120
real1_f Unpredict(bool expected=true)
"Uncompute" the Predict() method
Definition: qneuron.hpp:220
static real1_f applyAlpha(real1_f angle, real1_f alpha)
Definition: qneuron.hpp:59
bitCapInt GetInputPower()
Definition: qneuron.hpp:153
void SetAngles(real1 *nAngles)
Set the angles of this QNeuron.
Definition: qneuron.hpp:146
std::vector< bitLenInt > inputIndices
Definition: qneuron.hpp:47
static real1_f applyGelu(real1_f angle)
Definition: qneuron.hpp:55
void SetAlpha(real1_f a)
Set the "alpha" sharpness parameter of this QNeuron.
Definition: qneuron.hpp:134
real1_f LearnCycle(bool expected=true)
Definition: qneuron.hpp:275
static real1_f applyLeakyRelu(real1_f angle, real1_f alpha)
Definition: qneuron.hpp:73
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)
"QNeuron" is a "Quantum neuron" or "quantum perceptron" class that can learn and predict in superposi...
Definition: qneuron.hpp:99
real1_f tolerance
Definition: qneuron.hpp:46
real1_f alpha
Definition: qneuron.hpp:45
QNeuronActivationFn activationFn
Definition: qneuron.hpp:44
static real1_f applyRelu(real1_f angle)
Definition: qneuron.hpp:51
void SetActivationFn(QNeuronActivationFn f)
Sets activation function enum.
Definition: qneuron.hpp:140
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:315
std::unique_ptr< real1[]> angles
Definition: qneuron.hpp:48
Half-precision floating-point type.
Definition: half.hpp:2222
Definition: complex16x2simd.hpp:25
std::shared_ptr< QInterface > QInterfacePtr
Definition: qinterface.hpp:28
constexpr real1_f ZERO_R1_F
Definition: qrack_types.hpp:152
const real1 ONE_R1
Definition: qrack_types.hpp:153
constexpr real1_f ONE_R1_F
Definition: qrack_types.hpp:154
QRACK_CONST real1 FP_NORM_EPSILON
Definition: qrack_types.hpp:243
bitCapInt pow2(const bitLenInt &p)
Definition: qrack_functions.hpp:22
const real1 PI_R1
Definition: qrack_types.hpp:158
const real1 ZERO_R1
Definition: qrack_types.hpp:151
float real1_f
Definition: qrack_types.hpp:64
float real1_s
Definition: qrack_types.hpp:65
std::shared_ptr< QNeuron > QNeuronPtr
Definition: qneuron.hpp:37
QNeuronActivationFn
Enumerated list of activation functions.
Definition: qneuron.hpp:24
@ Sigmoid
Default.
Definition: qneuron.hpp:26
@ ReLU
Rectified linear.
Definition: qneuron.hpp:28
@ Generalized_Logistic
Version of (default) "Sigmoid" with tunable sharpness.
Definition: qneuron.hpp:32
@ GeLU
Gaussian linear.
Definition: qneuron.hpp:30
@ Leaky_ReLU
Leaky rectified linear.
Definition: qneuron.hpp:34
bitCapIntOcl pow2Ocl(const bitLenInt &p)
Definition: qrack_functions.hpp:23
const real1 SQRT1_2_R1
Definition: qrack_types.hpp:160
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
MICROSOFT_QUANTUM_DECL void U(_In_ uintq sid, _In_ uintq q, _In_ double theta, _In_ double phi, _In_ double lambda)
(External API) 3-parameter unitary gate
Definition: pinvoke_api.cpp:1362
#define bitLenInt
Definition: qrack_types.hpp:44
#define bitCapInt
Definition: qrack_types.hpp:105
#define bitCapIntOcl
Definition: qrack_types.hpp:91