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 
121 
128  real1_f Predict(const real1* angles, const bool& expected = true, const bool& resetInit = true,
129  const QNeuronActivationFn& activationFn = Sigmoid, const real1_f& alpha = ONE_R1_F)
130  {
131  if (resetInit) {
132  qReg->SetBit(outputIndex, false);
133  qReg->RY((real1_f)(PI_R1 / 2), outputIndex);
134  }
135 
136  if (inputIndices.empty()) {
137  // If there are no controls, this "neuron" is actually just a bias.
138  switch (activationFn) {
139  case ReLU:
140  qReg->RY((real1_f)(applyRelu(angles[0U])), outputIndex);
141  break;
142  case GeLU:
143  qReg->RY((real1_f)(applyGelu(angles[0U])), outputIndex);
144  break;
146  qReg->RY((real1_f)(applyAlpha(angles[0U], alpha)), outputIndex);
147  break;
148  case Leaky_ReLU:
149  qReg->RY((real1_f)(applyLeakyRelu(angles[0U], alpha)), outputIndex);
150  break;
151  case Sigmoid:
152  default:
153  qReg->RY((real1_f)(angles[0U]), outputIndex);
154  }
155  } else if (activationFn == Sigmoid) {
156 #if (FPPOW < 5) || (FPPOW > 6)
157  const bitCapIntOcl p = GetInputPower();
158  std::unique_ptr<real1[]> _angles(new real1[p]);
159  std::copy(angles, angles + p, _angles.get());
160  qReg->UniformlyControlledRY(inputIndices, outputIndex, _angles.get());
161 #else
162  qReg->UniformlyControlledRY(inputIndices, outputIndex, angles);
163 #endif
164  } else {
165  const bitCapIntOcl inputPower = GetInputPower();
166  std::unique_ptr<real1[]> nAngles(new real1[inputPower]);
167  switch (activationFn) {
168  case ReLU:
169  std::transform(angles, angles + inputPower, nAngles.get(), applyRelu);
170  break;
171  case GeLU:
172  std::transform(angles, angles + inputPower, nAngles.get(), applyGelu);
173  break;
175  std::transform(
176  angles, angles + inputPower, nAngles.get(), [&alpha](real1 a) { return applyAlpha(a, alpha); });
177  break;
178  case Leaky_ReLU:
179  std::transform(
180  angles, angles + inputPower, nAngles.get(), [&alpha](real1 a) { return applyLeakyRelu(a, alpha); });
181  break;
182  case Sigmoid:
183  default:
184  break;
185  }
186  qReg->UniformlyControlledRY(inputIndices, outputIndex, nAngles.get());
187  }
188  real1_f prob = qReg->Prob(outputIndex);
189  if (!expected) {
190  prob = ONE_R1_F - prob;
191  }
192  return prob;
193  }
194 
196  real1_f Unpredict(const real1* angles, const bool& expected = true,
197  const QNeuronActivationFn& activationFn = Sigmoid, const real1_f& alpha = ONE_R1_F)
198  {
199  if (inputIndices.empty()) {
200  // If there are no controls, this "neuron" is actually just a bias.
201  switch (activationFn) {
202  case ReLU:
203  qReg->RY((real1_f)(negApplyRelu(angles[0U])), outputIndex);
204  break;
205  case GeLU:
206  qReg->RY((real1_f)(negApplyGelu(angles[0U])), outputIndex);
207  break;
209  qReg->RY((real1_f)(-applyAlpha(angles[0U], alpha)), outputIndex);
210  break;
211  case Leaky_ReLU:
212  qReg->RY((real1_f)(-applyLeakyRelu(angles[0U], alpha)), outputIndex);
213  break;
214  case Sigmoid:
215  default:
216  qReg->RY((real1_f)(-angles[0U]), outputIndex);
217  }
218  } else {
219  const bitCapIntOcl inputPower = GetInputPower();
220  std::unique_ptr<real1[]> nAngles(new real1[inputPower]);
221  switch (activationFn) {
222  case ReLU:
223  std::transform(angles, angles + inputPower, nAngles.get(), negApplyRelu);
224  qReg->UniformlyControlledRY(inputIndices, outputIndex, nAngles.get());
225  break;
226  case GeLU:
227  std::transform(angles, angles + inputPower, nAngles.get(), negApplyGelu);
228  qReg->UniformlyControlledRY(inputIndices, outputIndex, nAngles.get());
229  break;
231  std::transform(
232  angles, angles + inputPower, nAngles.get(), [&alpha](real1 a) { return -applyAlpha(a, alpha); });
233  qReg->UniformlyControlledRY(inputIndices, outputIndex, nAngles.get());
234  break;
235  case Leaky_ReLU:
236  std::transform(angles, angles + inputPower, nAngles.get(),
237  [&alpha](real1 a) { return -applyLeakyRelu(a, alpha); });
238  qReg->UniformlyControlledRY(inputIndices, outputIndex, nAngles.get());
239  break;
240  case Sigmoid:
241  default:
242  std::transform(angles, angles + inputPower, nAngles.get(), [](real1 a) { return -a; });
243  qReg->UniformlyControlledRY(inputIndices, outputIndex, nAngles.get());
244  }
245  }
246  real1_f prob = qReg->Prob(outputIndex);
247  if (!expected) {
248  prob = ONE_R1_F - prob;
249  }
250  return prob;
251  }
252 
253  real1_f LearnCycle(real1* angles, const bool& expected = true, const QNeuronActivationFn& activationFn = Sigmoid,
254  const real1_f& alpha = ONE_R1_F)
255  {
256  const real1_f result = Predict(angles, expected, false, activationFn, alpha);
257  Unpredict(angles, expected, activationFn, alpha);
258  return result;
259  }
260 
269  void Learn(real1* angles, const real1_f& eta, const bool& expected = true, const bool& resetInit = true,
270  const QNeuronActivationFn& activationFn = Sigmoid, const real1_f& alpha = ONE_R1_F)
271  {
272  real1_f startProb = Predict(angles, expected, resetInit, activationFn, alpha);
273  Unpredict(angles, expected, activationFn, alpha);
274  if ((ONE_R1 - startProb) <= FP_NORM_EPSILON) {
275  return;
276  }
277  const bitCapIntOcl inputPower = GetInputPower();
278  for (bitCapIntOcl perm = 0U; perm < inputPower; ++perm) {
279  startProb = LearnInternal(angles, expected, eta, perm, startProb, activationFn, alpha);
280  if (0 > startProb) {
281  break;
282  }
283  }
284  }
285 
295  void LearnPermutation(real1* angles, const real1_f& eta, const bool& expected = true, const bool& resetInit = true,
296  const QNeuronActivationFn& activationFn = Sigmoid, const real1_f& alpha = ONE_R1_F)
297  {
298  const real1_f startProb = Predict(angles, expected, resetInit, activationFn, alpha);
299  Unpredict(angles, expected, activationFn, alpha);
300  if ((ONE_R1 - startProb) <= FP_NORM_EPSILON) {
301  return;
302  }
303  bitCapIntOcl perm = 0U;
304  for (size_t i = 0U; i < inputIndices.size(); ++i) {
305  if (qReg->M(inputIndices[i])) {
306  perm |= pow2Ocl(i);
307  }
308  }
309 
310  LearnInternal(angles, expected, eta, perm, startProb);
311  }
312 
313 protected:
314  real1_f LearnInternal(real1* angles, const bool& expected, const real1_f& eta, const bitCapIntOcl& permOcl,
315  const real1_f& startProb, const QNeuronActivationFn& activationFn = Sigmoid, const real1_f& alpha = ONE_R1_F)
316  {
317  const real1 origAngle = angles[permOcl];
318  real1& angle = angles[permOcl];
319 
320  const real1 delta = (real1)(eta * PI_R1);
321 
322  // Try positive angle increment:
323  angle += delta;
324  const real1_f plusProb = LearnCycle(angles, expected, activationFn, alpha);
325 
326  // If positive angle increment is not an improvement,
327  // try negative angle increment:
328  angle = origAngle - delta;
329  const real1_f minusProb = LearnCycle(angles, expected, activationFn, alpha);
330 
331  if ((startProb >= plusProb) && (startProb >= minusProb)) {
332  // If neither increment is an improvement,
333  // restore the original variational parameter.
334  angle = origAngle;
335  return startProb;
336  }
337 
338  if (plusProb > minusProb) {
339  angle = origAngle + delta;
340  return plusProb;
341  }
342 
343  return minusProb;
344  }
345 };
346 } // namespace Qrack
Definition: qneuron.hpp:25
bitLenInt GetInputCount()
Definition: qneuron.hpp:116
real1_f Predict(const real1 *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:128
static real1_f clampAngle(real1_f angle)
Definition: qneuron.hpp:55
QNeuron & operator=(QNeuron &toCopy)
Definition: qneuron.hpp:94
bitLenInt GetOutputIndex()
Definition: qneuron.hpp:120
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
real1_f Unpredict(const real1 *angles, const bool &expected=true, const QNeuronActivationFn &activationFn=Sigmoid, const real1_f &alpha=ONE_R1_F)
"Uncompute" the Predict() method
Definition: qneuron.hpp:196
void Learn(real1 *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:269
real1_f LearnInternal(real1 *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:314
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
real1_f LearnCycle(real1 *angles, const bool &expected=true, const QNeuronActivationFn &activationFn=Sigmoid, const real1_f &alpha=ONE_R1_F)
Definition: qneuron.hpp:253
static real1_f applyRelu(const real1_f &angle)
Definition: qneuron.hpp:31
std::vector< bitLenInt > inputIndices
Definition: qneuron.hpp:28
static real1_f negApplyGelu(const real1_f &angle)
Definition: qneuron.hpp:37
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
void LearnPermutation(real1 *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:295
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
Half-precision floating-point type.
Definition: half.hpp:2206
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:182
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
half_float::half real1
Definition: qrack_types.hpp:106
QRACK_CONST real1 FP_NORM_EPSILON
Definition: qrack_types.hpp:263
QRACK_CONST real1 ONE_R1
Definition: qrack_types.hpp:188
QRACK_CONST real1 ZERO_R1
Definition: qrack_types.hpp:186
float real1_f
Definition: qrack_types.hpp:107
float real1_s
Definition: qrack_types.hpp:108
std::shared_ptr< QNeuron > QNeuronPtr
Definition: qneuron.hpp:22
QRACK_CONST real1 PI_R1
Definition: qrack_types.hpp:180
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:157
unsigned int erf(unsigned int arg)
Error function and postprocessing.
Definition: half.hpp:2076
HALF_CONSTEXPR half abs(half arg)
Absolute value.
Definition: half.hpp:2958
half fmod(half x, half y)
Remainder of division.
Definition: half.hpp:2966
half pow(half x, half y)
Power function.
Definition: half.hpp:3721
#define QRACK_CONST
Definition: qrack_types.hpp:176
#define bitLenInt
Definition: qrack_types.hpp:41
#define ZERO_R1_F
Definition: qrack_types.hpp:162
#define bitCapIntOcl
Definition: qrack_types.hpp:53
#define ONE_R1_F
Definition: qrack_types.hpp:165