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.
- eqc_models-0.9.8.data/platlib/compile_extensions.py +23 -0
- eqc_models-0.9.8.data/platlib/eqc_models/__init__.py +15 -0
- eqc_models-0.9.8.data/platlib/eqc_models/algorithms/__init__.py +4 -0
- eqc_models-0.9.8.data/platlib/eqc_models/algorithms/base.py +10 -0
- eqc_models-0.9.8.data/platlib/eqc_models/algorithms/penaltymultiplier.py +169 -0
- eqc_models-0.9.8.data/platlib/eqc_models/allocation/__init__.py +6 -0
- eqc_models-0.9.8.data/platlib/eqc_models/allocation/allocation.py +367 -0
- eqc_models-0.9.8.data/platlib/eqc_models/allocation/portbase.py +128 -0
- eqc_models-0.9.8.data/platlib/eqc_models/allocation/portmomentum.py +137 -0
- eqc_models-0.9.8.data/platlib/eqc_models/assignment/__init__.py +5 -0
- eqc_models-0.9.8.data/platlib/eqc_models/assignment/qap.py +82 -0
- eqc_models-0.9.8.data/platlib/eqc_models/assignment/setpartition.py +170 -0
- eqc_models-0.9.8.data/platlib/eqc_models/base/__init__.py +72 -0
- eqc_models-0.9.8.data/platlib/eqc_models/base/base.py +150 -0
- eqc_models-0.9.8.data/platlib/eqc_models/base/constraints.py +276 -0
- eqc_models-0.9.8.data/platlib/eqc_models/base/operators.py +201 -0
- eqc_models-0.9.8.data/platlib/eqc_models/base/polyeval.c +11363 -0
- eqc_models-0.9.8.data/platlib/eqc_models/base/polyeval.cpython-310-darwin.so +0 -0
- eqc_models-0.9.8.data/platlib/eqc_models/base/polyeval.pyx +72 -0
- eqc_models-0.9.8.data/platlib/eqc_models/base/polynomial.py +274 -0
- eqc_models-0.9.8.data/platlib/eqc_models/base/quadratic.py +250 -0
- eqc_models-0.9.8.data/platlib/eqc_models/decoding.py +20 -0
- eqc_models-0.9.8.data/platlib/eqc_models/graph/__init__.py +5 -0
- eqc_models-0.9.8.data/platlib/eqc_models/graph/base.py +63 -0
- eqc_models-0.9.8.data/platlib/eqc_models/graph/hypergraph.py +307 -0
- eqc_models-0.9.8.data/platlib/eqc_models/graph/maxcut.py +155 -0
- eqc_models-0.9.8.data/platlib/eqc_models/graph/maxkcut.py +184 -0
- eqc_models-0.9.8.data/platlib/eqc_models/ml/__init__.py +15 -0
- eqc_models-0.9.8.data/platlib/eqc_models/ml/classifierbase.py +99 -0
- eqc_models-0.9.8.data/platlib/eqc_models/ml/classifierqboost.py +423 -0
- eqc_models-0.9.8.data/platlib/eqc_models/ml/classifierqsvm.py +237 -0
- eqc_models-0.9.8.data/platlib/eqc_models/ml/clustering.py +323 -0
- eqc_models-0.9.8.data/platlib/eqc_models/ml/clusteringbase.py +112 -0
- eqc_models-0.9.8.data/platlib/eqc_models/ml/decomposition.py +363 -0
- eqc_models-0.9.8.data/platlib/eqc_models/ml/forecast.py +255 -0
- eqc_models-0.9.8.data/platlib/eqc_models/ml/forecastbase.py +139 -0
- eqc_models-0.9.8.data/platlib/eqc_models/ml/regressor.py +220 -0
- eqc_models-0.9.8.data/platlib/eqc_models/ml/regressorbase.py +97 -0
- eqc_models-0.9.8.data/platlib/eqc_models/ml/reservoir.py +106 -0
- eqc_models-0.9.8.data/platlib/eqc_models/sequence/__init__.py +5 -0
- eqc_models-0.9.8.data/platlib/eqc_models/sequence/tsp.py +217 -0
- eqc_models-0.9.8.data/platlib/eqc_models/solvers/__init__.py +12 -0
- eqc_models-0.9.8.data/platlib/eqc_models/solvers/qciclient.py +707 -0
- eqc_models-0.9.8.data/platlib/eqc_models/utilities/__init__.py +6 -0
- eqc_models-0.9.8.data/platlib/eqc_models/utilities/fileio.py +38 -0
- eqc_models-0.9.8.data/platlib/eqc_models/utilities/polynomial.py +137 -0
- eqc_models-0.9.8.data/platlib/eqc_models/utilities/qplib.py +375 -0
- eqc_models-0.9.8.dist-info/LICENSE.txt +202 -0
- eqc_models-0.9.8.dist-info/METADATA +139 -0
- eqc_models-0.9.8.dist-info/RECORD +52 -0
- eqc_models-0.9.8.dist-info/WHEEL +5 -0
- eqc_models-0.9.8.dist-info/top_level.txt +2 -0
|
@@ -0,0 +1,128 @@
|
|
|
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 warnings
|
|
9
|
+
from functools import wraps
|
|
10
|
+
import numpy as np
|
|
11
|
+
import pandas as pd
|
|
12
|
+
|
|
13
|
+
from ..base import ConstraintsMixIn
|
|
14
|
+
from eqc_models import QuadraticModel
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class PortBase(ConstraintsMixIn, QuadraticModel):
|
|
18
|
+
def __init__(
|
|
19
|
+
self,
|
|
20
|
+
stocks: list,
|
|
21
|
+
stock_data_dir: str,
|
|
22
|
+
adj_date: str,
|
|
23
|
+
lookback_days: int = 60,
|
|
24
|
+
):
|
|
25
|
+
self.stocks = stocks
|
|
26
|
+
self.data_dir = stock_data_dir
|
|
27
|
+
self.adj_date = adj_date
|
|
28
|
+
self.lookback_days = lookback_days
|
|
29
|
+
|
|
30
|
+
self._H = self.build()
|
|
31
|
+
|
|
32
|
+
def get_stock_returns(self, min_date, max_date):
|
|
33
|
+
stocks = self.stocks
|
|
34
|
+
min_date = pd.to_datetime(min_date)
|
|
35
|
+
max_date = pd.to_datetime(max_date)
|
|
36
|
+
|
|
37
|
+
return_df = None
|
|
38
|
+
for stock in stocks:
|
|
39
|
+
stock_df = pd.read_csv(
|
|
40
|
+
os.path.join(self.data_dir, "%s.csv" % stock)
|
|
41
|
+
)
|
|
42
|
+
stock_df["Date"] = stock_df["Date"].astype("datetime64[ns]")
|
|
43
|
+
stock_df = (
|
|
44
|
+
stock_df.fillna(method="ffill")
|
|
45
|
+
.fillna(method="bfill")
|
|
46
|
+
.fillna(0)
|
|
47
|
+
)
|
|
48
|
+
stock_df[stock] = stock_df[stock].pct_change()
|
|
49
|
+
stock_df = stock_df.dropna()
|
|
50
|
+
|
|
51
|
+
stock_df = stock_df[
|
|
52
|
+
(stock_df["Date"] >= min_date)
|
|
53
|
+
& (stock_df["Date"] <= max_date)
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
if return_df is None:
|
|
57
|
+
return_df = stock_df
|
|
58
|
+
else:
|
|
59
|
+
return_df = return_df.merge(
|
|
60
|
+
stock_df,
|
|
61
|
+
how="outer",
|
|
62
|
+
on="Date",
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
return_df = (
|
|
66
|
+
return_df.fillna(method="ffill")
|
|
67
|
+
.fillna(method="bfill")
|
|
68
|
+
.fillna(0)
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
return return_df
|
|
72
|
+
|
|
73
|
+
def get_hamiltonian(
|
|
74
|
+
self,
|
|
75
|
+
return_df,
|
|
76
|
+
min_date,
|
|
77
|
+
max_date,
|
|
78
|
+
):
|
|
79
|
+
pass
|
|
80
|
+
|
|
81
|
+
def build(self):
|
|
82
|
+
# Get dates
|
|
83
|
+
adj_date = pd.to_datetime(self.adj_date)
|
|
84
|
+
min_date = adj_date - datetime.timedelta(days=self.lookback_days)
|
|
85
|
+
max_date = adj_date - datetime.timedelta(days=1)
|
|
86
|
+
|
|
87
|
+
# Get return dataframe
|
|
88
|
+
return_df = self.get_stock_returns(min_date, max_date)
|
|
89
|
+
return_df = return_df.sort_values("Date")
|
|
90
|
+
return_df = return_df.fillna(method="ffill").fillna(0)
|
|
91
|
+
|
|
92
|
+
# Get and set hamiltonian
|
|
93
|
+
J, C, sum_constraint = self.get_hamiltonian(
|
|
94
|
+
return_df,
|
|
95
|
+
min_date,
|
|
96
|
+
max_date,
|
|
97
|
+
)
|
|
98
|
+
self._C = C
|
|
99
|
+
self._J = J
|
|
100
|
+
self._sum_constraint = sum_constraint
|
|
101
|
+
|
|
102
|
+
# Set domain
|
|
103
|
+
num_variables = C.shape[0]
|
|
104
|
+
self.upper_bound = sum_constraint * np.ones((num_variables,))
|
|
105
|
+
|
|
106
|
+
return C, J
|
|
107
|
+
|
|
108
|
+
def get_dynamic_range(self):
|
|
109
|
+
C = self._C
|
|
110
|
+
J = self._J
|
|
111
|
+
|
|
112
|
+
if C is None:
|
|
113
|
+
return
|
|
114
|
+
|
|
115
|
+
if J is None:
|
|
116
|
+
return
|
|
117
|
+
|
|
118
|
+
absc = np.abs(C)
|
|
119
|
+
absj = np.abs(J)
|
|
120
|
+
minc = np.min(absc[absc > 0])
|
|
121
|
+
maxc = np.max(absc)
|
|
122
|
+
minj = np.min(absj[absj > 0])
|
|
123
|
+
maxj = np.max(absj)
|
|
124
|
+
|
|
125
|
+
minval = min(minc, minj)
|
|
126
|
+
maxval = max(maxc, maxj)
|
|
127
|
+
|
|
128
|
+
return 10 * np.log10(maxval / minval)
|
|
@@ -0,0 +1,137 @@
|
|
|
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 warnings
|
|
9
|
+
from functools import wraps
|
|
10
|
+
import numpy as np
|
|
11
|
+
import pandas as pd
|
|
12
|
+
|
|
13
|
+
from .portbase import PortBase
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class PortMomentum(PortBase):
|
|
17
|
+
def __init__(
|
|
18
|
+
self,
|
|
19
|
+
stocks: list,
|
|
20
|
+
stock_data_dir: str,
|
|
21
|
+
adj_date: str,
|
|
22
|
+
lookback_days: int = 60,
|
|
23
|
+
window_days: int = 30,
|
|
24
|
+
window_overlap_days: int = 15,
|
|
25
|
+
weight_upper_limit: float = 0.08,
|
|
26
|
+
r_base: float = 0.05 / 365,
|
|
27
|
+
alpha: float = 5.0,
|
|
28
|
+
beta: float = 1.0,
|
|
29
|
+
xi: float = 1.0,
|
|
30
|
+
):
|
|
31
|
+
self.stocks = stocks
|
|
32
|
+
self.data_dir = stock_data_dir
|
|
33
|
+
self.adj_date = adj_date
|
|
34
|
+
self.lookback_days = lookback_days
|
|
35
|
+
|
|
36
|
+
self.window_days = window_days
|
|
37
|
+
self.window_overlap_days = window_overlap_days
|
|
38
|
+
self.weight_upper_limit = weight_upper_limit
|
|
39
|
+
self.r_base = r_base
|
|
40
|
+
self.alpha = alpha
|
|
41
|
+
self.beta = beta
|
|
42
|
+
self.xi = xi
|
|
43
|
+
|
|
44
|
+
self._H = self.build()
|
|
45
|
+
|
|
46
|
+
def get_hamiltonian(
|
|
47
|
+
self,
|
|
48
|
+
return_df,
|
|
49
|
+
min_date,
|
|
50
|
+
max_date,
|
|
51
|
+
):
|
|
52
|
+
stocks = self.stocks
|
|
53
|
+
xi = self.xi
|
|
54
|
+
window_days = self.window_days
|
|
55
|
+
window_overlap_days = self.window_overlap_days
|
|
56
|
+
weight_upper_limit = self.weight_upper_limit
|
|
57
|
+
|
|
58
|
+
# Set some params
|
|
59
|
+
K = len(stocks)
|
|
60
|
+
|
|
61
|
+
# Calculate Q and p_vec
|
|
62
|
+
Q = np.zeros(shape=(K, K), dtype=np.float32)
|
|
63
|
+
p_vec = np.zeros(shape=(K), dtype=np.float32)
|
|
64
|
+
|
|
65
|
+
m = 0
|
|
66
|
+
min_date = pd.to_datetime(min_date)
|
|
67
|
+
max_date = pd.to_datetime(max_date)
|
|
68
|
+
tmp_date = min_date
|
|
69
|
+
while tmp_date <= max_date:
|
|
70
|
+
tmp_min_date = tmp_date
|
|
71
|
+
tmp_max_date = tmp_date + datetime.timedelta(days=window_days)
|
|
72
|
+
tmp_df = return_df[
|
|
73
|
+
(return_df["Date"] >= tmp_min_date)
|
|
74
|
+
& (return_df["Date"] <= tmp_max_date)
|
|
75
|
+
]
|
|
76
|
+
|
|
77
|
+
r_list = []
|
|
78
|
+
for i in range(K):
|
|
79
|
+
r_list.append(np.array(tmp_df[stocks[i]]))
|
|
80
|
+
|
|
81
|
+
Q_tmp = np.cov(r_list)
|
|
82
|
+
for i in range(K):
|
|
83
|
+
p_vec[i] += -self.r_base * np.mean(r_list[i])
|
|
84
|
+
for j in range(K):
|
|
85
|
+
Q[i][j] += Q_tmp[i][j]
|
|
86
|
+
|
|
87
|
+
tmp_date += datetime.timedelta(
|
|
88
|
+
days=window_days - window_overlap_days,
|
|
89
|
+
)
|
|
90
|
+
m += 1
|
|
91
|
+
|
|
92
|
+
fct = m
|
|
93
|
+
if fct > 0:
|
|
94
|
+
fct = 1.0 / fct
|
|
95
|
+
|
|
96
|
+
p_vec = fct * p_vec
|
|
97
|
+
Q = fct * Q
|
|
98
|
+
|
|
99
|
+
# Calculate the Hamiltonian
|
|
100
|
+
J_no_limit = xi * Q
|
|
101
|
+
C_no_limit = p_vec
|
|
102
|
+
|
|
103
|
+
# make sure J is symmetric up to machine precision
|
|
104
|
+
J_no_limit = 0.5 * (J_no_limit + J_no_limit.transpose())
|
|
105
|
+
|
|
106
|
+
if weight_upper_limit is None:
|
|
107
|
+
return J_no_limit, C_no_limit, 100.0
|
|
108
|
+
|
|
109
|
+
W_max = 100.0 * weight_upper_limit
|
|
110
|
+
|
|
111
|
+
J = np.zeros(shape=(2 * K, 2 * K), dtype=np.float32)
|
|
112
|
+
C = np.zeros(shape=(2 * K), dtype=np.float32)
|
|
113
|
+
|
|
114
|
+
for i in range(K):
|
|
115
|
+
for j in range(K):
|
|
116
|
+
J[i][j] = J_no_limit[i][j] + self.alpha
|
|
117
|
+
|
|
118
|
+
J[i][i] += self.beta
|
|
119
|
+
J[i][i + K] += self.beta
|
|
120
|
+
J[i + K][i] += self.beta
|
|
121
|
+
J[i + K][i + K] += self.beta
|
|
122
|
+
C[i] = (
|
|
123
|
+
C_no_limit[i] - 200.0 * self.alpha - 2 * self.beta * W_max
|
|
124
|
+
)
|
|
125
|
+
C[i + K] = -2 * self.beta * W_max
|
|
126
|
+
|
|
127
|
+
C = C.reshape((C.shape[0], 1))
|
|
128
|
+
|
|
129
|
+
# Check hamiltonian dims
|
|
130
|
+
stocks = self.stocks
|
|
131
|
+
K = len(stocks)
|
|
132
|
+
|
|
133
|
+
assert J.shape[0] == K or J.shape[0] == 2 * K
|
|
134
|
+
assert J.shape[1] == K or J.shape[0] == 2 * K
|
|
135
|
+
assert C.shape[0] == K or C.shape[0] == 2 * K
|
|
136
|
+
|
|
137
|
+
return J, C, K * W_max
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# (C) Quantum Computing Inc., 2024.
|
|
2
|
+
import numpy as np
|
|
3
|
+
from eqc_models.base import ConstrainedQuadraticModel
|
|
4
|
+
|
|
5
|
+
class QAPModel(ConstrainedQuadraticModel):
|
|
6
|
+
"""
|
|
7
|
+
Parameters
|
|
8
|
+
----------
|
|
9
|
+
|
|
10
|
+
A : np.array
|
|
11
|
+
matrix of distances or costs between locations
|
|
12
|
+
B : np.array
|
|
13
|
+
matrix of flows between facilities
|
|
14
|
+
C : np.array
|
|
15
|
+
matrix pf fixed costs of assigning facilities to locations
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
Formulates a quadratic programming model from three inputs. The inputs are composed of:
|
|
19
|
+
a matrix which describes the unit cost of moving a single asset between locations. This
|
|
20
|
+
matrix can describe asynchronous costs that differ by direction of flow; a matrix which
|
|
21
|
+
describes the quantity of flow between facilities. This matrix describes the quantity
|
|
22
|
+
in the direction of flow; a matrix which desribes the fixed cost of assigning a facility
|
|
23
|
+
to a location.
|
|
24
|
+
|
|
25
|
+
Using these flows and costs, an optimization problem is formulated to determine a
|
|
26
|
+
solution which minimizes the total cost for positioning facilities at locations. The
|
|
27
|
+
facilities do not have to be buildings or campuses and the locations do not have to be
|
|
28
|
+
geographical locations. See Beckmann and and Koopmans (1957) for the original reference.
|
|
29
|
+
|
|
30
|
+
>>> A = np.array([[0, 5, 8, 0, 1],
|
|
31
|
+
... [0, 0, 0, 10, 15],
|
|
32
|
+
... [0, 0, 0, 13, 18],
|
|
33
|
+
... [0, 0, 0, 0, 0.],
|
|
34
|
+
... [0, 0, 0, 1, 0.]])
|
|
35
|
+
>>> B = np.array([[0, 8.54, 6.4, 10, 8.94],
|
|
36
|
+
... [8.54, 0, 4.47, 5.39, 6.49],
|
|
37
|
+
... [6.4, 4.47, 0, 3.61, 3.0],
|
|
38
|
+
... [10, 5.39, 3.61, 0, 2.0],
|
|
39
|
+
... [8.94, 6.49, 3.0, 2.0, 0.]])
|
|
40
|
+
>>> C = np.array([[2, 3, 6, 3, 7],
|
|
41
|
+
... [3, 9, 2, 5, 9],
|
|
42
|
+
... [2, 6, 4, 1, 2],
|
|
43
|
+
... [7, 5, 8, 5, 7],
|
|
44
|
+
... [1, 9, 2, 9, 2.]])
|
|
45
|
+
>>> model = QAPModel(A, B, C)
|
|
46
|
+
>>> model.penalty_multiplier = 105.625
|
|
47
|
+
>>> Q = model.qubo.Q
|
|
48
|
+
>>> np.sum(Q)
|
|
49
|
+
24318.03
|
|
50
|
+
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
def __init__(self, A : np.ndarray, B : np.ndarray, C: np.ndarray):
|
|
54
|
+
|
|
55
|
+
self.A = A
|
|
56
|
+
self.B = B
|
|
57
|
+
self.C = C
|
|
58
|
+
# N is the number of facilities (same as locations)
|
|
59
|
+
# n is the number of variables (N ** 2)
|
|
60
|
+
self.N = N = A.shape[0]
|
|
61
|
+
self.upper_bound = np.ones((N**2,), dtype=np.int64)
|
|
62
|
+
|
|
63
|
+
# objective
|
|
64
|
+
A = self.A
|
|
65
|
+
B = self.B
|
|
66
|
+
C = self.C
|
|
67
|
+
n = self.N ** 2
|
|
68
|
+
|
|
69
|
+
objective = np.kron(A, B) + np.diag(C.reshape((n, )))
|
|
70
|
+
objective += objective.T
|
|
71
|
+
objective /= 2.
|
|
72
|
+
self.quad_objective = objective
|
|
73
|
+
self.linear_objective = np.zeros((n,))
|
|
74
|
+
# G
|
|
75
|
+
m = 2 * self.N
|
|
76
|
+
G = np.zeros((m, n), dtype=np.float32)
|
|
77
|
+
for i in range(self.N):
|
|
78
|
+
G[i, i::self.N] = 1
|
|
79
|
+
G[self.N + i, i*self.N:(i+1)*self.N] = 1
|
|
80
|
+
h = np.ones((m,))
|
|
81
|
+
self.lhs = G
|
|
82
|
+
self.rhs = h
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
from typing import List, Tuple, Dict, Union
|
|
2
|
+
import numpy as np
|
|
3
|
+
from eqc_models.base import ConstraintsMixIn, PolynomialModel
|
|
4
|
+
|
|
5
|
+
class SetPartitionModel(ConstraintsMixIn, PolynomialModel):
|
|
6
|
+
"""
|
|
7
|
+
This class represents a set partitioning model for optimization problems that require selecting subsets
|
|
8
|
+
to partition a universal set while minimizing an objective function defined by weights. Given a collection
|
|
9
|
+
of subsets, a weight is assigned to each subset, and the goal is to determine an optimal selection of subsets
|
|
10
|
+
to fully cover the universal set with minimized total weight.
|
|
11
|
+
|
|
12
|
+
Parameters
|
|
13
|
+
----------
|
|
14
|
+
|
|
15
|
+
subsets : List of sets
|
|
16
|
+
List of sets (subsets) defining the collection to partition.
|
|
17
|
+
Each element in this list represents a subset containing elements from the universal set.
|
|
18
|
+
|
|
19
|
+
weights : List of floats
|
|
20
|
+
List of weights corresponding to each subset. The length of this list should be equal to
|
|
21
|
+
the number of subsets, with each weight indicating the cost or weight associated with selecting
|
|
22
|
+
a particular subset.
|
|
23
|
+
|
|
24
|
+
Attributes
|
|
25
|
+
----------
|
|
26
|
+
|
|
27
|
+
H : tuple of np.ndarray
|
|
28
|
+
Tuple containing the linear (h) and quadratic (J) coefficients for the Hamiltonian representation
|
|
29
|
+
of the quadratic problem formulation.
|
|
30
|
+
|
|
31
|
+
penalty_multiplier : float
|
|
32
|
+
Value for weighting the penalties formed from the equality constraints.
|
|
33
|
+
|
|
34
|
+
polynomial : Polynomial
|
|
35
|
+
Polynomial operator representation for the model, constructed from the penalty terms
|
|
36
|
+
to encode the set partition constraints.
|
|
37
|
+
|
|
38
|
+
linear_objective : np.ndarray
|
|
39
|
+
Array representing the linear objective function coefficients based on the weights of subsets.
|
|
40
|
+
|
|
41
|
+
quad_objective : np.ndarray
|
|
42
|
+
Quadratic objective function matrix initialized as zeros (no quadratic terms for objective).
|
|
43
|
+
|
|
44
|
+
constraints : tuple of np.ndarray
|
|
45
|
+
Matrix `A` and vector `b` representing constraints for the set partition problem:
|
|
46
|
+
- `A`: Binary matrix indicating subset membership of elements in the universal set.
|
|
47
|
+
- `b`: Vector of ones, enforcing full coverage of the universal set by the selected subsets.
|
|
48
|
+
|
|
49
|
+
universal_set : set
|
|
50
|
+
Set containing all unique elements present across the input subsets, representing the elements
|
|
51
|
+
that must be fully covered in the partition solution.
|
|
52
|
+
|
|
53
|
+
Example
|
|
54
|
+
--------
|
|
55
|
+
|
|
56
|
+
Given a list of subsets representing a set partition problem, each with an associated weight:
|
|
57
|
+
|
|
58
|
+
>>> import numpy as np
|
|
59
|
+
>>> np.random.seed(21)
|
|
60
|
+
>>> subsets = [{"A", "B", "C"}, {"D", "E", "F"}, {"A", "F"}, {"B", "E"}, {"C", "D"}, {"A"},
|
|
61
|
+
{"B"}, {"C", "D", "E"}, {"B", "C"}]
|
|
62
|
+
>>> weights = [100 * np.random.random() for _ in subsets]
|
|
63
|
+
|
|
64
|
+
We can construct and use the `SetPartitionModel` as follows:
|
|
65
|
+
|
|
66
|
+
>>> model = SetPartitionModel(subsets=subsets, weights=weights)
|
|
67
|
+
>>> solution = np.random.randint(0, 2, len(subsets)) # Random binary solution vector
|
|
68
|
+
>>> print("Objective Value:", model.evaluateObjective(solution)) # Evaluate solution cost
|
|
69
|
+
|
|
70
|
+
This approach builds the constraints matrix and penalties automatically, enabling efficient
|
|
71
|
+
optimization using solvers like `Dirac3CloudSolver`.
|
|
72
|
+
"""
|
|
73
|
+
def __init__(self, subsets: List[set], weights: List[float]) -> None:
|
|
74
|
+
# Combine subsets to form the universal set
|
|
75
|
+
self.universal_set = set()
|
|
76
|
+
for subset in subsets:
|
|
77
|
+
self.universal_set = self.universal_set.union(subset)
|
|
78
|
+
|
|
79
|
+
# Create the constraint matrix A and vector b
|
|
80
|
+
A = []
|
|
81
|
+
for x in self.universal_set:
|
|
82
|
+
row = [1 if x in subset else 0 for subset in subsets]
|
|
83
|
+
A.append(row)
|
|
84
|
+
A = np.array(A)
|
|
85
|
+
b = np.ones((A.shape[0],))
|
|
86
|
+
|
|
87
|
+
# Define penalty terms
|
|
88
|
+
J = A.T @ A
|
|
89
|
+
h = -2 * b.T @ A
|
|
90
|
+
n = h.shape[0]
|
|
91
|
+
h = h.reshape((n, 1))
|
|
92
|
+
offset = b.T @ b
|
|
93
|
+
|
|
94
|
+
# Assign the constraints to the mixin's properties
|
|
95
|
+
self.constraints = (A, b)
|
|
96
|
+
|
|
97
|
+
# Initialize PolynomialModel with J and h, properly formatted as indices and coefficients
|
|
98
|
+
indices, coefficients = self._construct_polynomial_terms(h, J)
|
|
99
|
+
super().__init__(coefficients, indices)
|
|
100
|
+
|
|
101
|
+
# Define the linear objective function based on subset weights
|
|
102
|
+
self.linear_objective = np.array(weights).reshape((n, 1))
|
|
103
|
+
self.quad_objective = np.zeros_like(J)
|
|
104
|
+
|
|
105
|
+
def _construct_polynomial_terms(self, h: np.ndarray, J: np.ndarray) -> Tuple[List[List[int]], List[np.ndarray]]:
|
|
106
|
+
"""
|
|
107
|
+
Constructs the polynomial terms (indices and coefficients) needed for the quadratic
|
|
108
|
+
Hamiltonian representation of the problem.
|
|
109
|
+
|
|
110
|
+
Parameters
|
|
111
|
+
----------
|
|
112
|
+
h : np.ndarray
|
|
113
|
+
Linear term of the penalty function as a 1D array.
|
|
114
|
+
|
|
115
|
+
J : np.ndarray
|
|
116
|
+
Quadratic term of the penalty function as a 2D array.
|
|
117
|
+
|
|
118
|
+
Returns
|
|
119
|
+
-------
|
|
120
|
+
Tuple[List[List[int]], List[float]]
|
|
121
|
+
A tuple where:
|
|
122
|
+
- The first element is a list of index lists, representing terms in polynomial format.
|
|
123
|
+
- The second element is a list of float coefficients corresponding to each term.
|
|
124
|
+
"""
|
|
125
|
+
indices = []
|
|
126
|
+
coefficients = []
|
|
127
|
+
|
|
128
|
+
# Linear terms
|
|
129
|
+
for i in range(h.shape[0]):
|
|
130
|
+
if h[i, 0] != 0:
|
|
131
|
+
indices.append([0, i + 1]) # 1-based index
|
|
132
|
+
coefficients.append(h[i, 0])
|
|
133
|
+
|
|
134
|
+
# Quadratic terms
|
|
135
|
+
for i in range(J.shape[0]):
|
|
136
|
+
for j in range(i, J.shape[1]):
|
|
137
|
+
if J[i, j] != 0:
|
|
138
|
+
indices.append([i + 1, j + 1]) # 1-based indices
|
|
139
|
+
coefficients.append(J[i, j])
|
|
140
|
+
|
|
141
|
+
return indices, coefficients
|
|
142
|
+
|
|
143
|
+
@property
|
|
144
|
+
def H(self) -> Tuple[np.ndarray, np.ndarray]:
|
|
145
|
+
"""
|
|
146
|
+
Hamiltonian representation as a polynomial, returning the coefficients and indices.
|
|
147
|
+
|
|
148
|
+
Returns
|
|
149
|
+
-------
|
|
150
|
+
Tuple[np.ndarray, np.ndarray]
|
|
151
|
+
Tuple where the first element is an array of coefficients, and the second element
|
|
152
|
+
is an array of indices, each representing polynomial terms for the Hamiltonian.
|
|
153
|
+
"""
|
|
154
|
+
return self.coefficients, self.indices
|
|
155
|
+
|
|
156
|
+
def evaluateObjective(self, solution: np.ndarray) -> float:
|
|
157
|
+
"""
|
|
158
|
+
Evaluate the objective function by calculating the weighted sum of selected subsets.
|
|
159
|
+
|
|
160
|
+
Parameters
|
|
161
|
+
----------
|
|
162
|
+
solution : np.ndarray
|
|
163
|
+
Binary array where each element indicates if a subset is selected (1) or not (0).
|
|
164
|
+
|
|
165
|
+
Returns
|
|
166
|
+
-------
|
|
167
|
+
float
|
|
168
|
+
The value of the objective function, representing the total weight of selected subsets.
|
|
169
|
+
"""
|
|
170
|
+
return float(np.squeeze(solution).T @ np.squeeze(self.linear_objective))
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# (C) Quantum Computing Inc., 2024.
|
|
2
|
+
"""
|
|
3
|
+
This subpackage contains the building blocks for formulating and solving models
|
|
4
|
+
with EQC devices. Many well known models in the space of quantum computing use
|
|
5
|
+
quadratic operators. Two generally accepted formats for quadratic models are
|
|
6
|
+
Ising Hamiltonian and QUBO operators. Ising models are not explicitly supported
|
|
7
|
+
because of the simple equivalence to QUBO models. Converters from Ising to QUBO
|
|
8
|
+
are readily available and may be added as a utility function in the future, but
|
|
9
|
+
for now are left out of the package. Other quadratic models, such as integer
|
|
10
|
+
or floating point models are supported using the domain and upper bound of the
|
|
11
|
+
model variables and the solver choice.
|
|
12
|
+
|
|
13
|
+
Polynomial models are used to support higher order interactions between
|
|
14
|
+
variables. These models are defined over non-negative variables with the
|
|
15
|
+
highest power interaction being the only restriction on variable interactions.
|
|
16
|
+
This is also known as all-to-all connectivity.
|
|
17
|
+
|
|
18
|
+
Subpackages
|
|
19
|
+
-----------
|
|
20
|
+
|
|
21
|
+
constraints
|
|
22
|
+
This module defines support for linear equality and inequality constraints.
|
|
23
|
+
It can be paired with the penalty multiplier algorithm to choose multiplier
|
|
24
|
+
values which satisfy the enforcement of constraints as penalties. The mixin
|
|
25
|
+
pattern is used to allow more complex classes to be built from these bases.
|
|
26
|
+
|
|
27
|
+
- ConstraintMixIn
|
|
28
|
+
- ConstraintModel
|
|
29
|
+
- InequalitiesMixIn
|
|
30
|
+
- InequalityConstraintModel
|
|
31
|
+
|
|
32
|
+
quadratic
|
|
33
|
+
This module defines the methods required to convert a linear vector and
|
|
34
|
+
quadratic matrix into the required operator, either QUBO or Polynomial, for
|
|
35
|
+
solving with EQC. The support for constraints is also incorporated.
|
|
36
|
+
|
|
37
|
+
- ConstrainedQuadraticModel
|
|
38
|
+
- QuadraticModel
|
|
39
|
+
|
|
40
|
+
polynomial
|
|
41
|
+
This module defines the classes and methods for conversion from arrays of
|
|
42
|
+
coefficients and indices to operators for solving with EQC. The support for
|
|
43
|
+
constriants is also incorporated.
|
|
44
|
+
|
|
45
|
+
- PolynomialModel
|
|
46
|
+
- ConstrainedPolynomialModel
|
|
47
|
+
|
|
48
|
+
base
|
|
49
|
+
This module defines abstract base classes for models and solvers.
|
|
50
|
+
|
|
51
|
+
- EqcModel
|
|
52
|
+
- ModelSolver
|
|
53
|
+
|
|
54
|
+
operators
|
|
55
|
+
This module defines the operator types to use for passing problems to EQC
|
|
56
|
+
devices.
|
|
57
|
+
|
|
58
|
+
- Polynomial
|
|
59
|
+
- QUBO
|
|
60
|
+
|
|
61
|
+
"""
|
|
62
|
+
from .constraints import (ConstraintModel, ConstraintsMixIn, InequalitiesMixin,
|
|
63
|
+
InequalityConstraintModel)
|
|
64
|
+
from .quadratic import (ConstrainedQuadraticModel, QuadraticModel)
|
|
65
|
+
from .polynomial import (PolynomialModel, ConstrainedPolynomialModel)
|
|
66
|
+
from .base import (ModelSolver, EqcModel)
|
|
67
|
+
from .operators import (QUBO, Polynomial)
|
|
68
|
+
|
|
69
|
+
__all__ = ["ConstraintsMixIn", "ConstraintModel", "ConstrainedQuadraticModel",
|
|
70
|
+
"QuadraticModel", "PolynomialModel", "ConstrainedPolynomialModel",
|
|
71
|
+
"InequalitiesMixin", "InequalityConstraintModel",
|
|
72
|
+
"EqcModel", "ModelSolver", "QUBO", "Polynomial"]
|