eqc-models 0.9.9__py3-none-any.whl → 0.10.1__py3-none-any.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.
Files changed (70) hide show
  1. eqc_models-0.10.1.data/platlib/compile_extensions.py +67 -0
  2. {eqc_models-0.9.9.data → eqc_models-0.10.1.data}/platlib/eqc_models/algorithms/penaltymultiplier.py +20 -7
  3. {eqc_models-0.9.9.data → eqc_models-0.10.1.data}/platlib/eqc_models/base/polyeval.c +127 -123
  4. {eqc_models-0.9.9.data → eqc_models-0.10.1.data}/platlib/eqc_models/base/polyeval.cpython-310-darwin.so +0 -0
  5. eqc_models-0.10.1.data/platlib/eqc_models/base.py +115 -0
  6. eqc_models-0.10.1.data/platlib/eqc_models/combinatorics/__init__.py +5 -0
  7. eqc_models-0.10.1.data/platlib/eqc_models/combinatorics/setcover.py +94 -0
  8. eqc_models-0.10.1.data/platlib/eqc_models/communitydetection.py +25 -0
  9. eqc_models-0.10.1.data/platlib/eqc_models/eqcdirectsolver.py +61 -0
  10. eqc_models-0.10.1.data/platlib/eqc_models/graph/__init__.py +6 -0
  11. {eqc_models-0.9.9.data → eqc_models-0.10.1.data}/platlib/eqc_models/graph/base.py +28 -17
  12. eqc_models-0.10.1.data/platlib/eqc_models/graph/partition.py +148 -0
  13. eqc_models-0.10.1.data/platlib/eqc_models/graphs.py +28 -0
  14. eqc_models-0.10.1.data/platlib/eqc_models/maxcut.py +113 -0
  15. eqc_models-0.10.1.data/platlib/eqc_models/maxkcut.py +185 -0
  16. eqc_models-0.10.1.data/platlib/eqc_models/ml/classifierqboost.py +628 -0
  17. eqc_models-0.10.1.data/platlib/eqc_models/ml/cvqboost_hamiltonian.pyx +83 -0
  18. eqc_models-0.10.1.data/platlib/eqc_models/ml/cvqboost_hamiltonian_c_func.c +68 -0
  19. eqc_models-0.10.1.data/platlib/eqc_models/ml/cvqboost_hamiltonian_c_func.h +14 -0
  20. eqc_models-0.10.1.data/platlib/eqc_models/quadraticmodel.py +131 -0
  21. eqc_models-0.10.1.data/platlib/eqc_models/solvers/eqcdirect.py +160 -0
  22. {eqc_models-0.9.9.data → eqc_models-0.10.1.data}/platlib/eqc_models/utilities/polynomial.py +11 -0
  23. {eqc_models-0.9.9.dist-info → eqc_models-0.10.1.dist-info}/METADATA +2 -1
  24. eqc_models-0.10.1.dist-info/RECORD +66 -0
  25. eqc_models-0.9.9.data/platlib/compile_extensions.py +0 -23
  26. eqc_models-0.9.9.data/platlib/eqc_models/graph/__init__.py +0 -5
  27. eqc_models-0.9.9.data/platlib/eqc_models/ml/classifierqboost.py +0 -423
  28. eqc_models-0.9.9.dist-info/RECORD +0 -52
  29. {eqc_models-0.9.9.data → eqc_models-0.10.1.data}/platlib/eqc_models/__init__.py +0 -0
  30. {eqc_models-0.9.9.data → eqc_models-0.10.1.data}/platlib/eqc_models/algorithms/__init__.py +0 -0
  31. {eqc_models-0.9.9.data → eqc_models-0.10.1.data}/platlib/eqc_models/algorithms/base.py +0 -0
  32. {eqc_models-0.9.9.data → eqc_models-0.10.1.data}/platlib/eqc_models/allocation/__init__.py +0 -0
  33. {eqc_models-0.9.9.data → eqc_models-0.10.1.data}/platlib/eqc_models/allocation/allocation.py +0 -0
  34. {eqc_models-0.9.9.data → eqc_models-0.10.1.data}/platlib/eqc_models/allocation/portbase.py +0 -0
  35. {eqc_models-0.9.9.data → eqc_models-0.10.1.data}/platlib/eqc_models/allocation/portmomentum.py +0 -0
  36. {eqc_models-0.9.9.data → eqc_models-0.10.1.data}/platlib/eqc_models/assignment/__init__.py +0 -0
  37. {eqc_models-0.9.9.data → eqc_models-0.10.1.data}/platlib/eqc_models/assignment/qap.py +0 -0
  38. {eqc_models-0.9.9.data → eqc_models-0.10.1.data}/platlib/eqc_models/assignment/setpartition.py +0 -0
  39. {eqc_models-0.9.9.data → eqc_models-0.10.1.data}/platlib/eqc_models/base/__init__.py +0 -0
  40. {eqc_models-0.9.9.data → eqc_models-0.10.1.data}/platlib/eqc_models/base/base.py +0 -0
  41. {eqc_models-0.9.9.data → eqc_models-0.10.1.data}/platlib/eqc_models/base/constraints.py +0 -0
  42. {eqc_models-0.9.9.data → eqc_models-0.10.1.data}/platlib/eqc_models/base/operators.py +0 -0
  43. {eqc_models-0.9.9.data → eqc_models-0.10.1.data}/platlib/eqc_models/base/polyeval.pyx +0 -0
  44. {eqc_models-0.9.9.data → eqc_models-0.10.1.data}/platlib/eqc_models/base/polynomial.py +0 -0
  45. {eqc_models-0.9.9.data → eqc_models-0.10.1.data}/platlib/eqc_models/base/quadratic.py +0 -0
  46. {eqc_models-0.9.9.data → eqc_models-0.10.1.data}/platlib/eqc_models/decoding.py +0 -0
  47. {eqc_models-0.9.9.data → eqc_models-0.10.1.data}/platlib/eqc_models/graph/hypergraph.py +0 -0
  48. {eqc_models-0.9.9.data → eqc_models-0.10.1.data}/platlib/eqc_models/graph/maxcut.py +0 -0
  49. {eqc_models-0.9.9.data → eqc_models-0.10.1.data}/platlib/eqc_models/graph/maxkcut.py +0 -0
  50. {eqc_models-0.9.9.data → eqc_models-0.10.1.data}/platlib/eqc_models/ml/__init__.py +0 -0
  51. {eqc_models-0.9.9.data → eqc_models-0.10.1.data}/platlib/eqc_models/ml/classifierbase.py +0 -0
  52. {eqc_models-0.9.9.data → eqc_models-0.10.1.data}/platlib/eqc_models/ml/classifierqsvm.py +0 -0
  53. {eqc_models-0.9.9.data → eqc_models-0.10.1.data}/platlib/eqc_models/ml/clustering.py +0 -0
  54. {eqc_models-0.9.9.data → eqc_models-0.10.1.data}/platlib/eqc_models/ml/clusteringbase.py +0 -0
  55. {eqc_models-0.9.9.data → eqc_models-0.10.1.data}/platlib/eqc_models/ml/decomposition.py +0 -0
  56. {eqc_models-0.9.9.data → eqc_models-0.10.1.data}/platlib/eqc_models/ml/forecast.py +0 -0
  57. {eqc_models-0.9.9.data → eqc_models-0.10.1.data}/platlib/eqc_models/ml/forecastbase.py +0 -0
  58. {eqc_models-0.9.9.data → eqc_models-0.10.1.data}/platlib/eqc_models/ml/regressor.py +0 -0
  59. {eqc_models-0.9.9.data → eqc_models-0.10.1.data}/platlib/eqc_models/ml/regressorbase.py +0 -0
  60. {eqc_models-0.9.9.data → eqc_models-0.10.1.data}/platlib/eqc_models/ml/reservoir.py +0 -0
  61. {eqc_models-0.9.9.data → eqc_models-0.10.1.data}/platlib/eqc_models/sequence/__init__.py +0 -0
  62. {eqc_models-0.9.9.data → eqc_models-0.10.1.data}/platlib/eqc_models/sequence/tsp.py +0 -0
  63. {eqc_models-0.9.9.data → eqc_models-0.10.1.data}/platlib/eqc_models/solvers/__init__.py +0 -0
  64. {eqc_models-0.9.9.data → eqc_models-0.10.1.data}/platlib/eqc_models/solvers/qciclient.py +0 -0
  65. {eqc_models-0.9.9.data → eqc_models-0.10.1.data}/platlib/eqc_models/utilities/__init__.py +0 -0
  66. {eqc_models-0.9.9.data → eqc_models-0.10.1.data}/platlib/eqc_models/utilities/fileio.py +0 -0
  67. {eqc_models-0.9.9.data → eqc_models-0.10.1.data}/platlib/eqc_models/utilities/qplib.py +0 -0
  68. {eqc_models-0.9.9.dist-info → eqc_models-0.10.1.dist-info}/LICENSE.txt +0 -0
  69. {eqc_models-0.9.9.dist-info → eqc_models-0.10.1.dist-info}/WHEEL +0 -0
  70. {eqc_models-0.9.9.dist-info → eqc_models-0.10.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,628 @@
1
+ # (C) Quantum Computing Inc., 2024.
2
+ # Import libs
3
+ import os
4
+ import sys
5
+ import time
6
+ import datetime
7
+ import json
8
+ import gc
9
+ import warnings
10
+ from functools import wraps
11
+ from multiprocessing import shared_memory, Pool, set_start_method, Manager
12
+ from multiprocessing.managers import SharedMemoryManager
13
+ import numpy as np
14
+ from sklearn.tree import DecisionTreeClassifier
15
+ from sklearn.naive_bayes import GaussianNB
16
+ from sklearn.linear_model import LogisticRegression
17
+ from sklearn.gaussian_process import GaussianProcessClassifier
18
+ from sklearn.gaussian_process.kernels import RBF
19
+
20
+ from eqc_models.ml.classifierbase import ClassifierBase
21
+ #from eqc_models.ml.cvqboost_hamiltonian import get_hamiltonian_pyx
22
+
23
+
24
+ def timer(func):
25
+ @wraps(func)
26
+ def wrapper(*args, **kwargs):
27
+ beg_time = time.time()
28
+ val = func(*args, **kwargs)
29
+ end_time = time.time()
30
+ tot_time = end_time - beg_time
31
+
32
+ print(
33
+ "Runtime of %s: %0.2f seconds!"
34
+ % (
35
+ func.__name__,
36
+ tot_time,
37
+ )
38
+ )
39
+
40
+ return val
41
+
42
+ return wrapper
43
+
44
+
45
+ class WeakClassifier:
46
+ def __init__(
47
+ self,
48
+ X_train,
49
+ y_train,
50
+ weak_cls_type,
51
+ max_depth=10,
52
+ min_samples_split=100,
53
+ num_jobs=1,
54
+ ):
55
+ assert X_train.shape[0] == len(y_train)
56
+
57
+ self.X_train = X_train
58
+ self.y_train = y_train
59
+
60
+ if weak_cls_type == "dct":
61
+ self.clf = DecisionTreeClassifier(
62
+ max_depth=max_depth,
63
+ min_samples_split=min_samples_split,
64
+ random_state=0,
65
+ )
66
+ elif weak_cls_type == "nb":
67
+ self.clf = GaussianNB()
68
+ elif weak_cls_type == "lg":
69
+ self.clf = LogisticRegression(random_state=0)
70
+ elif weak_cls_type == "gp":
71
+ self.clf = GaussianProcessClassifier(
72
+ kernel=1.0 * RBF(1.0),
73
+ random_state=0,
74
+ )
75
+ else:
76
+ assert False, (
77
+ "Unknown weak classifier type <%s>!" % weak_cls_type
78
+ )
79
+
80
+ def train(self):
81
+ self.clf.fit(self.X_train, self.y_train)
82
+
83
+ def predict(self, X):
84
+ return self.clf.predict(X)
85
+
86
+
87
+ class QBoostClassifier(ClassifierBase):
88
+ """An implementation of QBoost classifier that uses QCi's Dirac-3.
89
+
90
+ Parameters
91
+ ----------
92
+ relaxation_schedule: Relaxation schedule used by Dirac-3;
93
+ default: 2.
94
+
95
+ num_samples: Number of samples used by Dirac-3; default: 1.
96
+
97
+ lambda_coef: A penalty multiplier; default: 0.
98
+
99
+ weak_cls_schedule: Weak classifier schedule. Is either 1, 2,
100
+ or 3; default: 2.
101
+
102
+ weak_cls_type: Type of weak classifier
103
+ - dct: Decison tree classifier
104
+ - nb: Naive Baysian classifier
105
+ - lg: Logistic regression
106
+ - gp: Gaussian process classifier
107
+
108
+ default: dct.
109
+
110
+ weak_max_depth: Max depth of the tree. Applied only when
111
+ weak_cls_type="dct". Default: 10.
112
+
113
+ weak_min_samples_split: The minimum number of samples required
114
+ to split an internal node. Applied only when
115
+ weak_cls_type="dct". Default: 100.
116
+
117
+ Examples
118
+ -----------
119
+
120
+ >>> from sklearn import datasets
121
+ >>> from sklearn.preprocessing import MinMaxScaler
122
+ >>> from sklearn.model_selection import train_test_split
123
+ >>> iris = datasets.load_iris()
124
+ >>> X = iris.data
125
+ >>> y = iris.target
126
+ >>> scaler = MinMaxScaler()
127
+ >>> X = scaler.fit_transform(X)
128
+ >>> for i in range(len(y)):
129
+ ... if y[i] == 0:
130
+ ... y[i] = -1
131
+ ... elif y[i] == 2:
132
+ ... y[i] = 1
133
+ >>> X_train, X_test, y_train, y_test = train_test_split(
134
+ ... X,
135
+ ... y,
136
+ ... test_size=0.2,
137
+ ... random_state=42,
138
+ ... )
139
+ >>> from eqc_models.ml.classifierqboost import QBoostClassifier
140
+ >>> obj = QBoostClassifier(
141
+ ... relaxation_schedule=2,
142
+ ... num_samples=1,
143
+ ... lambda_coef=0.0,
144
+ ... )
145
+ >>> from contextlib import redirect_stdout
146
+ >>> import io
147
+ >>> f = io.StringIO()
148
+ >>> with redirect_stdout(f):
149
+ ... obj.fit(X_train, y_train)
150
+ ... y_train_prd = obj.predict(X_train)
151
+ ... y_test_prd = obj.predict(X_test)
152
+ """
153
+
154
+ def __init__(
155
+ self,
156
+ relaxation_schedule=2,
157
+ num_samples=1,
158
+ lambda_coef=0,
159
+ weak_cls_schedule=2,
160
+ weak_cls_type="lg",
161
+ weak_max_depth=10,
162
+ weak_min_samples_split=100,
163
+ weak_cls_strategy="multi_processing",
164
+ weak_cls_num_jobs=None,
165
+ ):
166
+ super(QBoostClassifier).__init__()
167
+
168
+ assert weak_cls_schedule in [1, 2, 3]
169
+ assert weak_cls_type in ["dct", "nb", "lg", "gp"]
170
+ assert weak_cls_strategy in [
171
+ "multi_processing",
172
+ "multi_processing_shm",
173
+ "sequential",
174
+ ]
175
+
176
+ self.relaxation_schedule = relaxation_schedule
177
+ self.num_samples = num_samples
178
+ self.lambda_coef = lambda_coef
179
+ self.weak_cls_schedule = weak_cls_schedule
180
+ self.weak_cls_type = weak_cls_type
181
+ self.weak_max_depth = weak_max_depth
182
+ self.weak_min_samples_split = weak_min_samples_split
183
+ self.weak_cls_strategy = weak_cls_strategy
184
+ if weak_cls_num_jobs is None or weak_cls_num_jobs <= 0:
185
+ self.weak_cls_num_jobs = os.cpu_count()
186
+ else:
187
+ self.weak_cls_num_jobs = int(weak_cls_num_jobs)
188
+
189
+ self.h_list = []
190
+ self.ind_list = []
191
+ self.classes_ = None
192
+
193
+ @timer
194
+ def _build_weak_classifiers_sq(self, X, y):
195
+ n_records = X.shape[0]
196
+ n_dims = X.shape[1]
197
+
198
+ assert len(y) == n_records
199
+
200
+ self.h_list = []
201
+ self.ind_list = []
202
+
203
+ num_workers = self.weak_cls_num_jobs
204
+
205
+ tasks = []
206
+ for l in range(n_dims):
207
+ weak_classifier = WeakClassifier(
208
+ X[:, [l]],
209
+ y,
210
+ self.weak_cls_type,
211
+ self.weak_max_depth,
212
+ self.weak_min_samples_split,
213
+ )
214
+ weak_classifier.train()
215
+ self.ind_list.append([l])
216
+ self.h_list.append(weak_classifier)
217
+
218
+ if self.weak_cls_schedule >= 2:
219
+ for i in range(n_dims):
220
+ for j in range(i + 1, n_dims):
221
+ weak_classifier = WeakClassifier(
222
+ X[:, [i, j]],
223
+ y,
224
+ self.weak_cls_type,
225
+ self.weak_max_depth,
226
+ self.weak_min_samples_split,
227
+ )
228
+ weak_classifier.train()
229
+ self.ind_list.append([i, j])
230
+ self.h_list.append(weak_classifier)
231
+
232
+ if self.weak_cls_schedule >= 3:
233
+ for i in range(n_dims):
234
+ for j in range(i + 1, n_dims):
235
+ for k in range(j + 1, n_dims):
236
+ weak_classifier = WeakClassifier(
237
+ X[:, [i, j, k]],
238
+ y,
239
+ self.weak_cls_type,
240
+ self.weak_max_depth,
241
+ self.weak_min_samples_split,
242
+ )
243
+ weak_classifier.train()
244
+ self.ind_list.append([i, j, k])
245
+ self.h_list.append(weak_classifier)
246
+
247
+ return
248
+
249
+ def _train_weak_classifier_mp(
250
+ self,
251
+ indices,
252
+ X_subset,
253
+ y,
254
+ n_records,
255
+ n_dims,
256
+ weak_cls_type,
257
+ weak_max_depth,
258
+ weak_min_samples_split,
259
+ ):
260
+ # Train the weak classifier
261
+ weak_classifier = WeakClassifier(
262
+ X_subset,
263
+ y,
264
+ weak_cls_type,
265
+ weak_max_depth,
266
+ weak_min_samples_split,
267
+ )
268
+ weak_classifier.train()
269
+
270
+ return indices, weak_classifier
271
+
272
+ @timer
273
+ def _build_weak_classifiers_mp(self, X, y):
274
+ n_records = X.shape[0]
275
+ n_dims = X.shape[1]
276
+
277
+ assert len(y) == n_records
278
+
279
+ self.h_list = []
280
+ self.ind_list = []
281
+
282
+ num_workers = self.weak_cls_num_jobs
283
+ print(f"Using {num_workers} workers to build weak classifiers.")
284
+
285
+ set_start_method("fork", force=True)
286
+
287
+ tasks = []
288
+ for l in range(n_dims):
289
+ tasks.append(
290
+ (
291
+ [l],
292
+ X[:, [l]],
293
+ y,
294
+ n_records,
295
+ n_dims,
296
+ self.weak_cls_type,
297
+ self.weak_max_depth,
298
+ self.weak_min_samples_split,
299
+ )
300
+ )
301
+
302
+ if self.weak_cls_schedule >= 2:
303
+ for i in range(n_dims):
304
+ for j in range(i + 1, n_dims):
305
+ tasks.append(
306
+ (
307
+ [i, j],
308
+ X[:, [i, j]],
309
+ y,
310
+ n_records,
311
+ n_dims,
312
+ self.weak_cls_type,
313
+ self.weak_max_depth,
314
+ self.weak_min_samples_split,
315
+ )
316
+ )
317
+
318
+ if self.weak_cls_schedule >= 3:
319
+ for i in range(n_dims):
320
+ for j in range(i + 1, n_dims):
321
+ for k in range(j + 1, n_dims):
322
+ tasks.append(
323
+ (
324
+ [i, j, k],
325
+ X[:, [i, j, k]],
326
+ y,
327
+ n_records,
328
+ n_dims,
329
+ self.weak_cls_type,
330
+ self.weak_max_depth,
331
+ self.weak_min_samples_split,
332
+ )
333
+ )
334
+
335
+ # Parallel execution using Pool
336
+ with Pool(processes=num_workers) as pool:
337
+ results = pool.starmap(self._train_weak_classifier_mp, tasks)
338
+
339
+ pool.join()
340
+ pool.close()
341
+
342
+ for indices, weak_classifier in results:
343
+ self.ind_list.append(indices)
344
+ self.h_list.append(weak_classifier)
345
+
346
+ return
347
+
348
+ def _train_weak_classifier_shm(
349
+ self,
350
+ indices,
351
+ shm_X_name,
352
+ shm_y_name,
353
+ shared_list,
354
+ n_records,
355
+ n_dims,
356
+ weak_cls_type,
357
+ weak_max_depth,
358
+ weak_min_samples_split,
359
+ ):
360
+ """Train a weak classifier using shared memory."""
361
+
362
+ shm_X_worker = shared_memory.SharedMemory(name=shm_X_name)
363
+ shm_y_worker = shared_memory.SharedMemory(name=shm_y_name)
364
+ X_shared = np.ndarray(
365
+ (n_records, n_dims), dtype=np.float32, buffer=shm_X_worker.buf
366
+ )
367
+ y_shared = np.ndarray(
368
+ (n_records,), dtype=np.float32, buffer=shm_y_worker.buf
369
+ )
370
+ X_subset = X_shared[:, indices]
371
+
372
+ weak_classifier = WeakClassifier(
373
+ X_subset,
374
+ y_shared,
375
+ weak_cls_type,
376
+ weak_max_depth,
377
+ weak_min_samples_split,
378
+ )
379
+ weak_classifier.train()
380
+
381
+ shared_list.append((indices, weak_classifier))
382
+
383
+ shm_X_worker.close()
384
+ shm_y_worker.close()
385
+
386
+ @timer
387
+ def _build_weak_classifiers_shm(self, X, y):
388
+ n_records = X.shape[0]
389
+ n_dims = X.shape[1]
390
+
391
+ assert len(y) == n_records
392
+
393
+ self.h_list = []
394
+ self.ind_list = []
395
+
396
+ num_workers = self.weak_cls_num_jobs
397
+ print(f"Using {num_workers} workers to build weak classifiers.")
398
+
399
+ set_start_method("fork", force=True)
400
+
401
+ X = np.ascontiguousarray(X, dtype=np.float32)
402
+ y = np.ascontiguousarray(y, dtype=np.float32)
403
+
404
+ with SharedMemoryManager() as shm_manager:
405
+ shm_X = shm_manager.SharedMemory(size=X.nbytes)
406
+ shm_y = shm_manager.SharedMemory(size=y.nbytes)
407
+
408
+ X_shared = np.ndarray(X.shape, dtype=X.dtype, buffer=shm_X.buf)
409
+ y_shared = np.ndarray(y.shape, dtype=y.dtype, buffer=shm_y.buf)
410
+
411
+ np.copyto(X_shared, X)
412
+ np.copyto(y_shared, y)
413
+
414
+ with Manager() as manager:
415
+ shared_list = manager.list()
416
+ tasks = []
417
+ for l in range(n_dims):
418
+ tasks.append(
419
+ (
420
+ [l],
421
+ shm_X.name,
422
+ shm_y.name,
423
+ shared_list,
424
+ n_records,
425
+ n_dims,
426
+ self.weak_cls_type,
427
+ self.weak_max_depth,
428
+ self.weak_min_samples_split,
429
+ )
430
+ )
431
+
432
+ if self.weak_cls_schedule >= 2:
433
+ for i in range(n_dims):
434
+ for j in range(i + 1, n_dims):
435
+ tasks.append(
436
+ (
437
+ [i, j],
438
+ shm_X.name,
439
+ shm_y.name,
440
+ shared_list,
441
+ n_records,
442
+ n_dims,
443
+ self.weak_cls_type,
444
+ self.weak_max_depth,
445
+ self.weak_min_samples_split,
446
+ )
447
+ )
448
+
449
+ if self.weak_cls_schedule >= 3:
450
+ for i in range(n_dims):
451
+ for j in range(i + 1, n_dims):
452
+ for k in range(j + 1, n_dims):
453
+ tasks.append(
454
+ (
455
+ [i, j, k],
456
+ shm_X.name,
457
+ shm_y.name,
458
+ shared_list,
459
+ n_records,
460
+ n_dims,
461
+ self.weak_cls_type,
462
+ self.weak_max_depth,
463
+ self.weak_min_samples_split,
464
+ )
465
+ )
466
+
467
+ with Pool(processes=num_workers) as pool:
468
+ results = pool.starmap(
469
+ self._train_weak_classifier_shm, tasks
470
+ )
471
+ pool.close()
472
+ pool.join()
473
+
474
+ for item in list(shared_list):
475
+ self.ind_list.append(item[0])
476
+ self.h_list.append(item[1])
477
+
478
+ shm_X.close()
479
+ shm_X.unlink()
480
+ shm_y.close()
481
+ shm_y.unlink()
482
+
483
+ def _infer_one_weak_classifier(self, cls_ind, X_subset):
484
+ return self.h_list[cls_ind].predict(X_subset)
485
+
486
+ def _infer_weak_classifiers(self, X):
487
+ n_classifiers = len(self.h_list)
488
+ num_workers = self.weak_cls_num_jobs
489
+ print(f"Using {num_workers} workers for inference.")
490
+
491
+ set_start_method("fork", force=True)
492
+
493
+ tasks = []
494
+ for i in range(n_classifiers):
495
+ tasks.append((i, X[:, self.ind_list[i]]))
496
+
497
+ with Pool(processes=num_workers) as pool:
498
+ results = pool.starmap(self._infer_one_weak_classifier, tasks)
499
+
500
+ return list(results)
501
+
502
+ def fit(self, X, y):
503
+ """
504
+ Build a QBoost classifier from the training set (X, y).
505
+
506
+ Parameters
507
+ ----------
508
+ X : {array-like, sparse matrix} of shape (n_samples, n_features)
509
+ The training input samples.
510
+
511
+ y : array-like of shape (n_samples,)
512
+ The target values.
513
+
514
+ Returns
515
+ -------
516
+ Response of Dirac-3 in JSON format.
517
+ """
518
+
519
+ assert X.shape[0] == y.shape[0], "Inconsistent sizes!"
520
+
521
+ assert set(y) == {-1, 1}, "Target values should be in {-1, 1}"
522
+
523
+ self.classes_ = set(y)
524
+
525
+ J, C, sum_constraint = self.get_hamiltonian(X, y)
526
+
527
+ assert J.shape[0] == J.shape[1], "Inconsistent hamiltonian size!"
528
+ assert J.shape[0] == C.shape[0], "Inconsistent hamiltonian size!"
529
+
530
+ self.set_model(J, C, sum_constraint)
531
+
532
+ sol, response = self.solve()
533
+
534
+ assert len(sol) == C.shape[0], "Inconsistent solution size!"
535
+
536
+ self.params = self.convert_sol_to_params(sol)
537
+
538
+ assert len(self.params) == len(self.h_list), "Inconsistent size!"
539
+
540
+ return response
541
+
542
+ def predict_raw(self, X: np.array):
543
+ """
544
+ Predict raw output of the classifier for input X.
545
+
546
+ Parameters
547
+ ----------
548
+ X : {array-like, sparse matrix} of shape (n_samples, n_features)
549
+
550
+ Returns
551
+ -------
552
+ y : ndarray of shape (n_samples,)
553
+ The predicted raw output of the classifier.
554
+ """
555
+
556
+ n_records = X.shape[0]
557
+ n_classifiers = len(self.h_list)
558
+
559
+ y = np.zeros(shape=(n_records), dtype=np.float32)
560
+ h_vals = np.array(
561
+ [
562
+ self.h_list[i].predict(X[:, self.ind_list[i]])
563
+ for i in range(n_classifiers)
564
+ ]
565
+ )
566
+
567
+ y = np.tensordot(self.params, h_vals, axes=(0, 0))
568
+
569
+ return y
570
+
571
+ def predict(self, X: np.array):
572
+ """
573
+ Predict classes for X.
574
+
575
+ Parameters
576
+ ----------
577
+ X : {array-like, sparse matrix} of shape (n_samples, n_features)
578
+
579
+ Returns
580
+ -------
581
+ y : ndarray of shape (n_samples,)
582
+ The predicted classes.
583
+ """
584
+
585
+ y = self.predict_raw(X)
586
+ y = np.sign(y)
587
+
588
+ return y
589
+
590
+ @timer
591
+ def get_hamiltonian(
592
+ self,
593
+ X: np.array,
594
+ y: np.array,
595
+ ):
596
+ X = np.array(X, dtype=np.float32)
597
+ y = np.array(y, dtype=np.float32)
598
+
599
+ if self.weak_cls_strategy == "multi_processing":
600
+ self._build_weak_classifiers_mp(X, y)
601
+ elif self.weak_cls_strategy == "multi_processing_shm":
602
+ self._build_weak_classifiers_shm(X, y)
603
+ elif self.weak_cls_strategy == "sequential":
604
+ self._build_weak_classifiers_sq(X, y)
605
+
606
+ print("Built %d weak classifiers!" % len(self.h_list))
607
+
608
+ n_classifiers = len(self.h_list)
609
+ n_records = X.shape[0]
610
+ h_vals = np.array(
611
+ [
612
+ self.h_list[i].predict(X[:, self.ind_list[i]])
613
+ for i in range(n_classifiers)
614
+ ]
615
+ )
616
+
617
+ J = np.tensordot(h_vals, h_vals, axes=(1, 1))
618
+ J += np.diag(self.lambda_coef * np.ones((n_classifiers)))
619
+ C = -2.0 * np.tensordot(h_vals, y, axes=(1, 0))
620
+
621
+ # J, C = get_hamiltonian_pyx(y, h_vals, self.lambda_coef, n_records)
622
+
623
+ C = C.reshape((n_classifiers, 1))
624
+
625
+ return J, C, 1.0
626
+
627
+ def convert_sol_to_params(self, sol):
628
+ return np.array(sol)
@@ -0,0 +1,83 @@
1
+ cdef extern from *:
2
+ """
3
+ #define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION
4
+ """
5
+
6
+ cdef extern from "stdlib.h":
7
+ void* malloc(size_t size)
8
+ void free(void* ptr)
9
+
10
+ cdef extern from "cvqboost_hamiltonian_c_func.c" nogil:
11
+ void get_hamiltonian_c(
12
+ float **J,
13
+ float *C,
14
+ float **h_vals,
15
+ float *y,
16
+ float lambda_coef,
17
+ int n_records,
18
+ int n_classifiers
19
+ ) nogil
20
+
21
+ import os
22
+ os.environ["OMP_PROC_BIND"] = "close"
23
+ os.environ["OMP_PLACES"] = "cores"
24
+
25
+ import numpy as np
26
+ cimport numpy as np
27
+
28
+ def get_hamiltonian_pyx(
29
+ np.ndarray[np.float32_t, ndim=1, mode="c"] y,
30
+ np.ndarray[np.float32_t, ndim=2, mode="c"] h_vals,
31
+ float lambda_coef,
32
+ int n_records
33
+ ):
34
+ cdef int n_classifiers = h_vals.shape[0]
35
+
36
+ h_vals = np.ascontiguousarray(h_vals, dtype=np.float32)
37
+ y = np.ascontiguousarray(y, dtype=np.float32)
38
+
39
+ # Allocate J and C as NumPy arrays
40
+ cdef np.ndarray[np.float32_t, ndim=2, mode="c"] J = np.zeros(
41
+ (n_classifiers, n_classifiers),
42
+ dtype=np.float32
43
+ )
44
+ cdef np.ndarray[np.float32_t, ndim=1, mode="c"] C = np.zeros(
45
+ (n_classifiers,),
46
+ dtype=np.float32
47
+ )
48
+
49
+ # Create a C-style array of pointers for J
50
+ cdef float** J_c = <float**>malloc(n_classifiers * sizeof(float*))
51
+ if not J_c:
52
+ raise MemoryError("Failed to allocate memory for J_c.")
53
+
54
+ # Create a C-style array of pointers for h_vals
55
+ cdef float** h_vals_c = <float**>malloc(n_classifiers * sizeof(float*))
56
+ if not h_vals_c:
57
+ free(J_c)
58
+ raise MemoryError("Failed to allocate memory for h_vals_c.")
59
+
60
+ with nogil:
61
+ for i in range(n_classifiers):
62
+ J_c[i] = &J[i, 0]
63
+ for i in range(n_classifiers):
64
+ h_vals_c[i] = &h_vals[i, 0]
65
+
66
+
67
+ # Call the C function without the GIL
68
+ with nogil:
69
+ get_hamiltonian_c(
70
+ J_c,
71
+ &C[0],
72
+ h_vals_c,
73
+ &y[0],
74
+ lambda_coef,
75
+ n_records,
76
+ n_classifiers
77
+ )
78
+
79
+ # Free allocated memory
80
+ free(h_vals_c)
81
+ free(J_c)
82
+
83
+ return J, C