eqc-models 0.9.8__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 (52) hide show
  1. eqc_models-0.9.8.data/platlib/compile_extensions.py +23 -0
  2. eqc_models-0.9.8.data/platlib/eqc_models/__init__.py +15 -0
  3. eqc_models-0.9.8.data/platlib/eqc_models/algorithms/__init__.py +4 -0
  4. eqc_models-0.9.8.data/platlib/eqc_models/algorithms/base.py +10 -0
  5. eqc_models-0.9.8.data/platlib/eqc_models/algorithms/penaltymultiplier.py +169 -0
  6. eqc_models-0.9.8.data/platlib/eqc_models/allocation/__init__.py +6 -0
  7. eqc_models-0.9.8.data/platlib/eqc_models/allocation/allocation.py +367 -0
  8. eqc_models-0.9.8.data/platlib/eqc_models/allocation/portbase.py +128 -0
  9. eqc_models-0.9.8.data/platlib/eqc_models/allocation/portmomentum.py +137 -0
  10. eqc_models-0.9.8.data/platlib/eqc_models/assignment/__init__.py +5 -0
  11. eqc_models-0.9.8.data/platlib/eqc_models/assignment/qap.py +82 -0
  12. eqc_models-0.9.8.data/platlib/eqc_models/assignment/setpartition.py +170 -0
  13. eqc_models-0.9.8.data/platlib/eqc_models/base/__init__.py +72 -0
  14. eqc_models-0.9.8.data/platlib/eqc_models/base/base.py +150 -0
  15. eqc_models-0.9.8.data/platlib/eqc_models/base/constraints.py +276 -0
  16. eqc_models-0.9.8.data/platlib/eqc_models/base/operators.py +201 -0
  17. eqc_models-0.9.8.data/platlib/eqc_models/base/polyeval.c +11363 -0
  18. eqc_models-0.9.8.data/platlib/eqc_models/base/polyeval.cpython-310-darwin.so +0 -0
  19. eqc_models-0.9.8.data/platlib/eqc_models/base/polyeval.pyx +72 -0
  20. eqc_models-0.9.8.data/platlib/eqc_models/base/polynomial.py +274 -0
  21. eqc_models-0.9.8.data/platlib/eqc_models/base/quadratic.py +250 -0
  22. eqc_models-0.9.8.data/platlib/eqc_models/decoding.py +20 -0
  23. eqc_models-0.9.8.data/platlib/eqc_models/graph/__init__.py +5 -0
  24. eqc_models-0.9.8.data/platlib/eqc_models/graph/base.py +63 -0
  25. eqc_models-0.9.8.data/platlib/eqc_models/graph/hypergraph.py +307 -0
  26. eqc_models-0.9.8.data/platlib/eqc_models/graph/maxcut.py +155 -0
  27. eqc_models-0.9.8.data/platlib/eqc_models/graph/maxkcut.py +184 -0
  28. eqc_models-0.9.8.data/platlib/eqc_models/ml/__init__.py +15 -0
  29. eqc_models-0.9.8.data/platlib/eqc_models/ml/classifierbase.py +99 -0
  30. eqc_models-0.9.8.data/platlib/eqc_models/ml/classifierqboost.py +423 -0
  31. eqc_models-0.9.8.data/platlib/eqc_models/ml/classifierqsvm.py +237 -0
  32. eqc_models-0.9.8.data/platlib/eqc_models/ml/clustering.py +323 -0
  33. eqc_models-0.9.8.data/platlib/eqc_models/ml/clusteringbase.py +112 -0
  34. eqc_models-0.9.8.data/platlib/eqc_models/ml/decomposition.py +363 -0
  35. eqc_models-0.9.8.data/platlib/eqc_models/ml/forecast.py +255 -0
  36. eqc_models-0.9.8.data/platlib/eqc_models/ml/forecastbase.py +139 -0
  37. eqc_models-0.9.8.data/platlib/eqc_models/ml/regressor.py +220 -0
  38. eqc_models-0.9.8.data/platlib/eqc_models/ml/regressorbase.py +97 -0
  39. eqc_models-0.9.8.data/platlib/eqc_models/ml/reservoir.py +106 -0
  40. eqc_models-0.9.8.data/platlib/eqc_models/sequence/__init__.py +5 -0
  41. eqc_models-0.9.8.data/platlib/eqc_models/sequence/tsp.py +217 -0
  42. eqc_models-0.9.8.data/platlib/eqc_models/solvers/__init__.py +12 -0
  43. eqc_models-0.9.8.data/platlib/eqc_models/solvers/qciclient.py +707 -0
  44. eqc_models-0.9.8.data/platlib/eqc_models/utilities/__init__.py +6 -0
  45. eqc_models-0.9.8.data/platlib/eqc_models/utilities/fileio.py +38 -0
  46. eqc_models-0.9.8.data/platlib/eqc_models/utilities/polynomial.py +137 -0
  47. eqc_models-0.9.8.data/platlib/eqc_models/utilities/qplib.py +375 -0
  48. eqc_models-0.9.8.dist-info/LICENSE.txt +202 -0
  49. eqc_models-0.9.8.dist-info/METADATA +139 -0
  50. eqc_models-0.9.8.dist-info/RECORD +52 -0
  51. eqc_models-0.9.8.dist-info/WHEEL +5 -0
  52. eqc_models-0.9.8.dist-info/top_level.txt +2 -0
