eqc-models 0.9.8__py3-none-any.whl → 0.10.0__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 (68) hide show
  1. eqc_models-0.10.0.data/platlib/compile_extensions.py +67 -0
  2. {eqc_models-0.9.8.data → eqc_models-0.10.0.data}/platlib/eqc_models/assignment/setpartition.py +8 -29
  3. {eqc_models-0.9.8.data → eqc_models-0.10.0.data}/platlib/eqc_models/base/polyeval.c +127 -123
  4. {eqc_models-0.9.8.data → eqc_models-0.10.0.data}/platlib/eqc_models/base/polyeval.cpython-310-darwin.so +0 -0
  5. {eqc_models-0.9.8.data → eqc_models-0.10.0.data}/platlib/eqc_models/base/polynomial.py +84 -1
  6. eqc_models-0.10.0.data/platlib/eqc_models/base.py +115 -0
  7. eqc_models-0.10.0.data/platlib/eqc_models/combinatorics/setcover.py +93 -0
  8. eqc_models-0.10.0.data/platlib/eqc_models/communitydetection.py +25 -0
  9. eqc_models-0.10.0.data/platlib/eqc_models/eqcdirectsolver.py +61 -0
  10. {eqc_models-0.9.8.data → eqc_models-0.10.0.data}/platlib/eqc_models/graph/base.py +28 -17
  11. eqc_models-0.10.0.data/platlib/eqc_models/graph/partition.py +148 -0
  12. eqc_models-0.10.0.data/platlib/eqc_models/graphs.py +28 -0
  13. eqc_models-0.10.0.data/platlib/eqc_models/maxcut.py +113 -0
  14. eqc_models-0.10.0.data/platlib/eqc_models/maxkcut.py +185 -0
  15. eqc_models-0.10.0.data/platlib/eqc_models/ml/classifierqboost.py +628 -0
  16. eqc_models-0.10.0.data/platlib/eqc_models/ml/cvqboost_hamiltonian.pyx +83 -0
  17. eqc_models-0.10.0.data/platlib/eqc_models/ml/cvqboost_hamiltonian_c_func.c +68 -0
  18. eqc_models-0.10.0.data/platlib/eqc_models/ml/cvqboost_hamiltonian_c_func.h +14 -0
  19. eqc_models-0.10.0.data/platlib/eqc_models/quadraticmodel.py +131 -0
  20. {eqc_models-0.9.8.data → eqc_models-0.10.0.data}/platlib/eqc_models/sequence/tsp.py +38 -34
  21. eqc_models-0.10.0.data/platlib/eqc_models/solvers/eqcdirect.py +160 -0
  22. {eqc_models-0.9.8.data → eqc_models-0.10.0.data}/platlib/eqc_models/solvers/qciclient.py +46 -11
  23. {eqc_models-0.9.8.data → eqc_models-0.10.0.data}/platlib/eqc_models/utilities/polynomial.py +11 -0
  24. {eqc_models-0.9.8.dist-info → eqc_models-0.10.0.dist-info}/METADATA +3 -2
  25. eqc_models-0.10.0.dist-info/RECORD +65 -0
  26. {eqc_models-0.9.8.dist-info → eqc_models-0.10.0.dist-info}/WHEEL +1 -1
  27. eqc_models-0.9.8.data/platlib/compile_extensions.py +0 -23
  28. eqc_models-0.9.8.data/platlib/eqc_models/ml/classifierqboost.py +0 -423
  29. eqc_models-0.9.8.dist-info/RECORD +0 -52
  30. {eqc_models-0.9.8.data → eqc_models-0.10.0.data}/platlib/eqc_models/__init__.py +0 -0
  31. {eqc_models-0.9.8.data → eqc_models-0.10.0.data}/platlib/eqc_models/algorithms/__init__.py +0 -0
  32. {eqc_models-0.9.8.data → eqc_models-0.10.0.data}/platlib/eqc_models/algorithms/base.py +0 -0
  33. {eqc_models-0.9.8.data → eqc_models-0.10.0.data}/platlib/eqc_models/algorithms/penaltymultiplier.py +0 -0
  34. {eqc_models-0.9.8.data → eqc_models-0.10.0.data}/platlib/eqc_models/allocation/__init__.py +0 -0
  35. {eqc_models-0.9.8.data → eqc_models-0.10.0.data}/platlib/eqc_models/allocation/allocation.py +0 -0
  36. {eqc_models-0.9.8.data → eqc_models-0.10.0.data}/platlib/eqc_models/allocation/portbase.py +0 -0
  37. {eqc_models-0.9.8.data → eqc_models-0.10.0.data}/platlib/eqc_models/allocation/portmomentum.py +0 -0
  38. {eqc_models-0.9.8.data → eqc_models-0.10.0.data}/platlib/eqc_models/assignment/__init__.py +0 -0
  39. {eqc_models-0.9.8.data → eqc_models-0.10.0.data}/platlib/eqc_models/assignment/qap.py +0 -0
  40. {eqc_models-0.9.8.data → eqc_models-0.10.0.data}/platlib/eqc_models/base/__init__.py +0 -0
  41. {eqc_models-0.9.8.data → eqc_models-0.10.0.data}/platlib/eqc_models/base/base.py +0 -0
  42. {eqc_models-0.9.8.data → eqc_models-0.10.0.data}/platlib/eqc_models/base/constraints.py +0 -0
  43. {eqc_models-0.9.8.data → eqc_models-0.10.0.data}/platlib/eqc_models/base/operators.py +0 -0
  44. {eqc_models-0.9.8.data → eqc_models-0.10.0.data}/platlib/eqc_models/base/polyeval.pyx +0 -0
  45. {eqc_models-0.9.8.data → eqc_models-0.10.0.data}/platlib/eqc_models/base/quadratic.py +0 -0
  46. {eqc_models-0.9.8.data → eqc_models-0.10.0.data}/platlib/eqc_models/decoding.py +0 -0
  47. {eqc_models-0.9.8.data → eqc_models-0.10.0.data}/platlib/eqc_models/graph/__init__.py +0 -0
  48. {eqc_models-0.9.8.data → eqc_models-0.10.0.data}/platlib/eqc_models/graph/hypergraph.py +0 -0
  49. {eqc_models-0.9.8.data → eqc_models-0.10.0.data}/platlib/eqc_models/graph/maxcut.py +0 -0
  50. {eqc_models-0.9.8.data → eqc_models-0.10.0.data}/platlib/eqc_models/graph/maxkcut.py +0 -0
  51. {eqc_models-0.9.8.data → eqc_models-0.10.0.data}/platlib/eqc_models/ml/__init__.py +0 -0
  52. {eqc_models-0.9.8.data → eqc_models-0.10.0.data}/platlib/eqc_models/ml/classifierbase.py +0 -0
  53. {eqc_models-0.9.8.data → eqc_models-0.10.0.data}/platlib/eqc_models/ml/classifierqsvm.py +0 -0
  54. {eqc_models-0.9.8.data → eqc_models-0.10.0.data}/platlib/eqc_models/ml/clustering.py +0 -0
  55. {eqc_models-0.9.8.data → eqc_models-0.10.0.data}/platlib/eqc_models/ml/clusteringbase.py +0 -0
  56. {eqc_models-0.9.8.data → eqc_models-0.10.0.data}/platlib/eqc_models/ml/decomposition.py +0 -0
  57. {eqc_models-0.9.8.data → eqc_models-0.10.0.data}/platlib/eqc_models/ml/forecast.py +0 -0
  58. {eqc_models-0.9.8.data → eqc_models-0.10.0.data}/platlib/eqc_models/ml/forecastbase.py +0 -0
  59. {eqc_models-0.9.8.data → eqc_models-0.10.0.data}/platlib/eqc_models/ml/regressor.py +0 -0
  60. {eqc_models-0.9.8.data → eqc_models-0.10.0.data}/platlib/eqc_models/ml/regressorbase.py +0 -0
  61. {eqc_models-0.9.8.data → eqc_models-0.10.0.data}/platlib/eqc_models/ml/reservoir.py +0 -0
  62. {eqc_models-0.9.8.data → eqc_models-0.10.0.data}/platlib/eqc_models/sequence/__init__.py +0 -0
  63. {eqc_models-0.9.8.data → eqc_models-0.10.0.data}/platlib/eqc_models/solvers/__init__.py +0 -0
  64. {eqc_models-0.9.8.data → eqc_models-0.10.0.data}/platlib/eqc_models/utilities/__init__.py +0 -0
  65. {eqc_models-0.9.8.data → eqc_models-0.10.0.data}/platlib/eqc_models/utilities/fileio.py +0 -0
  66. {eqc_models-0.9.8.data → eqc_models-0.10.0.data}/platlib/eqc_models/utilities/qplib.py +0 -0
  67. {eqc_models-0.9.8.dist-info → eqc_models-0.10.0.dist-info}/LICENSE.txt +0 -0
  68. {eqc_models-0.9.8.dist-info → eqc_models-0.10.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,68 @@
1
+ #include <stdlib.h>
2
+ #include <stdio.h>
3
+ #include <string.h>
4
+ #include <omp.h>
5
+ #include "cvqboost_hamiltonian_c_func.h"
6
+
7
+ void get_hamiltonian_c(
8
+ float **J,
9
+ float *C,
10
+ float **h_vals,
11
+ float *y,
12
+ float lambda_coef,
13
+ int n_records,
14
+ int n_classifiers
15
+ ) {
16
+ float tmp_i, tmp_j;
17
+
18
+ // omp_set_num_threads(8);
19
+
20
+ /*
21
+ #pragma omp parallel
22
+ {
23
+ #pragma omp single
24
+ printf("Number of threads: %d\n", omp_get_num_threads());
25
+ }
26
+ */
27
+
28
+ double start = omp_get_wtime();
29
+ // Compute J matrix
30
+ //#pragma omp parallel for collapse(2) //schedule(static,10)
31
+ for (int i = 0; i < n_classifiers; i++) {
32
+ for (int j = 0; j < n_classifiers; j++) {
33
+ J[i][j] = 0.0f;
34
+
35
+ // Parallelized innermost loop
36
+ #pragma omp parallel for reduction(+:J[i][j]) schedule(dynamic, 10000)
37
+ for (int k = 0; k < n_records; k++) {
38
+ tmp_i = h_vals[i][k];
39
+ tmp_j = h_vals[j][k];
40
+ J[i][j] += tmp_i * tmp_j;
41
+ }
42
+ }
43
+ }
44
+ double end = omp_get_wtime();
45
+ printf("Time J loop: %f seconds\n", end - start);
46
+
47
+ // Compute C vector
48
+ start = omp_get_wtime();
49
+ //#pragma omp parallel for schedule(static,10)
50
+ for (int i = 0; i < n_classifiers; i++) {
51
+ C[i] = 0.0f;
52
+
53
+ // Parallelized innermost loop
54
+ #pragma omp parallel for reduction(+:C[i]) schedule(dynamic, 10000)
55
+ for (int m = 0; m < n_records; m++) {
56
+ tmp_i = -2.0f * y[m] * h_vals[i][m];
57
+ C[i] += tmp_i;
58
+ }
59
+ }
60
+ end = omp_get_wtime();
61
+ printf("Time C loop: %f seconds\n", end - start);
62
+
63
+ // Add lambda_coef to the diagonal of J
64
+ //#pragma omp parallel for schedule(static)
65
+ for (int i = 0; i < n_classifiers; i++) {
66
+ J[i][i] += lambda_coef;
67
+ }
68
+ }
@@ -0,0 +1,14 @@
1
+ #ifndef CVQBOOST_HAMILTONIAN_C_FUNC_H
2
+ #define CVQBOOST_HAMILTONIAN_C_FUNC_H
3
+
4
+ void get_hamiltonian_c(
5
+ float **J,
6
+ float *C,
7
+ float **h_vals,
8
+ float *y,
9
+ float lambda_coef,
10
+ int n_records,
11
+ int n_classifiers
12
+ );
13
+
14
+ #endif // CVQBOOST_HAMILTONIAN_C_FUNC_H
@@ -0,0 +1,131 @@
1
+ from typing import Tuple
2
+ import numpy as np
3
+ from .base import EqcModel
4
+
5
+ class QuadraticMixIn:
6
+ C = None
7
+ J = None
8
+
9
+ def encode(self, levels:int=200, norm_value:float=None, dtype=np.float32) -> np.ndarray:
10
+ """
11
+ Encode Hamiltonian into the domain of the device
12
+
13
+ The encoding method can be tuned using levels or norm_value. The parameter
14
+ levels and the member self.domains are used to generate a vector t such that
15
+ $$
16
+ x = ts
17
+ $$
18
+ thus,
19
+ $$
20
+ h^Tx + x^TJx = h^T(ts) + (ts)^TJ(ts) = (th)^Ts + s^T(t\\cross tJ)s
21
+ $$
22
+
23
+ """
24
+
25
+ n = max(self.domains.shape)
26
+ J = self.J
27
+ C = self.C
28
+ if norm_value is None:
29
+ max_level = levels - 1
30
+ multipliers = (self.domains) / max_level
31
+ J = np.array(np.outer(multipliers, multipliers) * J)
32
+ # h = np.multiply(h[:,0], multipliers)
33
+ C *= multipliers
34
+ C = C.reshape((n, 1))
35
+ else:
36
+ # normalize the operator
37
+ max_val_J = np.max(np.abs(J))
38
+ max_val_C = np.max(np.abs(C))
39
+ if max_val_J > max_val_C:
40
+ max_val = max_val_J
41
+ else:
42
+ max_val = max_val_C
43
+ C /= max_val
44
+ C *= norm_value
45
+ J /= max_val
46
+ J *= norm_value
47
+ # make J symmetric
48
+ J += J.T
49
+ J /= 2
50
+ # return operator in hJ format
51
+ H = np.hstack([C, J]).astype(dtype)
52
+
53
+ if self.machine_slacks > 0:
54
+ machine_slacks = self.machine_slacks
55
+ n = H.shape[0]
56
+ Hslack = np.zeros((n+machine_slacks, n+machine_slacks+1), dtype=dtype)
57
+ Hslack[:n, :n+1] = H
58
+ H = Hslack
59
+
60
+ return np.array(H)
61
+
62
+ @property
63
+ def sparse(self) -> Tuple[np.ndarray, np.ndarray]:
64
+ """ Put the linear and quadratic terms in a sparse format
65
+
66
+ :returns: coefficients : List, indices : List
67
+ """
68
+ C, J = self.H
69
+ n = self.n
70
+ indices = []
71
+ coefficients = []
72
+ # build a key (ordered tuple of indices) of length 2 for each element
73
+ for i in range(n):
74
+ if C[i,0] != 0:
75
+ key = (0, i+1)
76
+ indices.append(key)
77
+ coefficients.append(C[i,0])
78
+ # make J upper triangular
79
+ J = np.triu(J) + np.tril(J, -1).T
80
+ for i in range(n):
81
+ for j in range(i, n):
82
+ val = J[i, j]
83
+ if val != 0:
84
+ key = (i+1, j+1)
85
+ indices.append(key)
86
+ coefficients.append(val)
87
+ return np.array(coefficients, dtype=np.float32), np.array(indices, dtype=np.int32)
88
+
89
+ def evaluate(self, solution: np.ndarray, decode:bool=False, levels:int=200) -> float:
90
+ """
91
+ Evaluate the solution using the original operator. The decode
92
+ and levels parameters control the decoding of the solution. Without
93
+ specifying decode, the evaluation of the operator is done with the
94
+ solution provided.
95
+
96
+ """
97
+ H = self.H
98
+ h, J = H[:, 0], H[:, 1:]
99
+ if decode:
100
+ sol = self.decode(solution)
101
+ else:
102
+ sol = solution
103
+ return np.squeeze(sol.T@J@sol + h.T@sol)
104
+
105
+ class QuadraticModel(QuadraticMixIn, EqcModel):
106
+ """ Provides a quadratic operator and device sum constraint support """
107
+
108
+ def __init__(self, C : np.ndarray, J : np.ndarray, sum_constraint : float):
109
+ self._C = C
110
+ self._J = J
111
+ self._sum_constraint = sum_constraint
112
+
113
+ @property
114
+ def H(self):
115
+ return self._C, self._J
116
+
117
+ def check_constraint(self, solution: np.array) -> bool:
118
+ """ Evaluate the solution against the original sum constraint """
119
+ return np.sum(solution) == self.sum_constraint
120
+
121
+ @property
122
+ def sum_constraint(self) -> int:
123
+ """ Integer value which all qudits must sum to.
124
+ The value must be less than or equal to n * base for
125
+ the model to make sense. """
126
+
127
+ return self._sum_constraint
128
+
129
+ @sum_constraint.setter
130
+ def sum_constraint(self, value : int):
131
+ self._sum_constraint = value
@@ -87,14 +87,22 @@ class MTZTSPModel(InequalitiesMixin, TSPModel):
87
87
  1
88
88
  >>> model.distance(3, 2)
89
89
  3
90
- >>> solution = np.array([1, 0, 0, 1, 1, 0, 1, 2, 3, 4, 4, 2, 5])
90
+ >>> solution = np.array([1, 0, 0, 1, 1, 0, 1, 2, 3, 2, 2, 2, 5])
91
91
  >>> model.cost(solution)
92
92
  6
93
93
  >>> lhs, rhs = model.constraints
94
+ >>> lhs.shape
95
+ (10, 13)
96
+ >>> model.senses
97
+ ['EQ', 'EQ', 'EQ', 'EQ', 'EQ', 'EQ', 'LE', 'LE', 'LE', 'LE']
98
+ >>> rhs.shape
99
+ (10,)
94
100
  >>> (lhs@solution - rhs == 0).all()
95
101
  True
102
+ >>> model.upper_bound
103
+ array([1, 1, 1, 1, 1, 1, 2, 2, 2, 3, 3, 3, 3])
96
104
  >>> poly = model.polynomial
97
- >>> poly.evaluate(solution) + model.alpha * model.offset
105
+ >>> poly.evaluate(solution) + model.penalty_multiplier * model.offset
98
106
  6.0
99
107
  >>> infeasible = np.array([0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 4, 2, 5])
100
108
  >>> Pl, Pq = -2 * rhs.T@lhs, lhs.T@lhs
@@ -102,15 +110,15 @@ class MTZTSPModel(InequalitiesMixin, TSPModel):
102
110
  0.0
103
111
  >>> Pl.T@infeasible + infeasible.T@Pq@infeasible + model.offset > 0
104
112
  True
105
- >>> poly.evaluate(infeasible) + model.alpha * model.offset > 0
113
+ >>> poly.evaluate(infeasible) + model.penalty_multiplier * model.offset > 0
106
114
  True
107
- >>> infeasible = 1 - np.array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
108
- >>> poly.evaluate(infeasible) + model.alpha * model.offset > 6
115
+ >>> infeasible = np.array([1, 1, 0, 1, 0, 0, 0, 1, 2, 2, 2, 0, 3])
116
+ >>> poly.evaluate(infeasible) + model.penalty_multiplier * model.offset > 6
109
117
  True
110
- >>> val1 = poly.evaluate(infeasible) + model.alpha * model.offset
118
+ >>> val1 = poly.evaluate(infeasible) + model.penalty_multiplier * model.offset
111
119
  >>> model.penalty_multiplier *= 2
112
120
  >>> poly2 = model.polynomial
113
- >>> val2 = poly2.evaluate(infeasible) + model.alpha * model.offset
121
+ >>> val2 = poly2.evaluate(infeasible) + model.penalty_multiplier * model.offset
114
122
  >>> val1 < val2
115
123
  True
116
124
 
@@ -121,53 +129,54 @@ class MTZTSPModel(InequalitiesMixin, TSPModel):
121
129
  self.variables = variables = []
122
130
  coefficients = []
123
131
  indices = []
132
+ # choose a depot node
133
+ self.depot = depot = list(self.nodes)[0]
134
+ self.var_ub = var_ub = []
124
135
  for (u, v) in D.keys():
125
136
  varname = f"x_{u}_{v}"
126
137
  varidx = len(variables)
127
138
  variables.append(varname)
139
+ var_ub.append(1)
128
140
  indices.append((0, varidx+1))
129
141
  coefficients.append(D[(u, v)])
130
142
  for u in self.nodes:
131
143
  variables.append(f"s_{u}")
144
+ var_ub.append(self.N - 1)
132
145
  self.coefficients = coefficients
133
146
  self.indices = indices
134
147
  self.max_order = 2
148
+ # Build the constraints: Two constraints for every node, one for the
149
+ # edge chosen to enter and another for the edge chosen to leave.
150
+ # One constraint for every edge not leading to the depot.
135
151
 
136
- @property
137
- def constraints(self) -> Tuple[np.ndarray, np.ndarray]:
138
- """
139
- Build the constraints: Two constraints for every node, one for the
140
- edge chosen to enter and another for the edge chosen to leave.
141
- One constraint for every edge not leading to the depot.
142
-
143
- Returns: 2-Tuple of numpy arrays, one as lefthand side and the other
144
- for the righthand side. $Ax = b$
145
-
146
- """
147
- # choose a depot node
148
- depot = list(self.nodes)[0]
149
152
  depot_in = [uv for uv in self.edges if uv[-1] == depot]
150
153
  m = 2*self.N+len(self.edges) - len(depot_in)
151
- lhs = np.ndarray((m, self.n), dtype=np.int32)
152
- rhs = np.ndarray((m,), dtype=np.int32)
154
+ n = len(self.variables)
155
+ lhs = np.zeros((m, n), dtype=np.int64)
156
+ rhs = np.zeros((m,), dtype=np.int64)
153
157
  senses = ["EQ" for i in range(m)]
158
+ N = self.N
159
+ M = int(np.sqrt(N)) # N // 2
160
+ # scaling these coefficients
154
161
  for idx, node in enumerate(self.nodes):
155
- rhs[idx] = 1
156
- rhs[self.N + idx] = 1
162
+ rhs[idx] = M
163
+ rhs[self.N + idx] = M
157
164
  for (u, v) in self.edges:
158
165
  if v == node:
159
166
  varname = f"x_{u}_{v}"
160
167
  varidx = self.variables.index(varname)
161
- lhs[idx, varidx] = 1
168
+ lhs[idx, varidx] = M
162
169
  elif u == node:
163
170
  varname = f"x_{u}_{v}"
164
171
  varidx = self.variables.index(varname)
165
- lhs[self.N+idx, varidx] = 1
172
+ lhs[self.N+idx, varidx] = M
166
173
  # build these subtour elimination constraints
167
174
  # s_i - s_j + N x_{ij} <= N - 1
168
175
  idx = 0
169
176
  for (u, v) in self.edges:
170
177
  if v != depot:
178
+ varname = f"x_{u}_{v}"
179
+ varidx = self.variables.index(varname)
171
180
  senses[2*self.N + idx] = "LE"
172
181
  lhs[2*self.N + idx, varidx] = self.N - 1
173
182
  vidx = self.variables.index(f"s_{v}")
@@ -180,8 +189,6 @@ class MTZTSPModel(InequalitiesMixin, TSPModel):
180
189
  self.senses = senses
181
190
  self.lhs = lhs
182
191
  self.rhs = rhs
183
- # let the superclass handle the rest
184
- return super(MTZTSPModel, self).constraints
185
192
 
186
193
  def cost(self, solution : np.ndarray) -> float:
187
194
  """
@@ -206,12 +213,9 @@ class MTZTSPModel(InequalitiesMixin, TSPModel):
206
213
  def upper_bound(self) -> np.ndarray:
207
214
  """
208
215
  For all route variables, the domain is {0, 1}. The sequence variables
209
- can take on values in [0, 1, 2, ..., N].
216
+ can take on values in [0, 1, 2, ..., N-1].
210
217
 
211
218
  """
212
- # Since the sequence variables are all required, but the route variables
213
- # are not, just reference the count from the end to specify the sequence vars
214
- upper_bound = np.ones((len(self.variables),))
215
- upper_bound[-self.N:] = self.N
216
- return upper_bound
219
+ slacks = len([sense for sense in self.senses if sense != "EQ"])
220
+ return np.array(self.var_ub + [self.N for i in range(slacks)])
217
221
 
@@ -0,0 +1,160 @@
1
+ # (C) Quantum Computing Inc., 2024.
2
+ from typing import Dict
3
+ import logging
4
+ import numpy as np
5
+ from eqc_direct.client import EqcClient
6
+ from eqc_models.base.base import ModelSolver, EqcModel
7
+
8
+ log = logging.getLogger(name=__name__)
9
+
10
+
11
+ class EqcDirectMixin:
12
+ """
13
+ This class provides an instance method and property that
14
+ manage the direct connection to a QCi device.
15
+
16
+ """
17
+
18
+ ip_addr = None
19
+ port = None
20
+
21
+ def connect(self, ip_addr: str, port: str) -> str:
22
+ """
23
+ Explicitly set device address; if environment is
24
+ configured with the connection, this call is not required.
25
+
26
+ Parameters
27
+ ------------
28
+
29
+ ip_addr: The IP address of the device.
30
+
31
+ port: The port number of the device.
32
+
33
+ Parameters
34
+ ------------
35
+ The status.
36
+
37
+ """
38
+ self.ip_addr = ip_addr
39
+ self.port = port
40
+ client = self.client
41
+ return client.system_status()["status_desc"]
42
+
43
+ @property
44
+ def client(self):
45
+ params = {}
46
+ if self.ip_addr is not None:
47
+ params["ip_address"] = self.ip_addr
48
+ if self.port is not None:
49
+ params["port"] = self.port
50
+ return EqcClient(**params)
51
+
52
+
53
+ class EqcDirectSolver(ModelSolver, EqcDirectMixin):
54
+ """
55
+ This class provides an instance method for direct submission
56
+ of jobs to QCi devices.
57
+
58
+ """
59
+
60
+ def solve(
61
+ self,
62
+ model: EqcModel,
63
+ relaxation_schedule: int = 2,
64
+ precision: float = 1.0,
65
+ ) -> Dict:
66
+ """Parameters
67
+ -------------
68
+ model: An EqcModel instance.
69
+
70
+ relaxation_schedule: A predefined schedule indicator which
71
+ sets parameters on the device to control the sampling through
72
+ photon measurement; default is 2.
73
+
74
+ precision: A value which, when not None, indicates
75
+ the numerical precision desired in the solution: 1 for
76
+ integer, 0.1 for tenths place, 0.01 for hundreths and None for
77
+ raw; default is 1.0.
78
+
79
+ Returns
80
+ ---------
81
+ Json response from the solver.
82
+
83
+ """
84
+ poly_coefficients, poly_indices = model.sparse
85
+ # print(poly_indices)
86
+ if model.machine_slacks > 0:
87
+ # add a single 0 coefficient entry as the next-highest index
88
+ highest_idx = int(np.max(poly_indices))
89
+ # print("POLY HIGHEST", highest_idx)
90
+ for i in range(model.machine_slacks):
91
+ addtl_index = [0 for i in range(len(poly_indices[0]))]
92
+ addtl_index[-1] = highest_idx + i + 1
93
+ poly_indices = poly_indices.tolist() + [addtl_index]
94
+ poly_coefficients = poly_coefficients.tolist() + [0]
95
+ # print(poly_indices)
96
+ scval = model.sum_constraint
97
+
98
+ client = self.client
99
+ lock_id, start_ts, end_ts = client.wait_for_lock()
100
+ log.debug(
101
+ "Got device lock id %s. Wait time %f",
102
+ lock_id,
103
+ end_ts - start_ts,
104
+ )
105
+ resp = None
106
+ try:
107
+ log.debug(
108
+ "Calling device with parameters relaxation_schedule %d sum_constraint %s lock_id %s solution_precision %f",
109
+ relaxation_schedule,
110
+ scval,
111
+ lock_id,
112
+ precision,
113
+ )
114
+ resp = client.process_job(
115
+ poly_coefficients=poly_coefficients,
116
+ poly_indices=poly_indices,
117
+ relaxation_schedule=relaxation_schedule,
118
+ sum_constraint=scval,
119
+ lock_id=lock_id,
120
+ solution_precision=precision,
121
+ )
122
+ log.debug("Received response with status %s", resp["err_desc"])
123
+ log.debug(
124
+ "Runtime %f resulting in energy %f",
125
+ resp["runtime"],
126
+ resp["energy"],
127
+ )
128
+ log.debug(
129
+ "Distillation runtime %s resulting in energy %f",
130
+ resp["distilled_runtime"],
131
+ resp["distilled_energy"],
132
+ )
133
+ finally:
134
+ client.release_lock(lock_id=lock_id)
135
+ if resp is not None:
136
+ solution = resp["solution"]
137
+ energy = resp["energy"]
138
+ runtime = resp["runtime"]
139
+ dirac3_sol = np.array(solution)
140
+ log.debug(
141
+ "Energy %f Runtime %f Solution Size %i Solution Sum %f",
142
+ energy,
143
+ runtime,
144
+ len(dirac3_sol),
145
+ sum(dirac3_sol),
146
+ )
147
+ else:
148
+ raise RuntimeError("FAILED TO GET RESPONSE")
149
+ return resp
150
+
151
+
152
+ class Dirac3DirectSolver(EqcDirectSolver):
153
+
154
+ """
155
+ Naming this for when when other devices are available and have
156
+ different requirements. For instance, Dirac-3 requires the
157
+ summation constraint parameter, but others might not. The same
158
+ could be true for relaxation schedule.
159
+
160
+ """
@@ -5,6 +5,7 @@ import datetime
5
5
  import numpy as np
6
6
  from qci_client import QciClient
7
7
  from eqc_models.base.base import ModelSolver, EqcModel
8
+ from eqc_models.base.operators import OperatorNotAvailableError
8
9
 
9
10
  log = logging.getLogger(name=__name__)
10
11
 
@@ -69,6 +70,8 @@ class QciClientMixin:
69
70
 
70
71
  class Dirac1Mixin:
71
72
  sampler_type = "dirac-1"
73
+ requires_operator = "qubo"
74
+ max_upper_bound = 1
72
75
  job_params_names = ["num_samples", "alpha", "atol"]
73
76
 
74
77
  class QuboSolverMixin:
@@ -108,12 +111,15 @@ class Dirac3Mixin:
108
111
  """
109
112
 
110
113
  sampler_type = "dirac-3"
114
+ requires_operator = "polynomial"
115
+ # this restriction is based on the physical limit of Dirac-3 S1
116
+ max_upper_bound = 10000
111
117
  job_params_names = [
112
118
  "num_samples",
113
119
  "solution_precision",
114
120
  "relaxation_schedule",
115
121
  "mean_photon_number",
116
- "normalized_loss_rate",
122
+ "quantum_fluctuation_coefficient",
117
123
  ]
118
124
 
119
125
  def uploadJobFiles(self, client: QciClient, model: EqcModel):
@@ -262,6 +268,34 @@ class QciClientSolver(QciClientMixin, ModelSolver):
262
268
  def uploadJobFiles(self, client: QciClient, model: EqcModel):
263
269
  raise NotImplementedError("Subclass must override uploadJobFiles")
264
270
 
271
+ def checkModel(self, model):
272
+ """
273
+ Parameters
274
+ -------------
275
+
276
+ model: EqcModel
277
+ Instance of a model to validate against the solver requirements
278
+
279
+ This method raises an exception if the model supplied does not meet the requirements for the solver.
280
+ One of the validations is that the model supplies the operator that the solver uses. This is the
281
+ `qubo` operator for Dirac-1 and `polynomial` operator for Dirac-3. If the model does not supply
282
+ the operator, then the solver cannot accept it and the method will fail with an explanation.
283
+ Another validation is for the allowed upper bound of a model. For instance, solvers which only
284
+ handle binary variables can only accept models with variabes having an upper bound of 1.
285
+
286
+ """
287
+ try:
288
+ hasattr(model, self.requires_operator)
289
+ except OperatorNotAvailableError:
290
+ msg = (f"Class {model.__class__.__name__} does not provide a "
291
+ f"{self.requires_operator} operator")
292
+ raise ValueError(msg)
293
+ if np.max(model.upper_bound) > self.max_upper_bound:
294
+ msg = (f"Instance of {model.__class__.__name__} has greater "
295
+ f"upper bound on variables than {self.__class__.__name__} "
296
+ "supports")
297
+ raise ValueError(msg)
298
+
265
299
  def solve(
266
300
  self,
267
301
  model: EqcModel,
@@ -306,6 +340,7 @@ class QciClientSolver(QciClientMixin, ModelSolver):
306
340
 
307
341
  """
308
342
 
343
+ self.checkModel(model)
309
344
  job_config = {}
310
345
  job_config.update(
311
346
  {"num_samples": num_samples, "device_type": self.sampler_type}
@@ -439,7 +474,7 @@ class Dirac3CloudSolver(Dirac3Mixin, QciClientSolver):
439
474
  num_samples: int = 1,
440
475
  wait: bool = True,
441
476
  mean_photon_number: float = None,
442
- normalized_loss_rate: int = None,
477
+ quantum_fluctuation_coefficient: int = None,
443
478
  **job_kwargs,
444
479
  ):
445
480
  """
@@ -476,7 +511,7 @@ class Dirac3CloudSolver(Dirac3Mixin, QciClientSolver):
476
511
  Modify this value to control the relaxation schedule more
477
512
  precisely than the four presets given in schedules 1
478
513
  through 4. Allowed values are decimals between 0.1 and 2.
479
- normalized_loss_rate : int
514
+ quantum_fluctuation_coefficient: int
480
515
  an integer value which Sets the amount of loss introduced
481
516
  into the system for each loop during the measurement process.
482
517
  Modify this value to control the relaxation schedule more
@@ -494,7 +529,7 @@ class Dirac3CloudSolver(Dirac3Mixin, QciClientSolver):
494
529
  )
495
530
  job_kwargs["relaxation_schedule"] = relaxation_schedule
496
531
  job_kwargs["mean_photon_number"] = mean_photon_number
497
- job_kwargs["normalized_loss_rate"] = normalized_loss_rate
532
+ job_kwargs["quantum_fluctuation_coefficient"] = quantum_fluctuation_coefficient
498
533
  job_kwargs["solution_precision"] = solution_precision
499
534
  job_type = "sample-" + self.job_type
500
535
  return super().solve(
@@ -509,7 +544,7 @@ class Dirac3CloudSolver(Dirac3Mixin, QciClientSolver):
509
544
 
510
545
  else:
511
546
  job_kwargs["mean_photon_number"] = mean_photon_number
512
- job_kwargs["normalized_loss_rate"] = normalized_loss_rate
547
+ job_kwargs["quantum_fluctuation_coefficient"] = quantum_fluctuation_coefficient
513
548
  job_kwargs["relaxation_schedule"] = relaxation_schedule
514
549
  job_kwargs["num_levels"] = ub = [val + 1 for val in model.upper_bound.tolist()]
515
550
  job_type = "sample-" + self.job_type + "-integer"
@@ -551,7 +586,7 @@ class Dirac3IntegerCloudSolver(Dirac3Mixin, QciClientSolver):
551
586
  num_samples: int = 1,
552
587
  wait: bool = True,
553
588
  mean_photon_number: float = None,
554
- normalized_loss_rate: int = None,
589
+ quantum_fluctuation_coefficient: int = None,
555
590
  **job_kwargs,
556
591
  ):
557
592
  """
@@ -579,7 +614,7 @@ class Dirac3IntegerCloudSolver(Dirac3Mixin, QciClientSolver):
579
614
  Modify this value to control the relaxation schedule more
580
615
  precisely than the four presets given in schedules 1
581
616
  through 4. Allowed values are decimals between 0.1 and 2.
582
- normalized_loss_rate : int
617
+ quantum_fluctuation_coefficient: int
583
618
  an integer value which Sets the amount of loss introduced
584
619
  into the system for each loop during the measurement process.
585
620
  Modify this value to control the relaxation schedule more
@@ -601,7 +636,7 @@ class Dirac3IntegerCloudSolver(Dirac3Mixin, QciClientSolver):
601
636
  num_samples=num_samples,
602
637
  wait=wait,
603
638
  mean_photon_number=mean_photon_number,
604
- normalized_loss_rate=normalized_loss_rate,
639
+ quantum_fluctuation_coefficient=quantum_fluctuation_coefficient,
605
640
  relaxation_schedule=relaxation_schedule,
606
641
  num_levels=[val + 1 for val in model.upper_bound.tolist()],
607
642
  **job_kwargs,
@@ -638,7 +673,7 @@ class Dirac3ContinuousCloudSolver(Dirac3Mixin, QciClientSolver):
638
673
  num_samples: int = 1,
639
674
  wait: bool = True,
640
675
  mean_photon_number: float = None,
641
- normalized_loss_rate: int = None,
676
+ quantum_fluctuation_coefficient: int = None,
642
677
  **job_kwargs,
643
678
  ):
644
679
  """
@@ -673,7 +708,7 @@ class Dirac3ContinuousCloudSolver(Dirac3Mixin, QciClientSolver):
673
708
  Modify this value to control the relaxation schedule more
674
709
  precisely than the four presets given in schedules 1
675
710
  through 4. Allowed values are decimals between 0.1 and 2.
676
- normalized_loss_rate : int
711
+ quantum_fluctuation_coefficient: int
677
712
  an integer value which Sets the amount of loss introduced
678
713
  into the system for each loop during the measurement process.
679
714
  Modify this value to control the relaxation schedule more
@@ -701,7 +736,7 @@ class Dirac3ContinuousCloudSolver(Dirac3Mixin, QciClientSolver):
701
736
  sum_constraint=sum_constraint,
702
737
  relaxation_schedule=relaxation_schedule,
703
738
  mean_photon_number=mean_photon_number,
704
- normalized_loss_rate=normalized_loss_rate,
739
+ quantum_fluctuation_coefficient=quantum_fluctuation_coefficient,
705
740
  **job_kwargs,
706
741
  )
707
742