pyqrack-complex128 1.63.0__py3-none-macosx_14_0_arm64.whl → 1.78.3__py3-none-macosx_14_0_arm64.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.
pyqrack/__init__.py CHANGED
@@ -10,9 +10,10 @@ from .qrack_ace_backend import QrackAceBackend
10
10
  from .qrack_circuit import QrackCircuit
11
11
  from .qrack_neuron import QrackNeuron
12
12
  from .qrack_neuron_torch_layer import (
13
- QrackTorchNeuron,
14
- QrackNeuronFunction,
13
+ QrackNeuronTorch,
14
+ QrackNeuronTorchFunction,
15
15
  QrackNeuronTorchLayer,
16
+ QrackNeuronTorchLayerFunction,
16
17
  )
17
18
  from .qrack_simulator import QrackSimulator
18
19
  from .qrack_stabilizer import QrackStabilizer
@@ -139,6 +139,12 @@ class LHVQubit:
139
139
  new_y = 2 * (rho_prime[0][1].imag - rho_prime[1][0].imag)
140
140
  new_z = 2 * rho_prime[0][0].real - 1 # since Tr(ρ') = 1
141
141
 
142
+ p = math.sqrt(new_x**2 + new_y**2 + new_z**2)
143
+
144
+ new_x /= p
145
+ new_y /= p
146
+ new_z /= p
147
+
142
148
  self.bloch = [new_x, new_y, new_z]
143
149
 
144
150
  def prob(self, basis=Pauli.PauliZ):
@@ -202,7 +208,6 @@ class QrackAceBackend:
202
208
  sim(QrackSimulator): Array of simulators corresponding to "patches" between boundary rows.
203
209
  long_range_columns(int): How many ideal rows between QEC boundary rows?
204
210
  is_transpose(bool): Rows are long if False, columns are long if True