@@ -0,0 +1,72 @@
1
+ import numpy as np
2
+ cimport numpy as cnp
3
+ cnp.import_array()
4
+ fDTYPE = np.float64
5
+ ctypedef cnp.float64_t fDTYPE_t
6
+ iDTYPE = np.int64
7
+ ctypedef cnp.int64_t iDTYPE_t
8
+
9
+ def poly_eval(coeff, indices, solution):
10
+ cdef int i
11
+ cdef int n = 0
12
+ cdef cnp.ndarray values
13
+ # assert len(solution.shape) == 2, "Solution must be 2-d array"
14
+ # assert len(coeff.shape) == 1, "Coefficients must be 1-d array"
15
+ # assert len(indices.shape) == 2, "Inidices must be 2-d array"
16
+ # assert indices.shape[0] == coeff.shape[0], "Indices and coefficients must have the same first dimension"
17
+ if len(solution.shape) == 1:
18
+ n = solution.shape[0]
19
+ solution = solution.reshape((1, n))
20
+ # convert the solution to floating point instead of int
21
+ if solution.dtype == np.int64:
22
+ solution = solution.astype(np.float64)
23
+ values = np.zeros((solution.shape[0],))
24
+ # print(coeff.dtype, indices.dtype, solution.dtype)
25
+ for i in range(solution.shape[0]):
26
+ values[i] = poly_eval_c(coeff, indices, solution[i])
27
+ return np.squeeze(values)
28
+
29
+ cdef double poly_eval_c(cnp.ndarray[fDTYPE_t, ndim=1] coeff, cnp.ndarray[iDTYPE_t, ndim=2] indices,
30
+ cnp.ndarray[fDTYPE_t, ndim=1] solution):
31
+ # verify data types
32
+ # assert coeff.dtype == fDTYPE
33
+ # assert indices.dtype == iDTYPE
34
+ # check some bounds for memory safety
35
+ # assert indices.shape[0] == coeff.shape[0]
36
+ if np.max(indices) > solution.shape[0]:
37
+ raise ValueError("indices describe different size solution than provided")
38
+ elif np.min(indices) < 0:
39
+ raise ValueError("indices includes negative values, which must all be positive")
40
+
41
+ # streamline this code
42
+ # objective = 0
43
+ # for i in range(len(self.coefficients)):
44
+ # term = self.coefficients[i]
45
+ # for j in self.indices[i]:
46
+ # if j > 0:
47
+ # term *= solution[j-1]
48
+ # objective += term
49
+ # return objective
50
+ #
51
+
52
+ cdef int coeff_count = coeff.shape[0]
53
+ cdef int degree_count = indices.shape[1]
54
+ cdef double term = 0
55
+ cdef double ttl = 0
56
+ cdef int i = 0
57
+ cdef int j = 0
58
+ cdef double value = 0
59
+
60
+ # initialize terms, compute
61
+ for i in range(coeff_count):
62
+ term = coeff[i]
63
+ for j in range(degree_count):
64
+ if indices[i, j] > 0:
65
+ term *= solution[indices[i, j] - 1]
66
+ ttl += term
67
+
68
+ # collapse terms into first index and return
69
+ # for i in range(1, coeff_count):
70
+ # terms[0] += terms[i]
71
+
72
+ return ttl # terms[0]
@@ -0,0 +1,274 @@
1
+ # (C) Quantum Computing Inc., 2024.
2
+ from typing import Tuple, Union, List
3
+ import numpy as np
4
+ from eqc_models.base.base import EqcModel
5
+ from eqc_models.base.operators import Polynomial
6
+ from eqc_models.base.constraints import ConstraintsMixIn
7
+
8
+ class PolynomialMixin:
9
+ """This class provides an instance method and property that
10
+ manage polynomial models.
11
+ """
12
+
13
+ @property
14
+ def H(self) -> Tuple[np.ndarray, np.ndarray]:
15
+ """
16
+ Hamiltonian specified as a polynomial : coefficients, indices
17
+
18
+ indices are of the format [0, idx-1, ..., idx-d] which must be non-decreasing
19
+ and each idx-j is a 1-based index of the variable which is a power in the
20
+ term. For a polynomial where the highest degree is 3 and specifying a term
21
+ such as x_1x_2, the index array is [0, 1, 2]. Another example, x_1^2x_2 is
22
+ [1, 1, 2].
23
+
24
+ """
25
+ return self.coefficients, self.indices
26
+
27
+ @H.setter
28
+ def H(self, value : Tuple[np.ndarray, np.ndarray]):
29
+ """ Set H directly as coefficients, indices """
30
+
31
+ coefficients, indices = value
32
+ self.coefficients = coefficients
33
+ self.indices = indices
34
+
35
+ @property
36
+ def sparse(self) -> Tuple[np.ndarray, np.ndarray]:
37
+ return self.H
38
+
39
+ def evaluate(self, solution : np.ndarray) -> float:
40
+ """
41
+ Evaluate polynomial at solution
42
+
43
+ :solution: 1-d numpy array with the same length as the number of variables
44
+
45
+ returns a floating point value
46
+
47
+ """
48
+
49
+ value = self.polynomial.evaluate(np.array(solution))
50
+
51
+ return value
52
+
53
+ @property
54
+ def dynamic_range(self) -> float:
55
+ """
56
+ Dynamic range is a measure in decibels of the ratio of the largest
57
+ magnitude coefficient in a problem to the smallest non-zero magnitude
58
+ coefficient.
59
+
60
+ The possible range of values are all greater than or equal to 0. The
61
+ calculation is performed by finding the lowest non-zero of the
62
+ absolute value of all the coefficients, which could be empty. In that
63
+ case, the dynamic range is undefined, so an exception is raised. If
64
+ it is positive, then the maximum of the absolute values is divided
65
+ by the lowest. The base-10 logarithm of that value is taken and mul-
66
+ tiplied by 10. This is the dynamic range.
67
+
68
+ Returns
69
+ ----------
70
+
71
+ float
72
+
73
+ """
74
+ H = self.H
75
+ coefficients = np.array(H[0])
76
+ try:
77
+ lowest = np.min(np.abs(coefficients[coefficients!=0]))
78
+ except IndexError:
79
+ raise ValueError("Dynamic range of a Hamiltonian of all 0 is undefined")
80
+ highest = np.max(np.abs(coefficients))
81
+ return 10*np.log10(highest / lowest)
82
+
83
+ class PolynomialModel(PolynomialMixin, EqcModel):
84
+ """
85
+ Polynomial model base class.
86
+
87
+ Parameters
88
+ ------------
89
+ coefficients: An array of polynomial coeffients.
90
+ indices: An array of polynomial indices.
91
+
92
+ Examples
93
+ ------------
94
+
95
+ >>> coeffs = np.array([1, 2, 3])
96
+ >>> indices = np.array([[0, 0, 1], [0, 1, 1], [1, 1, 1]])
97
+ >>> from eqc_models.base.polynomial import PolynomialModel
98
+ >>> polynomial = PolynomialModel(coeffs, indices)
99
+ >>> solution = np.array([1, 1, 1])
100
+ >>> value = polynomial.evaluate(solution)
101
+ >>> int(value)
102
+ 6
103
+ >>> polynomial.H # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
104
+ (array([1, 2, 3]), array([[0, 0, 1],
105
+ [0, 1, 1],
106
+ [1, 1, 1]]))
107
+ """
108
+
109
+ def __init__(self, coefficients : Union[List, np.ndarray], indices : Union[List, np.ndarray]) -> None:
110
+ self.coefficients = coefficients
111
+ self.indices = indices
112
+
113
+ @property
114
+ def polynomial(self) -> Polynomial:
115
+ coefficients, indices = self.H
116
+ return Polynomial(coefficients=coefficients, indices=indices)
117
+
118
+ class ConstrainedPolynomialModel(ConstraintsMixIn, PolynomialModel):
119
+ """
120
+ Constrained Polynomial model base class.
121
+
122
+ Parameters
123
+ ------------
124
+ coefficients: An array of polynomial coeffients.
125
+ indices: An array of polynomial indices.
126
+ lhs: Left hand side of the linear constraints.
127
+ rhs: Right hand side of the linear constraints.
128
+
129
+ """
130
+
131
+ def __init__(self, coefficients : Union[List, np.ndarray], indices : Union[List, np.ndarray],
132
+ lhs : np.ndarray, rhs: np.ndarray):
133
+ self.coefficients = np.array(coefficients)
134
+ self.indices = np.array(indices).astype(np.int64)
135
+ self.max_order = self.indices.shape[1]
136
+ self.lhs = lhs
137
+ self.rhs = rhs
138
+
139
+ @property
140
+ def penalties(self):
141
+ """
142
+ Penalty terms specified as a polynomial: coefficients, indices
143
+
144
+ indices are of the format [0, idx-1, ..., idx-d] which must be non-decreasing
145
+ and each idx-j is a 1-based index of the variable which is a power in the
146
+ term. For a polynomial where the highest degree is 3 and specifying a term
147
+ such as x_1x_2, the index array is [0, 1, 2]. Another example, x_1^2x_2 is
148
+ [1, 1, 2].
149
+
150
+ Only linear equality constraints are supported. Translate Ax=b into
151
+ penalties using the superclass.
152
+ """
153
+
154
+ indices = []
155
+ coefficients = []
156
+ def lpad(index):
157
+ missing = self.max_order - len(index)
158
+ if missing > 0:
159
+ index = (0,) * missing + index
160
+ assert len(index) > 0
161
+ return np.array(index)
162
+ Pl, Pq = super(ConstrainedPolynomialModel, self).penalties
163
+ for i in range(Pl.shape[0]):
164
+ if Pl[i] != 0:
165
+ indices.append(lpad((0, i+1)))
166
+ coefficients.append(Pl[i])
167
+ for j in range(i, Pq.shape[1]):
168
+ if Pq[i, j] != 0:
169
+ indices.append(lpad((i+1, j+1)))
170
+ value = Pq[i, j]
171
+ if i!=j:
172
+ value += Pq[j, i]
173
+ coefficients.append(value)
174
+ return coefficients, indices
175
+
176
+ def evaluatePenalties(self, solution : np.ndarray, include_offset=False) -> float:
177
+ """
178
+ Take the polynomial form of the penalties from the penalties property
179
+ and evaluate the solution. The offset can be included by passing a True
180
+ value to the `include_offset` keyword argument.
181
+
182
+ Parameters
183
+ -----------
184
+
185
+ solution : np.ndarray
186
+ Solution to evaluate for a penalty value
187
+ include_offset : bool
188
+ Optional argument indicating whether or not to include the offset value.
189
+
190
+ Returns
191
+ ---------
192
+
193
+ Penalty value : float
194
+
195
+ Examples
196
+ ---------
197
+
198
+ >>> coeff = np.array([-1.0, -1.0])
199
+ >>> indices = np.array([(0, 1), (0, 2)])
200
+ >>> lhs = np.array([[1.0, 1.0]])
201
+ >>> rhs = np.array([1.0])
202
+ >>> model = ConstrainedPolynomialModel(coeff, indices, lhs, rhs)
203
+ >>> sol = np.array([1.0, 1.0])
204
+ >>> lhs@sol - rhs
205
+ array([1.])
206
+ >>> model.evaluatePenalties(sol)+model.offset
207
+ 1.0
208
+ >>> model.evaluatePenalties(sol)
209
+ 0.0
210
+ >>> model.evaluatePenalties(sol, include_offset=True)
211
+ 1.0
212
+
213
+ """
214
+
215
+ # get the coefficients and indices for the penalty polynomial
216
+ # use the Polynomial operator to evaluate the solution
217
+ coefficients, indices = self.penalties
218
+ solution = np.array(solution, dtype=np.float64)
219
+ polynomial = Polynomial(coefficients, indices)
220
+ if include_offset:
221
+ value = self.offset
222
+ else:
223
+ value = 0
224
+ value += polynomial.evaluate(solution)
225
+ return value
226
+
227
+ def evaluateObjective(self, solution : np.ndarray) -> float:
228
+ """
229
+ Take the polynomial coeff and indices from constructor and evalute the
230
+ solution with it.
231
+
232
+ Parameters
233
+ -----------
234
+
235
+ solution : np.ndarray
236
+ Soluttion to evaluate the objective value
237
+
238
+ Returns
239
+ --------
240
+
241
+ objective value : float
242
+
243
+ """
244
+ coefficients = self.coefficients
245
+ indices = self.indices
246
+ solution = np.array(solution, dtype=np.float64)
247
+ polynomial = Polynomial(coefficients, indices)
248
+ return polynomial.evaluate(solution)
249
+
250
+ @property
251
+ def H(self) -> Tuple[np.ndarray, np.ndarray]:
252
+ """ Provide the sparse format for the Hamiltonian """
253
+
254
+ p_coeff, p_indices = self.penalties
255
+ coefficients, indices = self.coefficients, self.indices
256
+ terms = {}
257
+ alpha = self.alpha
258
+ for index, coeff in zip(p_indices, p_coeff):
259
+ index = tuple(index)
260
+ assert len(index) > 1
261
+ if index not in terms:
262
+ terms[index] = alpha * coeff
263
+ else:
264
+ terms[index] += alpha * coeff
265
+ for index, coeff in zip(indices, coefficients):
266
+ index = tuple(index)
267
+ if index not in terms:
268
+ terms[index] = coeff
269
+ else:
270
+ terms[index] += coeff
271
+ indices = [index for index in terms.keys()]
272
+ indices.sort()
273
+ coefficients = [terms[tuple(index)] for index in indices]
274
+ return coefficients, indices
@@ -0,0 +1,250 @@
1
+ # (C) Quantum Computing Inc., 2024.
2
+ from typing import Tuple
3
+ import warnings
4
+ import numpy as np
5
+ from eqc_models.base.base import EqcModel
6
+ from eqc_models.base.constraints import ConstraintsMixIn
7
+ from eqc_models.base.operators import QUBO, Polynomial
8
+
9
+
10
+ class QuadraticMixIn:
11
+ """
12
+ This class provides an instance method and property that
13
+ manage quadratic models.
14
+
15
+ """
16
+
17
+ @property
18
+ def sparse(self) -> Tuple[np.ndarray, np.ndarray]:
19
+ """
20
+ Put the linear and quadratic terms in a sparse (poly) format
21
+
22
+ Returns
23
+ ----------
24
+
25
+ coefficients: List
26
+ indices: List
27
+
28
+ """
29
+ C, J = self.H
30
+ C = np.squeeze(C)
31
+ n = self.n
32
+ indices = []
33
+ coefficients = []
34
+ # build a key (ordered tuple of indices) of length 2 for each element
35
+ for i in range(n):
36
+ if C[i] != 0:
37
+ key = (0, i+1)
38
+ indices.append(key)
39
+ coefficients.append(C[i])
40
+ # make J upper triangular
41
+ J = np.triu(J) + np.tril(J, -1).T
42
+ for i in range(n):
43
+ for j in range(i, n):
44
+ val = J[i, j]
45
+ if val != 0:
46
+ key = (i+1, j+1)
47
+ indices.append(key)
48
+ coefficients.append(val)
49
+
50
+ return np.array(coefficients, dtype=np.float32), np.array(indices, dtype=np.int32)
51
+
52
+ @property
53
+ def polynomial(self) -> Polynomial:
54
+ coefficients, indices = self.sparse
55
+ return Polynomial(coefficients=coefficients, indices=indices)
56
+
57
+ def evaluate(self, solution: np.ndarray) -> float:
58
+ """
59
+ Evaluate the solution using the original operator.
60
+
61
+ """
62
+
63
+ sol = np.array(solution)
64
+ C, J = self.H
65
+ return np.squeeze(sol.T @ J @ sol + C.T @ sol)
66
+
67
+ @property
68
+ def dynamic_range(self) -> float:
69
+ """
70
+ Dynamic range is a measure in decibels of the ratio of the largest
71
+ magnitude coefficient in a problem to the smallest non-zero magnitude
72
+ coefficient.
73
+
74
+ The possible range of values are all greater than or equal to 0. The
75
+ calculation is performed by finding the lowest non-zero of the
76
+ absolute value of all the coefficients, which could be empty. The
77
+ values are in two arrays, so the minimum and maximum values of these
78
+ arrays are compared to each other. When there is no non-zero in either
79
+ of the arrays, then an exception is raised indicating that the dynamic
80
+ range of an operator of all zeros is undefined. If the lowest value is
81
+ positive, then the maximum of the absolute values is divided by the
82
+ lowest. The base-10 logarithm of that value is taken and multiplied by
83
+ 10. This is the dynamic range.
84
+
85
+ Returns
86
+ ----------
87
+
88
+ float
89
+
90
+ """
91
+ C, J = self.H
92
+ # if either C or J are all 0, then set min to very large value
93
+ try:
94
+ min_c = np.min(np.abs(C[C!=0]))
95
+ except IndexError:
96
+ min_c = 1e308
97
+ try:
98
+ min_j = np.min(np.abs(J[J!=0]))
99
+ except IndexError:
100
+ min_j = 1e308
101
+ max_c = np.max(np.abs(C))
102
+ max_j = np.max(np.abs(J))
103
+ lowest = min_c if min_c < min_j else min_j
104
+ highest = max_c if max_c > max_j else max_j
105
+ if lowest > highest:
106
+ raise ValueError("Dynamic range of a Hamiltonian of all 0 is undefined")
107
+ return 10*np.log10(highest / lowest)
108
+
109
+ @property
110
+ def qubo(self) -> QUBO:
111
+ """
112
+ Transform the model into QUBO form. Use `upper_bound` to determine
113
+ a log-encoding of the variables.
114
+
115
+ """
116
+ bin_n = 0
117
+ bits = []
118
+ #
119
+ C, J = self.H
120
+ # upper_bound is an array of the maximum values each variable can take
121
+ upper_bound = self.upper_bound
122
+ if np.sum(upper_bound)!=upper_bound.shape[0]:
123
+ for i in range(upper_bound.shape[0]):
124
+ bits.append(1+np.floor(np.log2(upper_bound[i])))
125
+ bin_n += bits[-1]
126
+ bin_n = int(bin_n)
127
+ Q = np.zeros((bin_n, bin_n), dtype=np.float32)
128
+ Q.shape
129
+ powers = [2**np.arange(bit_count) for bit_count in bits]
130
+ blocks = []
131
+ linear_blocks = []
132
+ for i in range(len(powers)):
133
+ # add the linear terms to the diagonal
134
+ linear_blocks.append(C[i]*powers[i])
135
+ row = []
136
+ for j in range(len(powers)):
137
+ mult = J[i,j]
138
+ block = np.outer(powers[i], powers[j])
139
+ block *= mult
140
+ row.append(block)
141
+ blocks.append(row)
142
+ Q[:, :] = np.block(blocks)
143
+ linear_operator = np.hstack(linear_blocks)
144
+ Q += np.diag(linear_operator)
145
+ else:
146
+ # in this case, the fomulation already has only binary variables
147
+ Q = np.zeros_like(J)
148
+ Q[:, :] = J
149
+ Q += np.diag(np.squeeze(C))
150
+
151
+ return QUBO(Q)
152
+
153
+ class QuadraticModel(QuadraticMixIn, EqcModel):
154
+ """
155
+ Provides a quadratic operator and device sum constraint support.
156
+
157
+ Parameters
158
+ -----------
159
+
160
+ J: Quadratic hamiltonian array.
161
+ C: Linear hamiltonian array.
162
+
163
+ Examples
164
+ ---------
165
+
166
+ >>> C = np.array([1, 2])
167
+ >>> J = np.array([[2, 1], [1, 2]])
168
+ >>> from eqc_models.base.quadratic import QuadraticModel
169
+ >>> model = QuadraticModel(C, J)
170
+ >>> model.upper_bound = np.array([1, 1])
171
+ >>> qubo = model.qubo
172
+ >>> qubo.Q # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
173
+ array([[3., 1.],
174
+ [1., 4.]])
175
+
176
+ """
177
+
178
+ def __init__(self, C: np.ndarray, J: np.ndarray):
179
+ self._H = C, J
180
+
181
+ class ConstrainedQuadraticModel(ConstraintsMixIn, QuadraticModel):
182
+ """
183
+ Provides a constrained quadratic operator and device sum constraint support.
184
+
185
+ Parameters
186
+ -----------
187
+
188
+ J: Quadratic hamiltonian array.
189
+ C: Linear hamiltonian array.
190
+ lhs: Left hand side of the linear constraints.
191
+ rhs: Right hand side of the linear constraints.
192
+
193
+ Examples
194
+ -------------------
195
+
196
+ >>> C = np.array([1, 2])
197
+ >>> J = np.array([[2, 1], [1, 2]])
198
+ >>> lhs = np.array([[1, 1], [1, 1]])
199
+ >>> rhs = np.array([1, 1])
200
+ >>> from eqc_models.base.quadratic import ConstrainedQuadraticModel
201
+ >>> model = ConstrainedQuadraticModel(C, J, lhs, rhs)
202
+ >>> model.penalty_multiplier = 1
203
+ >>> model.upper_bound = np.array([1, 1])
204
+ >>> qubo = model.qubo
205
+ >>> qubo.Q # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
206
+ array([[1., 3.],
207
+ [3., 2.]])
208
+
209
+ """
210
+
211
+ def __init__(self, C_obj: np.array, J_obj: np.array, lhs: np.array, rhs: np.array):
212
+ self.quad_objective = J_obj
213
+ self.linear_objective = C_obj
214
+ self.lhs = lhs
215
+ self.rhs = rhs
216
+ self._penalty_multiplier = None
217
+
218
+ @property
219
+ def H(self) -> Tuple[np.ndarray, np.ndarray]:
220
+ """
221
+ Return a pair of arrays, the linear and quadratic portions of a quadratic
222
+ operator that has the objective plus penalty functions multiplied by the
223
+ penalty multiplier. The linear terms are the first array and the quadratic
224
+ are the second.
225
+
226
+ Returns
227
+ -----------
228
+
229
+ `np.ndarray, np.nedarray`
230
+
231
+ """
232
+
233
+ C = self.linear_objective
234
+ J = self.quad_objective
235
+ pC, pJ = self.penalties
236
+ alpha = self.penalty_multiplier
237
+ return C + alpha * pC, J + alpha * pJ
238
+
239
+ @ConstraintsMixIn.penalty_multiplier.setter
240
+ def penalty_multiplier(self, value):
241
+ ConstraintsMixIn.penalty_multiplier.fset(self, value)
242
+
243
+ @property
244
+ def constraints(self):
245
+ return self.lhs, self.rhs
246
+
247
+ def evaluateObjective(self, solution: np.ndarray) -> float:
248
+ J = self.quad_objective
249
+ C = self.linear_objective
250
+ return np.squeeze(C.T @ solution + solution.T@J@solution)
@@ -0,0 +1,20 @@
1
+ # (C) Quantum Computing Inc., 2024.
2
+ import numpy as np
3
+
4
+ # setting EPSILON to the most precise value Dirac devices can currently achieve
5
+ EPSILON = 0.0001
6
+
7
+ class BisectionMixin:
8
+
9
+ def decode(self, solution):
10
+ """ Use a bisection method to determine the a binary solution from fractional values """
11
+ model = self.model
12
+
13
+ lower = np.min(solution)
14
+ upper = np.max(solution)
15
+
16
+ while upper - lower > EPSILON:
17
+ middle = (upper + lower) / 2
18
+ test_solution = (np.where(solution>=middle).astype(np.int32))
19
+ # test feasibility
20
+ # if model.is_feasible(test_solution):
@@ -0,0 +1,5 @@
1
+ # (C) Quantum Computing Inc., 2024.
2
+
3
+ from .maxcut import MaxCutModel
4
+
5
+ __all__ = ["MaxCutModel"]
@@ -0,0 +1,63 @@
1
+ # (C) Quantum Computing Inc., 2024.
2
+ from typing import List, Set
3
+ import networkx as nx
4
+ from ..base import QuadraticModel
5
+
6
+ class GraphModel(QuadraticModel):
7
+ """ """
8
+ def __init__(self, G : nx.Graph):
9
+ self.G = G
10
+ self.linear_objective, self.quad_objective = self.costFunction()
11
+
12
+ class NodeModel(GraphModel):
13
+ """
14
+ Base class for a model where the decision variables correspond to
15
+ the graph nodes.
16
+
17
+ """
18
+
19
+ @property
20
+ def variables(self) -> List[str]:
21
+ """ Provide a variable name to index lookup; order enforced by sorting the list before returning """
22
+ names = [node for node in self.G.nodes]
23
+ names.sort()
24
+ return names
25
+
26
+ def costFunction(self):
27
+ """
28
+ Parameters
29
+ -------------
30
+
31
+ None
32
+
33
+ Returns
34
+ --------------
35
+
36
+ :C: linear operator (vector array of coefficients) for cost function
37
+ :J: quadratic operator (N by N matrix array of coefficients ) for cost function
38
+
39
+ """
40
+ raise NotImplementedError("NodeModel does not implement costFunction")
41
+
42
+ def modularity(self, partition : Set[Set]) -> float:
43
+ """ Calculate modularity from a partition (set of communities) """
44
+
45
+ return nx.community.modularity(self.G, partition)
46
+
47
+ class TwoPartitionModel(NodeModel):
48
+ """
49
+ Base class for a generic graph paritioning model. Override the
50
+ cost function and evaluation methods to implement a two-partition
51
+ algorithm.
52
+
53
+ """
54
+
55
+ class EdgeModel(GraphModel):
56
+ """ Create a model where the variables are edge-based """
57
+
58
+ @property
59
+ def variables(self) -> List[str]:
60
+ """ Provide a variable name to index lookup; order enforced by sorting the list before returning """
61
+ names = [f"({u},{v})" for u, v in self.G.edges]
62
+ names.sort()
63
+ return names