eqc-models 0.12.0__py3-none-any.whl → 0.14.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.
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/base/base.py +11 -0
- eqc_models-0.14.0.data/platlib/eqc_models/base/binaries.py +33 -0
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/base/constraints.py +39 -0
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/base/polyeval.c +443 -267
- eqc_models-0.14.0.data/platlib/eqc_models/base/polyeval.cpython-310-darwin.so +0 -0
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/base/polynomial.py +21 -2
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/base/results.py +81 -1
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/graph/__init__.py +2 -0
- eqc_models-0.14.0.data/platlib/eqc_models/graph/rcshortestpath.py +81 -0
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/graph/shortestpath.py +52 -19
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/ml/classifierbase.py +0 -5
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/ml/classifierqboost.py +115 -72
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/ml/clustering.py +29 -1
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/ml/clusteringbase.py +29 -7
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/ml/decomposition.py +50 -11
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/ml/regressor.py +14 -0
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/ml/regressorbase.py +24 -6
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/ml/reservoir.py +17 -2
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/solvers/__init__.py +9 -1
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/solvers/eqcdirect.py +27 -6
- eqc_models-0.14.0.data/platlib/eqc_models/solvers/mip.py +115 -0
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/solvers/qciclient.py +11 -22
- eqc_models-0.14.0.data/platlib/eqc_models/solvers/responselog.py +47 -0
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/utilities/__init__.py +2 -1
- eqc_models-0.14.0.data/platlib/eqc_models/utilities/general.py +83 -0
- {eqc_models-0.12.0.dist-info → eqc_models-0.14.0.dist-info}/METADATA +10 -9
- eqc_models-0.14.0.dist-info/RECORD +70 -0
- eqc_models-0.12.0.data/platlib/eqc_models/base/polyeval.cpython-310-darwin.so +0 -0
- eqc_models-0.12.0.dist-info/RECORD +0 -65
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/compile_extensions.py +0 -0
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/__init__.py +0 -0
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/algorithms/__init__.py +0 -0
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/algorithms/base.py +0 -0
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/algorithms/penaltymultiplier.py +0 -0
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/allocation/__init__.py +0 -0
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/allocation/allocation.py +0 -0
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/allocation/portbase.py +0 -0
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/allocation/portmomentum.py +0 -0
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/assignment/__init__.py +0 -0
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/assignment/qap.py +0 -0
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/assignment/resource.py +0 -0
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/assignment/setpartition.py +0 -0
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/base/__init__.py +0 -0
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/base/operators.py +0 -0
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/base/polyeval.pyx +0 -0
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/base/quadratic.py +0 -0
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/combinatorics/__init__.py +0 -0
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/combinatorics/setcover.py +0 -0
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/combinatorics/setpartition.py +0 -0
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/decoding.py +0 -0
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/graph/base.py +0 -0
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/graph/hypergraph.py +0 -0
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/graph/maxcut.py +0 -0
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/graph/maxkcut.py +0 -0
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/graph/partition.py +0 -0
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/ml/__init__.py +0 -0
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/ml/classifierqsvm.py +0 -0
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/ml/cvqboost_hamiltonian.pyx +0 -0
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/ml/cvqboost_hamiltonian_c_func.c +0 -0
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/ml/cvqboost_hamiltonian_c_func.h +0 -0
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/ml/forecast.py +0 -0
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/ml/forecastbase.py +0 -0
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/process/base.py +0 -0
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/process/mpc.py +0 -0
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/sequence/__init__.py +0 -0
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/sequence/tsp.py +0 -0
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/utilities/fileio.py +0 -0
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/utilities/polynomial.py +0 -0
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/utilities/qplib.py +0 -0
- {eqc_models-0.12.0.dist-info → eqc_models-0.14.0.dist-info}/WHEEL +0 -0
- {eqc_models-0.12.0.dist-info → eqc_models-0.14.0.dist-info}/licenses/LICENSE.txt +0 -0
- {eqc_models-0.12.0.dist-info → eqc_models-0.14.0.dist-info}/top_level.txt +0 -0
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
# (C) Quantum Computing Inc., 2024.
|
|
2
2
|
from typing import Tuple, Union, List
|
|
3
|
+
import logging
|
|
3
4
|
import numpy as np
|
|
4
5
|
from eqc_models.base.base import EqcModel
|
|
5
6
|
from eqc_models.base.operators import Polynomial, QUBO, OperatorNotAvailableError
|
|
6
7
|
from eqc_models.base.constraints import ConstraintsMixIn
|
|
7
8
|
|
|
9
|
+
log = logging.getLogger(name=__name__)
|
|
10
|
+
|
|
8
11
|
class PolynomialMixin:
|
|
9
12
|
"""This class provides an instance method and property that
|
|
10
13
|
manage polynomial models.
|
|
@@ -46,7 +49,12 @@ class PolynomialMixin:
|
|
|
46
49
|
|
|
47
50
|
"""
|
|
48
51
|
|
|
49
|
-
|
|
52
|
+
coefficients, indices = self.H
|
|
53
|
+
log.debug("Coefficients (%d): %s", len(coefficients), coefficients)
|
|
54
|
+
log.debug("Indices (%d): %s", len(indices), indices)
|
|
55
|
+
polynomial = self.polynomial
|
|
56
|
+
log.debug("Polynomial: %s", polynomial)
|
|
57
|
+
value = polynomial.evaluate(np.array(solution))
|
|
50
58
|
|
|
51
59
|
return value
|
|
52
60
|
|
|
@@ -107,8 +115,19 @@ class PolynomialModel(PolynomialMixin, EqcModel):
|
|
|
107
115
|
"""
|
|
108
116
|
|
|
109
117
|
def __init__(self, coefficients : Union[List, np.ndarray], indices : Union[List, np.ndarray]) -> None:
|
|
118
|
+
# ensure that the coefficients are ordered and not duplicated
|
|
119
|
+
new_coefficients = {}
|
|
120
|
+
for idx, v in zip(indices, coefficients):
|
|
121
|
+
idx = tuple(idx)
|
|
122
|
+
if idx in new_coefficients:
|
|
123
|
+
new_coefficients[idx] += v
|
|
124
|
+
else:
|
|
125
|
+
new_coefficients[idx] = v
|
|
126
|
+
new_indices = [idx for idx in new_coefficients]
|
|
127
|
+
new_indices.sort()
|
|
128
|
+
coefficients = [new_coefficients[idx] for idx in new_indices]
|
|
110
129
|
self.coefficients = coefficients
|
|
111
|
-
self.indices =
|
|
130
|
+
self.indices = new_indices
|
|
112
131
|
|
|
113
132
|
@property
|
|
114
133
|
def polynomial(self) -> Polynomial:
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import dataclasses
|
|
2
|
+
from typing import Dict
|
|
2
3
|
import warnings
|
|
3
4
|
import numpy as np
|
|
4
5
|
|
|
@@ -41,6 +42,14 @@ class SolutionResults:
|
|
|
41
42
|
device : str
|
|
42
43
|
String that represents the device used to solve the model.
|
|
43
44
|
|
|
45
|
+
raw_solutions : np.ndarray
|
|
46
|
+
Numpy array of the solutions as returned by the solver device.
|
|
47
|
+
|
|
48
|
+
calibration_time : float
|
|
49
|
+
Total time spend during job exectution where the device performed calibration.
|
|
50
|
+
The calibration is not directly affected by the job submission and the time
|
|
51
|
+
is not included in run_time.
|
|
52
|
+
|
|
44
53
|
time_units : str
|
|
45
54
|
String indicator of the unit of time reported in the metrics. Only
|
|
46
55
|
ns is supported at this time.
|
|
@@ -56,6 +65,7 @@ class SolutionResults:
|
|
|
56
65
|
postprocessing_time : np.ndarray
|
|
57
66
|
penalties : np.ndarray = None
|
|
58
67
|
device : str = None
|
|
68
|
+
raw_solutions : np.ndarray = None
|
|
59
69
|
time_units : str = "ns"
|
|
60
70
|
|
|
61
71
|
@property
|
|
@@ -95,7 +105,22 @@ class SolutionResults:
|
|
|
95
105
|
|
|
96
106
|
@classmethod
|
|
97
107
|
def from_cloud_response(cls, model, response, solver):
|
|
98
|
-
"""
|
|
108
|
+
"""
|
|
109
|
+
Fill in the details from the cloud
|
|
110
|
+
|
|
111
|
+
Parameters
|
|
112
|
+
------------
|
|
113
|
+
|
|
114
|
+
model : eqc_models.base.EqcModel
|
|
115
|
+
EqcModel object describing the problem solved in response
|
|
116
|
+
|
|
117
|
+
response : Dict
|
|
118
|
+
Dictionary of the repsonse from the solver device.
|
|
119
|
+
|
|
120
|
+
solver : eqc_models.base.ModelSolver
|
|
121
|
+
ModelSolver object which is used to obtain job metrics.
|
|
122
|
+
|
|
123
|
+
"""
|
|
99
124
|
|
|
100
125
|
solutions = np.array(response["results"]["solutions"])
|
|
101
126
|
if model.machine_slacks > 0:
|
|
@@ -164,3 +189,58 @@ class SolutionResults:
|
|
|
164
189
|
device=device_type, time_units="ns")
|
|
165
190
|
|
|
166
191
|
return results
|
|
192
|
+
|
|
193
|
+
@classmethod
|
|
194
|
+
def from_eqcdirect_response(cls, model, response, solver):
|
|
195
|
+
"""
|
|
196
|
+
Fill in details from the response dictionary and possibly the solver device.
|
|
197
|
+
|
|
198
|
+
Parameters
|
|
199
|
+
------------
|
|
200
|
+
|
|
201
|
+
model : eqc_models.base.EqcModel
|
|
202
|
+
EqcModel object describing the problem solved in response
|
|
203
|
+
|
|
204
|
+
response : Dict
|
|
205
|
+
Dictionary of the repsonse from the solver device.
|
|
206
|
+
|
|
207
|
+
solver : eqc_models.base.ModelSolver
|
|
208
|
+
ModelSolver object which is used to obtain device information.
|
|
209
|
+
|
|
210
|
+
"""
|
|
211
|
+
solutions = np.array(response["solution"])
|
|
212
|
+
if model.machine_slacks > 0:
|
|
213
|
+
solutions = solutions[:,:-model.machine_slacks]
|
|
214
|
+
energies = np.array(response["energy"])
|
|
215
|
+
# interrogate to determine the device type
|
|
216
|
+
info_dict = solver.client.system_info()
|
|
217
|
+
device_type = info_dict["device_type"]
|
|
218
|
+
if hasattr(model, "evaluateObjective"):
|
|
219
|
+
objectives = np.zeros((solutions.shape[0],), dtype=np.float32)
|
|
220
|
+
for i in range(solutions.shape[0]):
|
|
221
|
+
try:
|
|
222
|
+
objective = model.evaluateObjective(solutions[i])
|
|
223
|
+
except NotImplementedError:
|
|
224
|
+
warnings.warn(f"Cannot set objective value in results for {model.__class__}")
|
|
225
|
+
objectives = None
|
|
226
|
+
break
|
|
227
|
+
objectives[i] = objective
|
|
228
|
+
else:
|
|
229
|
+
objectives = None
|
|
230
|
+
if hasattr(model, "evaluatePenalties"):
|
|
231
|
+
penalties = np.zeros((solutions.shape[0],), dtype=np.float32)
|
|
232
|
+
for i in range(solutions.shape[0]):
|
|
233
|
+
penalties[i] = model.evaluatePenalties(solutions[i]) + model.offset
|
|
234
|
+
else:
|
|
235
|
+
penalties = None
|
|
236
|
+
counts = np.ones(solutions.shape[0])
|
|
237
|
+
runtime = response["runtime"]
|
|
238
|
+
post = response["postprocessing_time"]
|
|
239
|
+
pre = response["preprocessing_time"]
|
|
240
|
+
results = SolutionResults(solutions, energies, counts, objectives,
|
|
241
|
+
runtime, pre, post, penalties=penalties,
|
|
242
|
+
device=device_type, time_units="s")
|
|
243
|
+
|
|
244
|
+
return results
|
|
245
|
+
|
|
246
|
+
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
from .base import EdgeMixin, EdgeModel, GraphModel, NodeModel
|
|
4
4
|
from .maxcut import MaxCutModel
|
|
5
5
|
from .partition import GraphPartitionModel
|
|
6
|
+
from .shortestpath import ShortestPathModel
|
|
7
|
+
from .rcshortestpath import RCShortestPathModel
|
|
6
8
|
|
|
7
9
|
__all__ = ["MaxCutModel", "GraphPartitionModel",
|
|
8
10
|
"EdgeMixin", "EdgeModel", "GraphModel",
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import (Any, Dict, Tuple, List)
|
|
3
|
+
import networkx as nx
|
|
4
|
+
import numpy as np
|
|
5
|
+
from eqc_models.graph.shortestpath import ShortestPathModel
|
|
6
|
+
|
|
7
|
+
log = logging.getLogger(name=__name__)
|
|
8
|
+
|
|
9
|
+
class RCShortestPathModel(ShortestPathModel):
|
|
10
|
+
"""
|
|
11
|
+
Model for resource constrained shortest path problems.
|
|
12
|
+
Use a shortest path base model to implement the routing
|
|
13
|
+
constraints and objective function. Add resource constraints.
|
|
14
|
+
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
def __init__(self, G : nx.DiGraph, s : Any, t : Any, T : int, resource_key : str="resource"):
|
|
18
|
+
if T <= 0:
|
|
19
|
+
raise ValueError("T must be positive")
|
|
20
|
+
elif round(T, 0) != T:
|
|
21
|
+
raise ValueError("T must be integer-valued")
|
|
22
|
+
self.T = T
|
|
23
|
+
self.resource_key = resource_key
|
|
24
|
+
# determine shortest path by weight
|
|
25
|
+
nx_path = nx.shortest_path(G, s, t)
|
|
26
|
+
path_length = len(nx_path)
|
|
27
|
+
self.resource_mult = resource_mult = 2 * path_length / self.T
|
|
28
|
+
super(RCShortestPathModel, self).__init__(G, s, t)
|
|
29
|
+
upper_bound = np.ones(len(self.variables))
|
|
30
|
+
upper_bound[-1] = np.ceil(resource_mult * T)
|
|
31
|
+
self.upper_bound = upper_bound
|
|
32
|
+
is_discrete = [True for i in range(upper_bound.shape[0])]
|
|
33
|
+
is_discrete[-1] = False
|
|
34
|
+
self.is_discrete = is_discrete
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
def variables(self):
|
|
38
|
+
variables = super(RCShortestPathModel, self).variables
|
|
39
|
+
return variables + ["resource_slack"]
|
|
40
|
+
|
|
41
|
+
def buildConstraints(self) -> Tuple[np.ndarray,np.ndarray]:
|
|
42
|
+
lhs, rhs = super(RCShortestPathModel, self).buildConstraints()
|
|
43
|
+
# add a single constraint
|
|
44
|
+
G = self.G
|
|
45
|
+
n = len(self.variables)
|
|
46
|
+
resource_lhs = np.zeros((1, n), dtype=np.float32)
|
|
47
|
+
resource_mult = self.resource_mult
|
|
48
|
+
log.debug("Resource multiplier %f", resource_mult)
|
|
49
|
+
for i in range(len(G.edges)):
|
|
50
|
+
(u, v) = self.variables[i]
|
|
51
|
+
# find the time to traverse the arc
|
|
52
|
+
resource_cost = G.edges[(u, v)][self.resource_key]
|
|
53
|
+
resource_cost *= resource_mult
|
|
54
|
+
log.debug(f"Adding resource %s for edge %s", resource_cost, (u, v))
|
|
55
|
+
resource_lhs[0, i] = resource_cost
|
|
56
|
+
resource_lhs[0, -1] = 1
|
|
57
|
+
lhs = self._stackLHS(lhs, resource_lhs)
|
|
58
|
+
rhs = np.hstack([rhs, [self.T*resource_mult]])
|
|
59
|
+
log.debug("LHS shape %s RHS shape %s", lhs.shape, rhs.shape)
|
|
60
|
+
return lhs, rhs
|
|
61
|
+
|
|
62
|
+
def pathCost(self, path):
|
|
63
|
+
""" sum the cost of all legs in the path """
|
|
64
|
+
assert self.s in path
|
|
65
|
+
G = self.G
|
|
66
|
+
node = self.s
|
|
67
|
+
max_len = len(self.G.nodes) - 1
|
|
68
|
+
path_len = 0
|
|
69
|
+
path_cost = 0
|
|
70
|
+
path_resources = 0
|
|
71
|
+
while node != t:
|
|
72
|
+
edge = (node, path[node])
|
|
73
|
+
if edge not in G.edges:
|
|
74
|
+
raise ValueError(f"Edge {edge} not found")
|
|
75
|
+
path_len += 1
|
|
76
|
+
path_cost += G.edges[edge]["weight"]
|
|
77
|
+
path_resources += G.edges[edge][self.resource_key]
|
|
78
|
+
if path_len > max_len:
|
|
79
|
+
raise ValueError("Invalid path. Describes a cycle.")
|
|
80
|
+
return path_cost, path_resources
|
|
81
|
+
|
|
@@ -62,6 +62,9 @@ class ShortestPathModel(EdgeMixin, ConstrainedQuadraticModel):
|
|
|
62
62
|
self.lhs, self.rhs = self.buildConstraints()
|
|
63
63
|
C, J = self.buildObjective()
|
|
64
64
|
super(ShortestPathModel, self).__init__(C, J, self.lhs, self.rhs)
|
|
65
|
+
self.upper_bound = np.ones(self.lhs.shape[1])
|
|
66
|
+
self.is_discrete = [True for i in range(self.lhs.shape[1])]
|
|
67
|
+
self.machine_slacks = 0
|
|
65
68
|
|
|
66
69
|
def buildConstraints(self) -> Tuple[np.ndarray,np.ndarray]:
|
|
67
70
|
"""
|
|
@@ -72,7 +75,7 @@ class ShortestPathModel(EdgeMixin, ConstrainedQuadraticModel):
|
|
|
72
75
|
$c$ is -1, 1 or 0 for $i=t$, $s$ or all others
|
|
73
76
|
|
|
74
77
|
"""
|
|
75
|
-
log.
|
|
78
|
+
log.debug("Building constraints to find path from %s to %s", self.s, self.t)
|
|
76
79
|
variables = self.variables
|
|
77
80
|
nodes = [n for n in self.G.nodes]
|
|
78
81
|
m = len(nodes)
|
|
@@ -84,7 +87,8 @@ class ShortestPathModel(EdgeMixin, ConstrainedQuadraticModel):
|
|
|
84
87
|
_rhs[node_index, 0] = 1
|
|
85
88
|
elif k == self.t:
|
|
86
89
|
_rhs[node_index, 0] = -1
|
|
87
|
-
for l
|
|
90
|
+
for l in range(len(self.G.edges)): # don't enumerate the edges because the order could change
|
|
91
|
+
(i, j) = self.variables[l]
|
|
88
92
|
if i == j:
|
|
89
93
|
# self loops are not allowed
|
|
90
94
|
raise ValueError("Self loops are not allowed in ShortestPathModel")
|
|
@@ -97,8 +101,8 @@ class ShortestPathModel(EdgeMixin, ConstrainedQuadraticModel):
|
|
|
97
101
|
j_index = nodes.index(j)
|
|
98
102
|
_cons[i_index, l] = 1
|
|
99
103
|
_cons[j_index, l] = -1
|
|
100
|
-
log.
|
|
101
|
-
log.
|
|
104
|
+
log.debug("LHS shape %s RHS shape %s", _cons.shape, _rhs.shape)
|
|
105
|
+
log.debug("checksum %f min %f", np.sum(_cons), np.min(_cons))
|
|
102
106
|
assert np.sum(_rhs) == 0
|
|
103
107
|
return _cons, np.squeeze(_rhs)
|
|
104
108
|
|
|
@@ -114,9 +118,10 @@ class ShortestPathModel(EdgeMixin, ConstrainedQuadraticModel):
|
|
|
114
118
|
m, n = len(nodes), len(variables)
|
|
115
119
|
_obj = [0 for i in range(n)]
|
|
116
120
|
for index, name in enumerate(variables):
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
121
|
+
if type(name) == type((1,2)):
|
|
122
|
+
i, j = name
|
|
123
|
+
_obj[index] = v = G.get_edge_data(i, j)["weight"]
|
|
124
|
+
assert not np.isnan(v), f"Got a NaN at {i, j}"
|
|
120
125
|
J = np.zeros((n, n))
|
|
121
126
|
return np.array(_obj), J
|
|
122
127
|
|
|
@@ -126,32 +131,60 @@ class ShortestPathModel(EdgeMixin, ConstrainedQuadraticModel):
|
|
|
126
131
|
a dictionary with each edge described by key, value pairs.
|
|
127
132
|
|
|
128
133
|
"""
|
|
129
|
-
variables = self.variables
|
|
134
|
+
variables = self.variables[:len(self.G.edges)]
|
|
135
|
+
# log.debug("Using variables %s", variables)
|
|
130
136
|
|
|
131
137
|
lhs, rhs = self.constraints
|
|
132
|
-
|
|
138
|
+
log.debug("LHS shape %s RHS shape %s", lhs.shape, rhs.shape)
|
|
139
|
+
upper_thresh = max(solution[:len(variables)])
|
|
133
140
|
lower_thresh = 0
|
|
141
|
+
got_path = None
|
|
134
142
|
while upper_thresh - lower_thresh > 1e-6:
|
|
135
|
-
log.
|
|
143
|
+
log.debug("Lower Value: %f Upper Value %f", lower_thresh, upper_thresh)
|
|
136
144
|
thresh = (lower_thresh + upper_thresh) / 2
|
|
137
145
|
nx_path = None
|
|
138
146
|
G = nx.DiGraph()
|
|
139
147
|
for (i, j), value in zip(variables, solution):
|
|
140
148
|
if value > thresh:
|
|
141
149
|
G.add_edge(i, j)
|
|
150
|
+
edges = [e for e in G.edges]
|
|
151
|
+
log.debug("Resulting edge count %s", len(edges))
|
|
152
|
+
self.alt_g = G
|
|
142
153
|
path = {}
|
|
143
154
|
try:
|
|
144
155
|
nx_path = nx.shortest_path(G, self.s, self.t)
|
|
145
|
-
upper_thresh = thresh
|
|
146
156
|
lower_thresh = thresh
|
|
157
|
+
got_path = nx_path
|
|
158
|
+
log.debug("Got path size %d", len(got_path))
|
|
147
159
|
except (nx.exception.NodeNotFound, nx.NetworkXAlgorithmError) as err:
|
|
148
|
-
|
|
149
|
-
if
|
|
160
|
+
upper_thresh = thresh
|
|
161
|
+
if got_path is None:
|
|
150
162
|
raise RuntimeError(f"Solution does not describe path from {self.s} to {self.t}")
|
|
151
|
-
path = {}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
163
|
+
# path = {}
|
|
164
|
+
# log.debug("Translating path to dictionary")
|
|
165
|
+
# for i, v in enumerate(got_path):
|
|
166
|
+
# path[got_path[i-1]] = v
|
|
167
|
+
# log.debug("Updated path %s", path)
|
|
168
|
+
# if self.t in path:
|
|
169
|
+
# log.debug("Removing %s from path keys.", self.t)
|
|
170
|
+
# del path[self.t]
|
|
171
|
+
return got_path
|
|
172
|
+
|
|
173
|
+
def pathCost(self, path):
|
|
174
|
+
""" sum the cost of all legs in the path """
|
|
175
|
+
assert self.s in path
|
|
176
|
+
G = self.G
|
|
177
|
+
node = self.s
|
|
178
|
+
max_len = len(self.G.nodes) - 1
|
|
179
|
+
path_len = 0
|
|
180
|
+
path_cost = 0
|
|
181
|
+
while node != t:
|
|
182
|
+
edge = (node, path[node])
|
|
183
|
+
if edge not in G.edges:
|
|
184
|
+
raise ValueError(f"Edge {edge} not found")
|
|
185
|
+
path_len += 1
|
|
186
|
+
path_cost += G.edges[edge]["weight"]
|
|
187
|
+
if path_len > max_len:
|
|
188
|
+
raise ValueError("Invalid path. Describes a cycle.")
|
|
189
|
+
return path_cost
|
|
157
190
|
|
|
@@ -31,10 +31,6 @@ class ClassifierBase(QuadraticModel):
|
|
|
31
31
|
|
|
32
32
|
assert solver_access in ["cloud", "direct"]
|
|
33
33
|
|
|
34
|
-
if solver_access == "direct":
|
|
35
|
-
assert ip_addr is not None, "ip_addr should be set when using direct solver!"
|
|
36
|
-
assert port is not None,"port should be set when using direct solver!"
|
|
37
|
-
|
|
38
34
|
self.solver_access = solver_access
|
|
39
35
|
self.ip_addr = ip_addr
|
|
40
36
|
self.port = port
|
|
@@ -78,7 +74,6 @@ class ClassifierBase(QuadraticModel):
|
|
|
78
74
|
self,
|
|
79
75
|
sum_constraint=self._sum_constraint,
|
|
80
76
|
relaxation_schedule=self.relaxation_schedule,
|
|
81
|
-
solution_precision=None,
|
|
82
77
|
num_samples=self.num_samples,
|
|
83
78
|
)
|
|
84
79
|
|