205
- correction_bias(float): Bias magnitude and direction during pseudo-QEC
206
211
  """
207
212
 
208
213
  def __init__(
@@ -211,7 +216,6 @@ class QrackAceBackend:
211
216
  long_range_columns=4,
212
217
  long_range_rows=4,
213
218
  is_transpose=False,
214
- correction_bias=0,
215
219
  isTensorNetwork=False,
216
220
  isSchmidtDecomposeMulti=False,
217
221
  isSchmidtDecompose=True,
@@ -240,7 +244,6 @@ class QrackAceBackend:
240
244
  self.long_range_columns = long_range_columns
241
245
  self.long_range_rows = long_range_rows
242
246
  self.is_transpose = is_transpose
243
- self.correction_bias = correction_bias
244
247
 
245
248
  fppow = 5
246
249
  if "QRACK_FPPOW" in os.environ:
@@ -429,7 +432,8 @@ class QrackAceBackend:
429
432
  def _unpack(self, lq):
430
433
  return self._qubits[lq]
431
434
 
432
- def _get_qb_lhv_indices(self, hq):
435
+ @staticmethod
436
+ def _get_qb_lhv_indices(hq):
433
437
  qb = []
434
438
  if len(hq) < 2:
435
439
  qb = [0]
@@ -443,15 +447,16 @@ class QrackAceBackend:
443
447
 
444
448
  return qb, lhv
445
449
 
446
- def _get_lhv_bloch_angles(self, sim):
450
+ @staticmethod
451
+ def _get_lhv_bloch_angles(sim):
447
452
  # Z axis
448
- z = 1 - 2 * sim.prob(Pauli.PauliZ)
453
+ z = sim.bloch[2]
449
454
 
450
455
  # X axis
451
- x = 1 - 2 * sim.prob(Pauli.PauliX)
456
+ x = sim.bloch[0]
452
457
 
453
458
  # Y axis
454
- y = 1 - 2 * sim.prob(Pauli.PauliY)
459
+ y = sim.bloch[1]
455
460
 
456
461
  inclination = math.atan2(math.sqrt(x**2 + y**2), z)
457
462
  azimuth = math.atan2(y, x)
@@ -483,9 +488,7 @@ class QrackAceBackend:
483
488
 
484
489
  return azimuth, inclination
485
490
 
486
- def _rotate_to_bloch(
487
- self, hq, delta_azimuth, delta_inclination
488
- ):
491
+ def _rotate_to_bloch(self, hq, delta_azimuth, delta_inclination):
489
492
  sim = self.sim[hq[0]]
490
493
  q = hq[1]
491
494
 
@@ -502,10 +505,8 @@ class QrackAceBackend:
502
505
 
503
506
  sim.mtrx([m00, m01, m10, m11], q)
504
507
 
505
-
506
- def _rotate_lhv_to_bloch(
507
- self, sim, delta_azimuth, delta_inclination
508
- ):
508
+ @staticmethod
509
+ def _rotate_lhv_to_bloch(sim, delta_azimuth, delta_inclination):
509
510
  # Apply rotation as "Azimuth, Inclination" (AI)
510
511
  cosA = math.cos(delta_azimuth)
511
512
  sinA = math.sin(delta_azimuth)
@@ -519,14 +520,13 @@ class QrackAceBackend:
519
520
 
520
521
  sim.mtrx([m00, m01, m10, m11])
521
522
 
522
-
523
523
  def _correct(self, lq, phase=False, skip_rotation=False):
524
524
  hq = self._unpack(lq)
525
525
 
526
526
  if len(hq) == 1:
527
527
  return
528
528
 
529
- qb, lhv = self._get_qb_lhv_indices(hq)
529
+ qb, lhv = QrackAceBackend._get_qb_lhv_indices(hq)
530
530
 
531
531
  if phase:
532
532
  for q in qb:
@@ -575,7 +575,7 @@ class QrackAceBackend:
575
575
  a, i = [0, 0, 0, 0, 0], [0, 0, 0, 0, 0]
576
576
  a[0], i[0] = self._get_bloch_angles(hq[0])
577
577
  a[1], i[1] = self._get_bloch_angles(hq[1])
578
- a[2], i[2] = self._get_lhv_bloch_angles(hq[2])
578
+ a[2], i[2] = QrackAceBackend._get_lhv_bloch_angles(hq[2])
579
579
  a[3], i[3] = self._get_bloch_angles(hq[3])
580
580
  a[4], i[4] = self._get_bloch_angles(hq[4])
581
581
 
@@ -591,12 +591,12 @@ class QrackAceBackend:
591
591
  i_target /= 5
592
592
  for x in range(5):
593
593
  if x == 2:
594
- self._rotate_lhv_to_bloch(hq[x], a_target - a[x], i_target - i[x])
594
+ QrackAceBackend._rotate_lhv_to_bloch(
595
+ hq[x], a_target - a[x], i_target - i[x]
596
+ )
595
597
  else:
596
598
  self._rotate_to_bloch(hq[x], a_target - a[x], i_target - i[x])
597
599
 
598
- self.apply_magnetic_bias([lq], self.correction_bias)
599
-
600
600
  else:
601
601
  # RMS
602
602
  p = [
@@ -620,7 +620,7 @@ class QrackAceBackend:
620
620
  a, i = [0, 0, 0], [0, 0, 0]
621
621
  a[0], i[0] = self._get_bloch_angles(hq[0])
622
622
  a[1], i[1] = self._get_bloch_angles(hq[1])
623
- a[2], i[2] = self._get_lhv_bloch_angles(hq[2])
623
+ a[2], i[2] = QrackAceBackend._get_lhv_bloch_angles(hq[2])
624
624
 
625
625
  a_target = 0
626
626
  i_target = 0
@@ -634,12 +634,12 @@ class QrackAceBackend:
634
634
  i_target /= 3
635
635
  for x in range(3):
636
636
  if x == 2:
637
- self._rotate_lhv_to_bloch(hq[x], a_target - a[x], i_target - i[x])
637
+ QrackAceBackend._rotate_lhv_to_bloch(
638
+ hq[x], a_target - a[x], i_target - i[x]
639
+ )
638
640
  else:
639
641
  self._rotate_to_bloch(hq[x], a_target - a[x], i_target - i[x])
640
642
 
641
- self.apply_magnetic_bias([lq], self.correction_bias)
642
-
643
643
  if phase:
644
644
  for q in qb:
645
645
  b = hq[q]
@@ -656,14 +656,18 @@ class QrackAceBackend:
656
656
  for c in range(len(hq)):
657
657
  h = hq[c]
658
658
  if c == 2:
659
- a, i = self._get_lhv_bloch_angles(h)
660
- self._rotate_lhv_to_bloch(
661
- h, math.atan(math.tan(a) * b) - a, math.atan(math.tan(i) * b) - i
659
+ a, i = QrackAceBackend._get_lhv_bloch_angles(h)
660
+ QrackAceBackend._rotate_lhv_to_bloch(
661
+ h,
662
+ math.atan(math.tan(a) * b) - a,
663
+ math.atan(math.tan(i) * b) - i,
662
664
  )
663
665
  else:
664
666
  a, i = self._get_bloch_angles(h)
665
667
  self._rotate_to_bloch(
666
- h, math.atan(math.tan(a) * b) - a, math.atan(math.tan(i) * b) - i
668
+ h,
669
+ math.atan(math.tan(a) * b) - a,
670
+ math.atan(math.tan(i) * b) - i,
667
671
  )
668
672
 
669
673
  def u(self, lq, th, ph, lm):
@@ -673,7 +677,7 @@ class QrackAceBackend:
673
677
  self.sim[b[0]].u(b[1], th, ph, lm)
674
678
  return
675
679
 
676
- qb, lhv = self._get_qb_lhv_indices(hq)
680
+ qb, lhv = QrackAceBackend._get_qb_lhv_indices(hq)
677
681
 
678
682
  for q in qb:
679
683
  b = hq[q]
@@ -692,7 +696,7 @@ class QrackAceBackend:
692
696
  self.sim[b[0]].r(p, th, b[1])
693
697
  return
694
698
 
695
- qb, lhv = self._get_qb_lhv_indices(hq)
699
+ qb, lhv = QrackAceBackend._get_qb_lhv_indices(hq)
696
700
 
697
701
  for q in qb:
698
702
  b = hq[q]
@@ -720,7 +724,7 @@ class QrackAceBackend:
720
724
 
721
725
  self._correct(lq)
722
726
 
723
- qb, lhv = self._get_qb_lhv_indices(hq)
727
+ qb, lhv = QrackAceBackend._get_qb_lhv_indices(hq)
724
728
 
725
729
  for q in qb:
726
730
  b = hq[q]
@@ -738,7 +742,7 @@ class QrackAceBackend:
738
742
  self.sim[b[0]].s(b[1])
739
743
  return
740
744
 
741
- qb, lhv = self._get_qb_lhv_indices(hq)
745
+ qb, lhv = QrackAceBackend._get_qb_lhv_indices(hq)
742
746
 
743
747
  for q in qb:
744
748
  b = hq[q]
@@ -754,7 +758,7 @@ class QrackAceBackend:
754
758
  self.sim[b[0]].adjs(b[1])
755
759
  return
756
760
 
757
- qb, lhv = self._get_qb_lhv_indices(hq)
761
+ qb, lhv = QrackAceBackend._get_qb_lhv_indices(hq)
758
762
 
759
763
  for q in qb:
760
764
  b = hq[q]
@@ -770,7 +774,7 @@ class QrackAceBackend:
770
774
  self.sim[b[0]].x(b[1])
771
775
  return
772
776
 
773
- qb, lhv = self._get_qb_lhv_indices(hq)
777
+ qb, lhv = QrackAceBackend._get_qb_lhv_indices(hq)
774
778
 
775
779
  for q in qb:
776
780
  b = hq[q]
@@ -786,7 +790,7 @@ class QrackAceBackend:
786
790
  self.sim[b[0]].y(b[1])
787
791
  return
788
792
 
789
- qb, lhv = self._get_qb_lhv_indices(hq)
793
+ qb, lhv = QrackAceBackend._get_qb_lhv_indices(hq)
790
794
 
791
795
  for q in qb:
792
796
  b = hq[q]
@@ -802,7 +806,7 @@ class QrackAceBackend:
802
806
  self.sim[b[0]].z(b[1])
803
807
  return
804
808
 
805
- qb, lhv = self._get_qb_lhv_indices(hq)
809
+ qb, lhv = QrackAceBackend._get_qb_lhv_indices(hq)
806
810
 
807
811
  for q in qb:
808
812
  b = hq[q]
@@ -818,7 +822,7 @@ class QrackAceBackend:
818
822
  self.sim[b[0]].t(b[1])
819
823
  return
820
824
 
821
- qb, lhv = self._get_qb_lhv_indices(hq)
825
+ qb, lhv = QrackAceBackend._get_qb_lhv_indices(hq)
822
826
 
823
827
  for q in qb:
824
828
  b = hq[q]
@@ -834,7 +838,7 @@ class QrackAceBackend:
834
838
  self.sim[b[0]].adjt(b[1])
835
839
  return
836
840
 
837
- qb, lhv = self._get_qb_lhv_indices(hq)
841
+ qb, lhv = QrackAceBackend._get_qb_lhv_indices(hq)
838
842
 
839
843
  for q in qb:
840
844
  b = hq[q]
@@ -916,8 +920,8 @@ class QrackAceBackend:
916
920
 
917
921
  self._correct(lq1)
918
922
 
919
- qb1, lhv1 = self._get_qb_lhv_indices(hq1)
920
- qb2, lhv2 = self._get_qb_lhv_indices(hq2)
923
+ qb1, lhv1 = QrackAceBackend._get_qb_lhv_indices(hq1)
924
+ qb2, lhv2 = QrackAceBackend._get_qb_lhv_indices(hq2)
921
925
  # Apply cross coupling on hardware qubits first
922
926
  self._apply_coupling(pauli, anti, qb1, lhv1, hq1, qb2, lhv2, hq2, lq1_lr)
923
927
  # Apply coupling to the local-hidden-variable target
@@ -1064,7 +1068,7 @@ class QrackAceBackend:
1064
1068
  p = self.prob(lq)
1065
1069
  result = ((p + self._epsilon) >= 1) or (random.random() < p)
1066
1070
 
1067
- qb, lhv = self._get_qb_lhv_indices(hq)
1071
+ qb, lhv = QrackAceBackend._get_qb_lhv_indices(hq)
1068
1072
 
1069
1073
  for q in qb:
1070
1074
  b = hq[q]
@@ -1090,7 +1094,7 @@ class QrackAceBackend:
1090
1094
 
1091
1095
  self._correct(lq)
1092
1096
 
1093
- qb, lhv = self._get_qb_lhv_indices(hq)
1097
+ qb, lhv = QrackAceBackend._get_qb_lhv_indices(hq)
1094
1098
 
1095
1099
  for q in qb:
1096
1100
  b = hq[q]
@@ -1443,6 +1447,7 @@ class QrackAceBackend:
1443
1447
 
1444
1448
  return _data
1445
1449
 
1450
+ @staticmethod
1446
1451
  def get_qiskit_basis_gates():
1447
1452
  return [
1448
1453
  "id",
pyqrack/qrack_circuit.py CHANGED
@@ -59,7 +59,7 @@ class QrackCircuit:
59
59
  self.cid = Qrack.qrack_lib.qcircuit_inverse(clone_cid)
60
60
  elif len(past_light_cone) > 0:
61
61
  self.cid = Qrack.qrack_lib.qcircuit_past_light_cone(
62
- clone_cid, len(past_light_cone), self._ulonglong_byref(past_light_cone)
62
+ clone_cid, len(past_light_cone), QrackCircuit._ulonglong_byref(past_light_cone)
63
63
  )
64
64
  else:
65
65
  self.cid = Qrack.qrack_lib.init_qcircuit_clone(clone_cid)
@@ -69,16 +69,20 @@ class QrackCircuit:
69
69
  Qrack.qrack_lib.destroy_qcircuit(self.cid)
70
70
  self.cid = None
71
71
 
72
- def _ulonglong_byref(self, a):
72
+ @staticmethod
73
+ def _ulonglong_byref(a):
73
74
  return (ctypes.c_ulonglong * len(a))(*a)
74
75
 
75
- def _double_byref(self, a):
76
+ @staticmethod
77
+ def _double_byref(a):
76
78
  return (ctypes.c_double * len(a))(*a)
77
79
 
78
- def _complex_byref(self, a):
80
+ @staticmethod
81
+ def _complex_byref(a):
79
82
  t = [(c.real, c.imag) for c in a]
80
- return self._double_byref([float(item) for sublist in t for item in sublist])
83
+ return QrackCircuit._double_byref([float(item) for sublist in t for item in sublist])
81
84
 
85
+ @staticmethod
82
86
  def _mtrx_to_u4(m):
83
87
  nrm = abs(m[0])
84
88
  if (nrm * nrm) < sys.float_info.epsilon:
@@ -103,6 +107,7 @@ class QrackCircuit:
103
107
 
104
108
  return th, ph, lm, np.angle(phase)
105
109
 
110
+ @staticmethod
106
111
  def _u3_to_mtrx(params):
107
112
  th = float(params[0])
108
113
  ph = float(params[1])
@@ -115,6 +120,7 @@ class QrackCircuit:
115
120
 
116
121
  return [c + 0j, -el * s, ep * s, ep * el * c]
117
122
 
123
+ @staticmethod
118
124
  def _u4_to_mtrx(params):
119
125
  m = QrackCircuit._u3_to_mtrx(params)
120
126
  g = np.exp(1j * float(params[3]))
@@ -123,6 +129,7 @@ class QrackCircuit:
123
129
 
124
130
  return m
125
131
 
132
+ @staticmethod
126
133
  def _make_mtrx_unitary(m):
127
134
  return QrackCircuit._u4_to_mtrx(QrackCircuit._mtrx_to_u4(m))
128
135
 
@@ -190,7 +197,7 @@ class QrackCircuit:
190
197
  raise ValueError(
191
198
  "2x2 matrix 'm' in QrackCircuit.mtrx() must contain at least 4 elements."
192
199
  )
193
- Qrack.qrack_lib.qcircuit_append_1qb(self.cid, self._complex_byref(m), q)
200
+ Qrack.qrack_lib.qcircuit_append_1qb(self.cid, QrackCircuit._complex_byref(m), q)
194
201
 
195
202
  def ucmtrx(self, c, m, q, p):
196
203
  """Multi-controlled single-target-qubit gate
