Qrack  10.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 
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:
28  std::vector<bitLenInt> inputIndices;
30 
31  static real1_f applyRelu(const real1_f& angle) { return std::max((real1_f)ZERO_R1_F, (real1_f)angle); }
32 
33  static real1_f negApplyRelu(const real1_f& angle) { return -std::max((real1_f)ZERO_R1_F, (real1_f)angle); }
34 
35  static real1_f applyGelu(const real1_f& angle) { return angle * (1 + erf((real1_s)(angle * SQRT1_2_R1))); }
36 
37  static real1_f negApplyGelu(const real1_f& angle) { return -angle * (1 + erf((real1_s)(angle * SQRT1_2_R1))); }
38 
39  static real1_f applyAlpha(real1_f angle, const real1_f& alpha)
40  {
41  real1_f toRet = ZERO_R1;
42  if (angle > PI_R1) {
43  angle -= PI_R1;
44  toRet = PI_R1;
45  } else if (angle <= -PI_R1) {
46  angle += PI_R1;
47  toRet = -PI_R1;
48  }
49 
50  return toRet + (pow((2 * abs(angle) / PI_R1), alpha) * (PI_R1 / 2) * ((angle < 0) ? -1 : 1));
51  }
52 
53  static real1_f applyLeakyRelu(const real1_f& angle, const real1_f& alpha) { return std::max(alpha * angle, angle); }
54 
55  static real1_f clampAngle(real1_f angle)
56  {
57  // From Tiama, (OpenAI ChatGPT instance)
58  QRACK_CONST real1_f PI_2 = 2 * PI_R1;
59  QRACK_CONST real1_f PI_4 = 4 * PI_R1;
60  angle = fmod(angle, PI_4);
61  if (angle <= -PI_2) {
62  angle += PI_4;
63  } else if (angle > PI_2) {
64  angle -= PI_4;
65  }
66 
67  return angle;
68  }
69 
70 public:
81  QNeuron(QInterfacePtr reg, const std::vector<bitLenInt>& inputIndcs, const bitLenInt& outputIndx)
82  : outputIndex(outputIndx)
83  , inputIndices(inputIndcs)
84  , qReg(reg)
85  {
86  }
87 
89  QNeuron(const QNeuron& toCopy)
90  : QNeuron(toCopy.qReg, toCopy.inputIndices, toCopy.outputIndex)
91  {
92  }
93 
95  {
96  qReg = toCopy.qReg;
97  inputIndices = toCopy.inputIndices;
98  outputIndex = toCopy.outputIndex;
99 
100  return *this;
101  }
102 
104  void SetSimulator(QInterfacePtr sim) { qReg = sim; }
105 
107  void SetIndices(const std::vector<bitLenInt>& inputIndcs, const bitLenInt& outputIndx)
108  {
109  inputIndices = inputIndcs;
110  outputIndex = outputIndx;
111  }
112 
115 
116  bitLenInt GetInputCount() { return inputIndices.size(); }
117 
119 
126  real1_f Predict(const real1_s* angles, const bool& expected = true, const bool& resetInit = true,
127  const QNeuronActivationFn& activationFn = Sigmoid, const real1_f& alpha = ONE_R1_F)
128  {
129  if (resetInit) {
130  qReg->SetBit(outputIndex, false);
131  qReg->RY((real1_f)(PI_R1 / 2), outputIndex);
132  }
133 
134  if (inputIndices.empty()) {
135  // If there are no controls, this "neuron" is actually just a bias.
136  switch (activationFn) {
137  case ReLU:
138  qReg->RY((real1_f)(applyRelu(angles[0U])), outputIndex);
139  break;
140  case GeLU:
141  qReg->RY((real1_f)(applyGelu(angles[0U])), outputIndex);
142  break;
144  qReg->RY((real1_f)(applyAlpha(angles[0U], alpha)), outputIndex);
145  break;
146  case Leaky_ReLU:
147  qReg->RY((real1_f)(applyLeakyRelu(angles[0U], alpha)), outputIndex);
148  break;
149  case Sigmoid:
150  default:
151  qReg->RY((real1_f)(angles[0U]), outputIndex);
152  }
153  } else if (activationFn == Sigmoid) {
154 #if (FPPOW < 5) || (FPPOW > 6)
155  const bitCapIntOcl p = GetInputPower();
156  std::unique_ptr<real1[]> _angles(new real1[p]);
157  std::copy(angles, angles + p, _angles.get());
158  qReg->UniformlyControlledRY(inputIndices, outputIndex, _angles.get());
159 #else
160  qReg->UniformlyControlledRY(inputIndices, outputIndex, angles);
161 #endif
162  } else {
163  const bitCapIntOcl inputPower = GetInputPower();
164  std::unique_ptr<real1[]> nAngles(new real1[inputPower]);
165  switch (activationFn) {
166  case ReLU:
167  std::transform(angles, angles + inputPower, nAngles.get(), applyRelu);
168  break;
169  case GeLU:
170  std::transform(angles, angles + inputPower, nAngles.get(), applyGelu);
171  break;
173  std::transform(
174  angles, angles + inputPower, nAngles.get(), [&alpha](real1_s a) { return applyAlpha(a, alpha); });
175  break;
176  case Leaky_ReLU:
177  std::transform(
178  angles, angles + inputPower, nAngles.get(), [&alpha](real1_s a) { return applyLeakyRelu(a, alpha); });
179  break;
180  case Sigmoid:
181  default:
182  break;
183  }
184  qReg->UniformlyControlledRY(inputIndices, outputIndex, nAngles.get());
185  }
186  real1_f prob = qReg->Prob(outputIndex);
187  if (!expected) {
188  prob = ONE_R1_F - prob;
189  }
190  return prob;
191  }
192 
194  real1_f Unpredict(const real1_s* angles, const bool& expected = true,
195  const QNeuronActivationFn& activationFn = Sigmoid, const real1_f& alpha = ONE_R1_F)
196  {
197  if (inputIndices.empty()) {
198  // If there are no controls, this "neuron" is actually just a bias.
199  switch (activationFn) {
200  case ReLU:
201  qReg->RY((real1_f)(negApplyRelu(angles[0U])), outputIndex);
202  break;
203  case GeLU:
204  qReg->RY((real1_f)(negApplyGelu(angles[0U])), outputIndex);
205  break;
207  qReg->RY((real1_f)(-applyAlpha(angles[0U], alpha)), outputIndex);
208  break;
209  case Leaky_ReLU:
210  qReg->RY((real1_f)(-applyLeakyRelu(angles[0U], alpha)), outputIndex);
211  break;
212  case Sigmoid:
213  default:
214  qReg->RY((real1_f)(-angles[0U]), outputIndex);
215  }
216  } else {
217  const bitCapIntOcl inputPower = GetInputPower();
218  std::unique_ptr<real1[]> nAngles(new real1[inputPower]);
219  switch (activationFn) {
220  case ReLU:
221  std::transform(angles, angles + inputPower, nAngles.get(), negApplyRelu);
222  qReg->UniformlyControlledRY(inputIndices, outputIndex, nAngles.get());
223  break;
224  case GeLU:
225  std::transform(angles, angles + inputPower, nAngles.get(), negApplyGelu);
226  qReg->UniformlyControlledRY(inputIndices, outputIndex, nAngles.get());
227  break;
229  std::transform(
230  angles, angles + inputPower, nAngles.get(), [&alpha](real1_s a) { return -applyAlpha(a, alpha); });
231  qReg->UniformlyControlledRY(inputIndices, outputIndex, nAngles.get());
232  break;
233  case Leaky_ReLU:
234  std::transform(angles, angles + inputPower, nAngles.get(),
235  [&alpha](real1_s a) { return -applyLeakyRelu(a, alpha); });
236  qReg->UniformlyControlledRY(inputIndices, outputIndex, nAngles.get());
237  break;
238  case Sigmoid:
239  default:
240  std::transform(angles, angles + inputPower, nAngles.get(), [](real1_s a) { return -a; });
241  qReg->UniformlyControlledRY(inputIndices, outputIndex, nAngles.get());
242  }
243  }
244  real1_f prob = qReg->Prob(outputIndex);
245  if (!expected) {
246  prob = ONE_R1_F - prob;
247  }
248  return prob;
249  }
250 
251  real1_f LearnCycle(real1_s* angles, const bool& expected = true, const QNeuronActivationFn& activationFn = Sigmoid,
252  const real1_f& alpha = ONE_R1_F)
253  {
254  const real1_f result = Predict(angles, expected, false, activationFn, alpha);
255  Unpredict(angles, expected, activationFn, alpha);
256  return result;
257  }
258 
267  void Learn(real1_s* angles, const real1_f& eta, const bool& expected = true, const bool& resetInit = true,
268  const QNeuronActivationFn& activationFn = Sigmoid, const real1_f& alpha = ONE_R1_F)
269  {
270  real1_f startProb = Predict(angles, expected, resetInit, activationFn, alpha);
271  Unpredict(angles, expected, activationFn, alpha);
272  if ((ONE_R1 - startProb) <= FP_NORM_EPSILON) {
273  return;
274  }
275  const bitCapIntOcl inputPower = GetInputPower();
276  for (bitCapIntOcl perm = 0U; perm < inputPower; ++perm) {
277  startProb = LearnInternal(angles, expected, eta, perm, startProb, activationFn, alpha);
278  if (0 > startProb) {
279  break;
280  }
281  }
282  }
283 
293  void LearnPermutation(real1_s* angles, const real1_f& eta, const bool& expected = true,
294  const bool& resetInit = true, const QNeuronActivationFn& activationFn = Sigmoid,
295  const real1_f& alpha = ONE_R1_F)
296  {
297  const real1_f startProb = Predict(angles, expected, resetInit, activationFn, alpha);
298  Unpredict(angles, expected, activationFn, alpha);
299  if ((ONE_R1 - startProb) <= FP_NORM_EPSILON) {
300  return;
301  }
302  bitCapIntOcl perm = 0U;
303  for (size_t i = 0U; i < inputIndices.size(); ++i) {
304  if (qReg->M(inputIndices[i])) {
305  perm |= pow2Ocl(i);
306  }
307  }
308 
309  LearnInternal(angles, expected, eta, perm, startProb);
310  }
311 
312 protected:
313  real1_f LearnInternal(real1_s* angles, const bool& expected, const real1_f& eta, const bitCapIntOcl& permOcl,
314  const real1_f& startProb, const QNeuronActivationFn& activationFn = Sigmoid, const real1_f& alpha = ONE_R1_F)
315  {
316  const real1_s origAngle = angles[permOcl];
317  real1_s& angle = angles[permOcl];
318 
319  const real1_s delta = (real1_s)(eta * PI_R1);
320 
321  // Try positive angle increment:
322  angle += delta;
323  const real1_f plusProb = LearnCycle(angles, expected, activationFn, alpha);
324 
325  // If positive angle increment is not an improvement,
326  // try negative angle increment:
327  angle = origAngle - delta;
328  const real1_f minusProb = LearnCycle(angles, expected, activationFn, alpha);
329 
330  if ((startProb >= plusProb) && (startProb >= minusProb)) {
331  // If neither increment is an improvement,
332  // restore the original variational parameter.
333  angle = origAngle;
334  return startProb;
335  }
336 
337  if (plusProb > minusProb) {
338  angle = origAngle + delta;
339  return plusProb;
340  }
341 
342  return minusProb;
343  }
344 };
345 } // namespace Qrack
Definition: qneuron.hpp:25
real1_f LearnInternal(real1_s *angles, const bool &expected, const real1_f &eta, const bitCapIntOcl &permOcl, const real1_f &startProb, const QNeuronActivationFn &activationFn=Sigmoid, const real1_f &alpha=ONE_R1_F)
Definition: qneuron.hpp:313
bitLenInt GetInputCount()
Definition: qneuron.hpp:116
static real1_f clampAngle(real1_f angle)
Definition: qneuron.hpp:55
QNeuron & operator=(QNeuron &toCopy)
Definition: qneuron.hpp:94
static real1_f applyAlpha(real1_f angle, const real1_f &alpha)
Definition: qneuron.hpp:39
static real1_f negApplyRelu(const real1_f &angle)
Definition: qneuron.hpp:33
QInterfacePtr qReg
Definition: qneuron.hpp:29
bitLenInt outputIndex
Definition: qneuron.hpp:27
QNeuron(const QNeuron &toCopy)
Create a new QNeuron which is an exact duplicate of another, including its learned state.
Definition: qneuron.hpp:89
static real1_f applyLeakyRelu(const real1_f &angle, const real1_f &alpha)
Definition: qneuron.hpp:53
void LearnPermutation(real1_s *angles, const real1_f &eta, const bool &expected=true, const bool &resetInit=true, const QNeuronActivationFn &activationFn=Sigmoid, const real1_f &alpha=ONE_R1_F)
Perform one learning iteration, measuring the entire QInterface and training the resulting permutatio...
Definition: qneuron.hpp:293
static real1_f applyRelu(const real1_f &angle)
Definition: qneuron.hpp:31
real1_f Unpredict(const real1_s *angles, const bool &expected=true, const QNeuronActivationFn &activationFn=Sigmoid, const real1_f &alpha=ONE_R1_F)
"Uncompute" the Predict() method
Definition: qneuron.hpp:194
void Learn(real1_s *angles, const real1_f &eta, const bool &expected=true, const bool &resetInit=true, const QNeuronActivationFn &activationFn=Sigmoid, const real1_f &alpha=ONE_R1_F)
Perform one learning iteration, training all parameters.
Definition: qneuron.hpp:267
std::vector< bitLenInt > inputIndices
Definition: qneuron.hpp:28
static real1_f negApplyGelu(const real1_f &angle)
Definition: qneuron.hpp:37
real1_f Predict(const real1_s *angles, const bool &expected=true, const bool &resetInit=true, const QNeuronActivationFn &activationFn=Sigmoid, const real1_f &alpha=ONE_R1_F)
Predict a binary classification.
Definition: qneuron.hpp:126
QInterfacePtr GetSimulator()
Retrieve the simulator.
Definition: qneuron.hpp:114
bitCapIntOcl GetInputPower()
Definition: qneuron.hpp:118
void SetSimulator(QInterfacePtr sim)
Replace the simulator.
Definition: qneuron.hpp:104
static real1_f applyGelu(const real1_f &angle)
Definition: qneuron.hpp:35
QNeuron(QInterfacePtr reg, const std::vector< bitLenInt > &inputIndcs, const bitLenInt &outputIndx)
"QNeuron" is a "Quantum neuron" or "quantum perceptron" class that can learn and predict in superposi...
Definition: qneuron.hpp:81
void SetIndices(const std::vector< bitLenInt > &inputIndcs, const bitLenInt &outputIndx)
Replace the input and output indices.
Definition: qneuron.hpp:107
real1_f LearnCycle(real1_s *angles, const bool &expected=true, const QNeuronActivationFn &activationFn=Sigmoid, const real1_f &alpha=ONE_R1_F)
Definition: qneuron.hpp:251
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:188
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:1199
QRACK_CONST real1 FP_NORM_EPSILON
Definition: qrack_types.hpp:268
QRACK_CONST real1 ONE_R1
Definition: qrack_types.hpp:193
QRACK_CONST real1 ZERO_R1
Definition: qrack_types.hpp:191
float real1_f
Definition: qrack_types.hpp:103
float real1_s
Definition: qrack_types.hpp:104
std::shared_ptr< QNeuron > QNeuronPtr
Definition: qneuron.hpp:22
QRACK_CONST real1 PI_R1
Definition: qrack_types.hpp:186
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:144
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 QRACK_CONST
Definition: qrack_types.hpp:182
#define bitLenInt
Definition: qrack_types.hpp:42
#define ZERO_R1_F
Definition: qrack_types.hpp:168
#define bitCapIntOcl
Definition: qrack_types.hpp:54
#define ONE_R1_F
Definition: qrack_types.hpp:171