@@ -214,7 +221,7 @@ class QrackCircuit:
214
221
  "2x2 matrix 'm' in QrackCircuit.ucmtrx() must contain at least 4 elements."
215
222
  )
216
223
  Qrack.qrack_lib.qcircuit_append_mc(
217
- self.cid, self._complex_byref(m), len(c), self._ulonglong_byref(c), q, p
224
+ self.cid, QrackCircuit._complex_byref(m), len(c), QrackCircuit._ulonglong_byref(c), q, p
218
225
  )
219
226
 
220
227
  def run(self, qsim):
@@ -248,20 +255,6 @@ class QrackCircuit:
248
255
  """
249
256
  Qrack.qrack_lib.qcircuit_out_to_file(self.cid, filename.encode("utf-8"))
250
257
 
251
- def in_from_file(filename):
252
- """Read in optimized circuit from file
253
-
254
- Reads in an (optimized) circuit from a file named
255
- according to the "filename" parameter.
256
-
257
- Args:
258
- filename: Name of file
259
- """
260
- out = QrackCircuit()
261
- Qrack.qrack_lib.qcircuit_in_from_file(out.cid, filename.encode("utf-8"))
262
-
263
- return out
264
-
265
258
  def out_to_string(self):
266
259
  """Output optimized circuit to string
267
260
 
@@ -273,19 +266,6 @@ class QrackCircuit:
273
266
 
274
267
  return out.value.decode("utf-8")
275
268
 
276
- def file_gate_count(filename):
277
- """File gate count
278
-
279
- Return the count of gates in a QrackCircuit file
280
-
281
- Args:
282
- filename: Name of file
283
- """
284
- tokens = []
285
- with open(filename, "r") as file:
286
- tokens = file.read().split()
287
- return int(tokens[1])
288
-
289
269
  def to_qiskit_circuit(self):
290
270
  """Convert to a Qiskit circuit
291
271
 
@@ -302,6 +282,36 @@ class QrackCircuit:
302
282
 
303
283
  return QrackCircuit.string_to_qiskit_circuit(self.out_to_string())
304
284
 
285
+ @staticmethod
286
+ def in_from_file(filename):
287
+ """Read in optimized circuit from file
288
+
289
+ Reads in an (optimized) circuit from a file named
290
+ according to the "filename" parameter.
291
+
292
+ Args:
293
+ filename: Name of file
294
+ """
295
+ out = QrackCircuit()
296
+ Qrack.qrack_lib.qcircuit_in_from_file(out.cid, filename.encode("utf-8"))
297
+
298
+ return out
299
+
300
+ @staticmethod
301
+ def file_gate_count(filename):
302
+ """File gate count
303
+
304
+ Return the count of gates in a QrackCircuit file
305
+
306
+ Args:
307
+ filename: Name of file
308
+ """
309
+ tokens = []
310
+ with open(filename, "r") as file:
311
+ tokens = file.read().split()
312
+ return int(tokens[1])
313
+
314
+ @staticmethod
305
315
  def file_to_qiskit_circuit(filename):
306
316
  """Convert an output file to a Qiskit circuit
307
317
 
@@ -325,6 +335,7 @@ class QrackCircuit:
325
335
  with open(filename, "r") as file:
326
336
  return QrackCircuit.string_to_qiskit_circuit(file.read())
327
337
 
338
+ @staticmethod
328
339
  def string_to_qiskit_circuit(circ_string):
329
340
  """Convert an output string to a Qiskit circuit
330
341
 
@@ -398,6 +409,7 @@ class QrackCircuit:
398
409
 
399
410
  return circ
400
411
 
412
+ @staticmethod
401
413
  def in_from_qiskit_circuit(circ):
402
414
  """Read a Qiskit circuit into a QrackCircuit
403
415
 
@@ -443,6 +455,7 @@ class QrackCircuit:
443
455
 
444
456
  return out
445
457
 
458
+ @staticmethod
446
459
  def file_to_quimb_circuit(
447
460
  filename,
448
461
  circuit_type=QuimbCircuitType.Circuit,
@@ -526,6 +539,7 @@ class QrackCircuit:
526
539
 
527
540
  return tcirc
528
541
 
542
+ @staticmethod
529
543
  def file_to_tensorcircuit(
530
544
  filename, inputs=None, circuit_params=None, binding_params=None
531
545
  ):
@@ -558,6 +572,7 @@ class QrackCircuit:
558
572
  qcirc, qcirc.num_qubits, inputs, circuit_params, binding_params
559
573
  )
560
574
 
575
+ @staticmethod
561
576
  def in_from_tensorcircuit(tcirc, enable_instruction=False, enable_inputs=False):
562
577
  """Convert a TensorCircuit circuit to a QrackCircuit
563
578
 
pyqrack/qrack_neuron.py CHANGED
@@ -64,7 +64,7 @@ class QrackNeuron:
64
64
  self.nid = Qrack.qrack_lib.init_qneuron(
65
65
  simulator.sid,
66
66
  len(controls),
67
- self._ulonglong_byref(controls),
67
+ QrackNeuron._ulonglong_byref(controls),
68
68
  target,
69
69
  activation_fn,
70
70
  alpha,
@@ -99,10 +99,12 @@ class QrackNeuron:
99
99
  self._throw_if_error()
100
100
  return result
101
101
 
102
- def _ulonglong_byref(self, a):
102
+ @staticmethod
103
+ def _ulonglong_byref(a):
103
104
  return (ctypes.c_ulonglong * len(a))(*a)
104
105
 
105
- def _real1_byref(self, a):
106
+ @staticmethod
107
+ def _real1_byref(a):
106
108
  # This needs to be c_double, if PyQrack is built with fp64.
107
109
  if Qrack.fppow < 6:
108
110
  return (ctypes.c_float * len(a))(*a)
@@ -125,7 +127,7 @@ class QrackNeuron:
125
127
  raise ValueError(
126
128
  "Angles 'a' in QrackNeuron.set_angles() must contain at least (2 ** len(self.controls)) elements."
127
129
  )
128
- Qrack.qrack_lib.set_qneuron_angles(self.nid, self._real1_byref(a))
130
+ Qrack.qrack_lib.set_qneuron_angles(self.nid, QrackNeuron._real1_byref(a))
129
131
  self._throw_if_error()
130
132
 
131
133
  def get_angles(self):
@@ -137,7 +139,7 @@ class QrackNeuron:
137
139
  Raises:
138
140
  RuntimeError: QrackNeuron C++ library raised an exception.
139
141
  """
140
- ket = self._real1_byref([0.0] * (1 << len(self.controls)))
142
+ ket = QrackNeuron._real1_byref([0.0] * (1 << len(self.controls)))
141
143
  Qrack.qrack_lib.get_qneuron_angles(self.nid, ket)
142
144
  self._throw_if_error()
143
145
  return list(ket)
@@ -260,3 +262,89 @@ class QrackNeuron:
260
262
  """
261
263
  Qrack.qrack_lib.qneuron_learn_permutation(self.nid, eta, e, r)
262
264
  self._throw_if_error()
265
+
266
+ @staticmethod
267
+ def quantile_bounds(vec, bits):
268
+ """ Calculate vector quantile bounds
269
+
270
+ This is a static helper method to calculate the quantile
271
+ bounds of 2 ** bits worth of quantiles.
272
+
273
+ Args:
274
+ vec: numerical vector
275
+ bits: log2() of quantile count
276
+
277
+ Returns:
278
+ Quantile (n + 1) bounds for n-quantile division, including
279
+ minimum and maximum values
280
+ """
281
+
282
+ bins = 1 << bits
283
+ n = len(vec)
284
+ vec_sorted = sorted(vec)
285
+
286
+ return [vec_sorted[0]] + [vec_sorted[(k * n) // bins] for k in range(1, bins)] + [vec_sorted[-1]]
287
+
288
+ @staticmethod
289
+ def discretize(vec, bounds):
290
+ """ Discretize vector by quantile bounds
291
+
292
+ This is a static helper method to discretize a numerical
293
+ vector according to quantile bounds calculated by the
294
+ quantile_bounds(vec, bits) static method.
295
+
296
+ Args:
297
+ vec: numerical vector
298
+ bounds: (n + 1) n-quantile bounds including extrema
299
+
300
+ Returns:
301
+ Discretized bit-row vector, least-significant first
302
+ """
303
+
304
+ bounds = bounds[1:]
305
+ bounds_len = len(bounds)
306
+ bits = bounds_len.bit_length() - 1
307
+ n = len(vec)
308
+ vec_discrete = [[False] * n for _ in range(bits)]
309
+ for i, v in enumerate(vec):
310
+ p = 0
311
+ while (p < bounds_len) and (v > bounds[p]):
312
+ p += 1
313
+ for b in range(bits):
314
+ vec_discrete[b][i] = bool((p >> b) & 1)
315
+
316
+ return vec_discrete
317
+
318
+ @staticmethod
319
+ def flatten_and_transpose(arr):
320
+ """ Flatten and transpose feature matrix
321
+
322
+ This is a static helper method to convert a multi-feature
323
+ bit-row matrix to an observation-row matrix with flat
324
+ feature columns.
325
+
326
+ Args:
327
+ arr: bit-row matrix
328
+
329
+ Returns:
330
+ Observation-row matrix with flat feature columns
331
+ """
332
+ return list(zip(*[item for sublist in arr for item in sublist]))
333
+
334
+ @staticmethod
335
+ def bin_endpoints_average(bounds):
336
+ """ Bin endpoints average
337
+
338
+ This is a static helper method that accepts the output
339
+ bins from quantile_bounds() and returns the average points
340
+ between the bin endpoints. (This is NOT always necessarily
341
+ the best heuristic for how to convert binned results back
342
+ to numerical results, but it is often a reasonable way.)
343
+
344
+ Args:
345
+ bounds: (n + 1) n-quantile bounds including extrema
346
+
347
+ Returns:
348
+ List of average points between the bin endpoints
349
+ """
350
+ return [((bounds[i] + bounds[i + 1]) / 2) for i in range(len(bounds) - 1)]