qhyper 0.dev1__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.
- QHyper/__init__.py +0 -0
- QHyper/optimizers/__init__.py +17 -0
- QHyper/optimizers/base.py +39 -0
- QHyper/optimizers/basinhopping.py +79 -0
- QHyper/optimizers/cem.py +128 -0
- QHyper/optimizers/qml_gradient_descent.py +72 -0
- QHyper/optimizers/random.py +90 -0
- QHyper/optimizers/scipy_minimizer.py +65 -0
- QHyper/problems/__init__.py +0 -0
- QHyper/problems/base.py +62 -0
- QHyper/problems/community_detection.py +185 -0
- QHyper/problems/knapsack.py +176 -0
- QHyper/problems/maxcut.py +37 -0
- QHyper/problems/tsp.py +190 -0
- QHyper/problems/workflow_scheduling.py +220 -0
- QHyper/solvers/__init__.py +14 -0
- QHyper/solvers/base.py +86 -0
- QHyper/solvers/converter.py +128 -0
- QHyper/solvers/cqm/__init__.py +1 -0
- QHyper/solvers/cqm/cqm.py +66 -0
- QHyper/solvers/dqm/__init__.py +1 -0
- QHyper/solvers/dqm/dqm.py +23 -0
- QHyper/solvers/gurobi/__init__.py +1 -0
- QHyper/solvers/gurobi/gurobi.py +77 -0
- QHyper/solvers/vqa/__init__.py +25 -0
- QHyper/solvers/vqa/base.py +108 -0
- QHyper/solvers/vqa/eval_funcs/__init__.py +0 -0
- QHyper/solvers/vqa/eval_funcs/base.py +40 -0
- QHyper/solvers/vqa/eval_funcs/wfeval.py +32 -0
- QHyper/solvers/vqa/pqc/__init__.py +0 -0
- QHyper/solvers/vqa/pqc/base.py +133 -0
- QHyper/solvers/vqa/pqc/h_qaoa.py +86 -0
- QHyper/solvers/vqa/pqc/mixers.py +12 -0
- QHyper/solvers/vqa/pqc/qaoa.py +130 -0
- QHyper/solvers/vqa/pqc/wf_qaoa.py +80 -0
- QHyper/util.py +4 -0
- qhyper-0.dev1.dist-info/LICENSE.txt +21 -0
- qhyper-0.dev1.dist-info/METADATA +26 -0
- qhyper-0.dev1.dist-info/RECORD +41 -0
- qhyper-0.dev1.dist-info/WHEEL +5 -0
- qhyper-0.dev1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import math
|
|
2
|
+
from collections import defaultdict
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
|
|
5
|
+
import networkx as nx
|
|
6
|
+
import numpy as np
|
|
7
|
+
import pandas as pd
|
|
8
|
+
import sympy
|
|
9
|
+
from wfcommons import Instance
|
|
10
|
+
from wfcommons.utils import read_json
|
|
11
|
+
|
|
12
|
+
from networkx.classes.reportviews import NodeView
|
|
13
|
+
|
|
14
|
+
from sympy.core.expr import Expr
|
|
15
|
+
from typing import cast
|
|
16
|
+
|
|
17
|
+
from QHyper.hyperparameter_gen.parser import Expression
|
|
18
|
+
from .base import Problem
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass
|
|
22
|
+
class TargetMachine:
|
|
23
|
+
name: str
|
|
24
|
+
memory: int
|
|
25
|
+
cpu: dict[str, float]
|
|
26
|
+
price: float
|
|
27
|
+
memory_cost_multiplier: float
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class Workflow:
|
|
31
|
+
def __init__(
|
|
32
|
+
self, tasks_file: str, machines_file: str, deadline: float
|
|
33
|
+
) -> None:
|
|
34
|
+
self.wf_instance = Instance(tasks_file)
|
|
35
|
+
self.tasks = self._get_tasks()
|
|
36
|
+
self.machines = self._get_machines(machines_file)
|
|
37
|
+
self.deadline = deadline
|
|
38
|
+
self._set_paths()
|
|
39
|
+
self.time_matrix, self.cost_matrix = self._calc_dataframes()
|
|
40
|
+
self.task_names = self.time_matrix.index
|
|
41
|
+
self.machine_names = self.time_matrix.columns
|
|
42
|
+
|
|
43
|
+
def _get_tasks(self) -> NodeView:
|
|
44
|
+
return self.wf_instance.workflow.nodes(data=True)
|
|
45
|
+
|
|
46
|
+
def _get_machines(self, machines_file: str) -> dict[str, TargetMachine]:
|
|
47
|
+
target_machines = read_json(machines_file)
|
|
48
|
+
return {
|
|
49
|
+
machine['name']: TargetMachine(**machine)
|
|
50
|
+
for machine in target_machines["machines"]
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
def _set_paths(self) -> None:
|
|
54
|
+
all_paths = []
|
|
55
|
+
for root in self.wf_instance.roots():
|
|
56
|
+
for leaf in self.wf_instance.leaves():
|
|
57
|
+
paths = nx.all_simple_paths(
|
|
58
|
+
self.wf_instance.workflow, root, leaf)
|
|
59
|
+
all_paths.extend(paths)
|
|
60
|
+
|
|
61
|
+
self.paths = all_paths
|
|
62
|
+
|
|
63
|
+
def _calc_dataframes(self) -> tuple[pd.DataFrame, pd.DataFrame]:
|
|
64
|
+
costs, runtimes = {}, {}
|
|
65
|
+
for machine_name, machine_details in self.machines.items():
|
|
66
|
+
machine_cost, machine_runtime = [], []
|
|
67
|
+
for task_name, task in self.tasks:
|
|
68
|
+
old_machine = task["task"].machine
|
|
69
|
+
number_of_operations = (
|
|
70
|
+
task["task"].runtime
|
|
71
|
+
* old_machine.cpu_speed
|
|
72
|
+
* old_machine.cpu_cores
|
|
73
|
+
* 10 ** 6
|
|
74
|
+
)
|
|
75
|
+
# todo can this overflow?
|
|
76
|
+
real_runtime = number_of_operations / (
|
|
77
|
+
machine_details.cpu["speed"]
|
|
78
|
+
* machine_details.cpu["count"] * 10 ** 6)
|
|
79
|
+
machine_runtime.append(real_runtime)
|
|
80
|
+
machine_cost.append(real_runtime * machine_details.price)
|
|
81
|
+
costs[machine_name] = machine_cost
|
|
82
|
+
runtimes[machine_name] = machine_runtime
|
|
83
|
+
|
|
84
|
+
time_df = pd.DataFrame(
|
|
85
|
+
data=runtimes, index=self.wf_instance.workflow.nodes)
|
|
86
|
+
cost_df = pd.DataFrame(
|
|
87
|
+
data=costs, index=self.wf_instance.workflow.nodes)
|
|
88
|
+
|
|
89
|
+
return time_df, cost_df
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def calc_slack_coefficients(constant: int) -> list[int]:
|
|
93
|
+
num_slack = int(np.floor(np.log2(constant)))
|
|
94
|
+
slack_coefficients = [2 ** j for j in range(num_slack)]
|
|
95
|
+
if constant - 2 ** num_slack >= 0:
|
|
96
|
+
slack_coefficients.append(constant - 2 ** num_slack + 1)
|
|
97
|
+
return slack_coefficients
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class WorkflowSchedulingProblem(Problem):
|
|
101
|
+
def __init__(self, workflow: Workflow):
|
|
102
|
+
self.workflow: Workflow = workflow
|
|
103
|
+
self.slack_coefficients = self._get_slacks()
|
|
104
|
+
self.variables: tuple[sympy.Symbol] = sympy.symbols(' '.join(
|
|
105
|
+
[f'x{i}' for i in range(
|
|
106
|
+
len(self.workflow.tasks) * len(self.workflow.machines))]
|
|
107
|
+
) + ' ' + ' '.join(
|
|
108
|
+
[f's{i}' for i in range(len(self.slack_coefficients))]))
|
|
109
|
+
|
|
110
|
+
self._set_objective_function()
|
|
111
|
+
self._set_constraints()
|
|
112
|
+
|
|
113
|
+
def _get_slacks(self) -> list[int]:
|
|
114
|
+
min_path_runtime, _ = self.get_deadlines()
|
|
115
|
+
deadline_diff = int(self.workflow.deadline - min_path_runtime)
|
|
116
|
+
return calc_slack_coefficients(deadline_diff)
|
|
117
|
+
|
|
118
|
+
def _set_objective_function(self) -> None:
|
|
119
|
+
expression: Expr = cast(Expr, 0)
|
|
120
|
+
for task_id, task_name in enumerate(self.workflow.time_matrix.index):
|
|
121
|
+
for machine_id, machine_name in enumerate(
|
|
122
|
+
self.workflow.time_matrix.columns):
|
|
123
|
+
cost = self.workflow.cost_matrix[machine_name][task_name]
|
|
124
|
+
expression += cost * self.variables[
|
|
125
|
+
machine_id
|
|
126
|
+
+ task_id * len(self.workflow.time_matrix.columns)
|
|
127
|
+
]
|
|
128
|
+
|
|
129
|
+
self.objective_function: Expression = Expression(expression)
|
|
130
|
+
|
|
131
|
+
def _set_constraints(self) -> None:
|
|
132
|
+
self.constraints: list[Expression] = []
|
|
133
|
+
|
|
134
|
+
# machine assignment constraint
|
|
135
|
+
for task_id in range(len(self.workflow.time_matrix.index)):
|
|
136
|
+
expression: Expr = cast(Expr, 0)
|
|
137
|
+
for machine_id in range(len(self.workflow.time_matrix.columns)):
|
|
138
|
+
expression += self.variables[
|
|
139
|
+
machine_id
|
|
140
|
+
+ task_id * len(self.workflow.time_matrix.columns)
|
|
141
|
+
]
|
|
142
|
+
expression -= 1
|
|
143
|
+
|
|
144
|
+
self.constraints.append(Expression(expression))
|
|
145
|
+
|
|
146
|
+
# deadline constraint
|
|
147
|
+
|
|
148
|
+
min_deadline, _ = self.get_deadlines()
|
|
149
|
+
# deadline_for_slacks = int(self.workflow.deadline - min_deadline)
|
|
150
|
+
|
|
151
|
+
for path in self.workflow.paths:
|
|
152
|
+
expression = cast(Expr, -self.workflow.deadline)
|
|
153
|
+
for task_id, task_name in enumerate(
|
|
154
|
+
self.workflow.time_matrix.index):
|
|
155
|
+
for machine_id, machine_name in enumerate(
|
|
156
|
+
self.workflow.time_matrix.columns):
|
|
157
|
+
if task_name in path:
|
|
158
|
+
time = self.workflow.time_matrix[
|
|
159
|
+
machine_name][task_name]
|
|
160
|
+
expression += time * self.variables[
|
|
161
|
+
machine_id
|
|
162
|
+
+ task_id * len(self.workflow.time_matrix.columns)
|
|
163
|
+
]
|
|
164
|
+
|
|
165
|
+
first_slack_index = (
|
|
166
|
+
len(self.workflow.time_matrix.index)
|
|
167
|
+
* len(self.workflow.time_matrix.columns)
|
|
168
|
+
)
|
|
169
|
+
for i, coefficient in enumerate(self.slack_coefficients):
|
|
170
|
+
expression += (
|
|
171
|
+
coefficient * self.variables[first_slack_index + i])
|
|
172
|
+
|
|
173
|
+
self.constraints.append(Expression(expression))
|
|
174
|
+
|
|
175
|
+
def check_solution_correctness(self) -> None:
|
|
176
|
+
raise NotImplementedError # todo check if slack values are correct
|
|
177
|
+
|
|
178
|
+
def decode_solution(self, solution: dict) -> dict:
|
|
179
|
+
decoded_solution = {}
|
|
180
|
+
for variable, value in solution.items():
|
|
181
|
+
_, id = variable[0], int(variable[1:]) # todo add validation
|
|
182
|
+
if value == 1.0:
|
|
183
|
+
machine_id = id % len(self.workflow.machines)
|
|
184
|
+
task_id = id // len(self.workflow.machines)
|
|
185
|
+
decoded_solution[self.workflow.time_matrix.index[task_id]] = (
|
|
186
|
+
self.workflow.time_matrix.columns[machine_id])
|
|
187
|
+
|
|
188
|
+
return decoded_solution
|
|
189
|
+
|
|
190
|
+
def get_deadlines(self) -> tuple[float, float]: # todo test this function
|
|
191
|
+
"""Calculates the minimum and maximum path runtime
|
|
192
|
+
for the whole workflow."""
|
|
193
|
+
|
|
194
|
+
flat_runtimes = [
|
|
195
|
+
(runtime, name)
|
|
196
|
+
for n, machine_runtimes in self.workflow.time_matrix.items()
|
|
197
|
+
for runtime, name in zip(
|
|
198
|
+
machine_runtimes, self.workflow.task_names)
|
|
199
|
+
]
|
|
200
|
+
|
|
201
|
+
max_path_runtime = 0.0
|
|
202
|
+
min_path_runtime = 0.0
|
|
203
|
+
|
|
204
|
+
for path in self.workflow.paths:
|
|
205
|
+
max_runtime: defaultdict[str, float] = defaultdict(lambda: 0.0)
|
|
206
|
+
min_runtime: defaultdict[str, float] = defaultdict(
|
|
207
|
+
lambda: math.inf)
|
|
208
|
+
|
|
209
|
+
for runtime, name in flat_runtimes:
|
|
210
|
+
if name not in path:
|
|
211
|
+
continue
|
|
212
|
+
max_runtime[name] = max(max_runtime[name], runtime)
|
|
213
|
+
min_runtime[name] = min(min_runtime[name], runtime)
|
|
214
|
+
max_path_runtime = max(max_path_runtime, sum(max_runtime.values()))
|
|
215
|
+
min_path_runtime = max(min_path_runtime, sum(min_runtime.values()))
|
|
216
|
+
|
|
217
|
+
return min_path_runtime, max_path_runtime
|
|
218
|
+
|
|
219
|
+
def get_score(self, result: str, penalty: float = 0) -> float:
|
|
220
|
+
return 0
|
QHyper/solvers/base.py
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
from abc import abstractmethod
|
|
2
|
+
|
|
3
|
+
from typing import Any, Optional
|
|
4
|
+
|
|
5
|
+
from QHyper.problems.base import Problem
|
|
6
|
+
from QHyper.optimizers import OPTIMIZERS_BY_NAME
|
|
7
|
+
from QHyper.optimizers.base import Optimizer
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Solver:
|
|
11
|
+
"""
|
|
12
|
+
Abstract base class for solvers.
|
|
13
|
+
|
|
14
|
+
Attributes
|
|
15
|
+
----------
|
|
16
|
+
problem : Problem
|
|
17
|
+
The problem to be solved.
|
|
18
|
+
hyper_optimizer: Optimizer, optional
|
|
19
|
+
Hyperparameter optmizer.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
problem: Problem
|
|
23
|
+
hyper_optimizer: Optimizer = None
|
|
24
|
+
|
|
25
|
+
def __init__(self, problem: Problem, **kwargs: Any) -> None:
|
|
26
|
+
pass
|
|
27
|
+
|
|
28
|
+
@staticmethod
|
|
29
|
+
def from_config(problem: Problem, config: dict[str, Any]) -> 'Solver':
|
|
30
|
+
"""
|
|
31
|
+
Alternative way of creating solver.
|
|
32
|
+
Expect dict with two keys:
|
|
33
|
+
- type - type of solver
|
|
34
|
+
- args - arguments which will be passed to Solver instance
|
|
35
|
+
|
|
36
|
+
Parameters
|
|
37
|
+
----------
|
|
38
|
+
problem : Problem
|
|
39
|
+
The problem to be solved
|
|
40
|
+
config : dict[str. Any]
|
|
41
|
+
Configuration in form of dict
|
|
42
|
+
|
|
43
|
+
Returns
|
|
44
|
+
-------
|
|
45
|
+
Solver
|
|
46
|
+
Initialized Solver object
|
|
47
|
+
"""
|
|
48
|
+
from QHyper.solvers import SOLVERS
|
|
49
|
+
|
|
50
|
+
try:
|
|
51
|
+
solver_type = config['solver'].pop('type').lower()
|
|
52
|
+
solver_args = config['solver'].pop('args')
|
|
53
|
+
except KeyError:
|
|
54
|
+
raise Exception("Configuration for Solver was not provided")
|
|
55
|
+
|
|
56
|
+
solver = SOLVERS[solver_type](problem, **solver_args)
|
|
57
|
+
|
|
58
|
+
if 'hyper_optimizer' in config:
|
|
59
|
+
try:
|
|
60
|
+
optimizer_type = config['hyper_optimizer'].pop('type')
|
|
61
|
+
except KeyError:
|
|
62
|
+
raise Exception("Type for hyper optimizer was not provided")
|
|
63
|
+
|
|
64
|
+
solver.hyper_optimizer = OPTIMIZERS_BY_NAME[optimizer_type](
|
|
65
|
+
**config['hyper_optimizer']
|
|
66
|
+
)
|
|
67
|
+
return solver
|
|
68
|
+
|
|
69
|
+
@abstractmethod
|
|
70
|
+
def solve(
|
|
71
|
+
self,
|
|
72
|
+
params_inits: dict[str, Any]
|
|
73
|
+
) -> Any:
|
|
74
|
+
"""
|
|
75
|
+
Parameters
|
|
76
|
+
----------
|
|
77
|
+
params_inits : dict[str, Any], optional
|
|
78
|
+
Initial parameters for the optimization. Default is None.
|
|
79
|
+
|
|
80
|
+
Returns
|
|
81
|
+
-------
|
|
82
|
+
Any
|
|
83
|
+
The result of the solver.
|
|
84
|
+
"""
|
|
85
|
+
|
|
86
|
+
...
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
from typing import Any, cast
|
|
2
|
+
|
|
3
|
+
import dimod
|
|
4
|
+
from dimod import ConstrainedQuadraticModel, DiscreteQuadraticModel
|
|
5
|
+
from QHyper.hyperparameter_gen.parser import Expression
|
|
6
|
+
from QHyper.problems.base import Problem
|
|
7
|
+
from QHyper.util import QUBO, VARIABLES
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def dict_to_list(my_dict: QUBO) -> list[tuple[Any, ...]]:
|
|
11
|
+
return [tuple([*key, val]) for key, val in my_dict.items()]
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Converter:
|
|
15
|
+
@staticmethod
|
|
16
|
+
def create_qubo(problem: Problem, weights: list[float]) -> QUBO:
|
|
17
|
+
results: dict[VARIABLES, float] = {}
|
|
18
|
+
|
|
19
|
+
if len(weights) != len(problem.constraints) + 1:
|
|
20
|
+
raise Exception(
|
|
21
|
+
f"Expected {len(problem.constraints)+1} weights, "
|
|
22
|
+
f"got {len(weights)}"
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
objective_function = Expression(
|
|
26
|
+
{
|
|
27
|
+
key: weights[0] * val
|
|
28
|
+
for key, val in problem.objective_function.as_dict().items()
|
|
29
|
+
}
|
|
30
|
+
)
|
|
31
|
+
for key, value in objective_function.as_dict().items():
|
|
32
|
+
if key in results:
|
|
33
|
+
results[key] += value
|
|
34
|
+
else:
|
|
35
|
+
results[key] = value
|
|
36
|
+
|
|
37
|
+
constraint_weights = weights[1:]
|
|
38
|
+
|
|
39
|
+
for weight, element in zip(constraint_weights, problem.constraints):
|
|
40
|
+
constraint = Expression(element.polynomial**2 * weight)
|
|
41
|
+
for key, value in constraint.as_dict().items():
|
|
42
|
+
if key in results:
|
|
43
|
+
results[key] += value
|
|
44
|
+
else:
|
|
45
|
+
results[key] = value
|
|
46
|
+
|
|
47
|
+
return results
|
|
48
|
+
|
|
49
|
+
@staticmethod
|
|
50
|
+
def create_weight_free_qubo(problem: Problem) -> QUBO:
|
|
51
|
+
results: dict[VARIABLES, float] = {}
|
|
52
|
+
|
|
53
|
+
objective_function = Expression(problem.objective_function.polynomial)
|
|
54
|
+
for key, value in objective_function.as_dict().items():
|
|
55
|
+
if key in results:
|
|
56
|
+
results[key] += value
|
|
57
|
+
else:
|
|
58
|
+
results[key] = value
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
return results
|
|
62
|
+
|
|
63
|
+
@staticmethod
|
|
64
|
+
def to_cqm(problem: Problem) -> ConstrainedQuadraticModel:
|
|
65
|
+
binary_polynomial = dimod.BinaryPolynomial(
|
|
66
|
+
problem.objective_function.as_dict(), dimod.BINARY
|
|
67
|
+
)
|
|
68
|
+
cqm = dimod.make_quadratic_cqm(binary_polynomial)
|
|
69
|
+
|
|
70
|
+
# todo this cqm can probably be initialized in some other way
|
|
71
|
+
for var in problem.variables:
|
|
72
|
+
if str(var) not in cqm.variables:
|
|
73
|
+
cqm.add_variable(dimod.BINARY, str(var))
|
|
74
|
+
|
|
75
|
+
for i, constraint in enumerate(problem.constraints):
|
|
76
|
+
cqm.add_constraint(
|
|
77
|
+
dict_to_list(constraint.as_dict()), "==", label=i
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
return cqm
|
|
81
|
+
|
|
82
|
+
@staticmethod
|
|
83
|
+
def to_qubo(problem: Problem) -> tuple[QUBO, float]:
|
|
84
|
+
cqm = Converter.to_cqm(problem)
|
|
85
|
+
bqm, _ = dimod.cqm_to_bqm(cqm, lagrange_multiplier=10)
|
|
86
|
+
return cast(tuple[QUBO, float], bqm.to_qubo()) # (qubo, offset)
|
|
87
|
+
|
|
88
|
+
@staticmethod
|
|
89
|
+
def to_dqm(problem: Problem) -> DiscreteQuadraticModel:
|
|
90
|
+
dqm = dimod.DiscreteQuadraticModel()
|
|
91
|
+
|
|
92
|
+
BIN_OFFSET = 1 if problem.cases == 1 else 0
|
|
93
|
+
|
|
94
|
+
def binary_to_discrete(v: str) -> str:
|
|
95
|
+
id = int(v[1:])
|
|
96
|
+
discrete_id = id // problem.cases
|
|
97
|
+
return f"x{discrete_id}"
|
|
98
|
+
|
|
99
|
+
variables_discrete = [
|
|
100
|
+
binary_to_discrete(str(v))
|
|
101
|
+
for v in problem.variables[:: problem.cases]
|
|
102
|
+
]
|
|
103
|
+
for var in variables_discrete:
|
|
104
|
+
if var not in dqm.variables:
|
|
105
|
+
dqm.add_variable(problem.cases + BIN_OFFSET, var)
|
|
106
|
+
|
|
107
|
+
for vars, bias in problem.objective_function.as_dict().items():
|
|
108
|
+
s_i, *s_j = vars
|
|
109
|
+
x_i = binary_to_discrete(s_i)
|
|
110
|
+
xi_idx: int = cast(int, dqm.variables.index(x_i))
|
|
111
|
+
if s_j:
|
|
112
|
+
x_j = binary_to_discrete(*s_j)
|
|
113
|
+
xj_idx: int = cast(int, dqm.variables.index(x_j))
|
|
114
|
+
dqm.set_quadratic(
|
|
115
|
+
dqm.variables[xi_idx],
|
|
116
|
+
dqm.variables[xj_idx],
|
|
117
|
+
{
|
|
118
|
+
(case, case): bias
|
|
119
|
+
for case in range(problem.cases + BIN_OFFSET)
|
|
120
|
+
},
|
|
121
|
+
)
|
|
122
|
+
else:
|
|
123
|
+
dqm.set_linear(
|
|
124
|
+
dqm.variables[xi_idx],
|
|
125
|
+
[bias for _ in range(problem.cases + BIN_OFFSET)],
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
return dqm
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .cqm import CQM
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from dimod import ConstrainedQuadraticModel
|
|
3
|
+
from dwave.system import LeapHybridCQMSampler
|
|
4
|
+
|
|
5
|
+
from typing import Any, Optional
|
|
6
|
+
|
|
7
|
+
from QHyper.solvers.converter import Converter
|
|
8
|
+
from QHyper.problems.base import Problem
|
|
9
|
+
from QHyper.solvers.base import Solver
|
|
10
|
+
from QHyper.optimizers.base import Optimizer
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
DWAVE_API_TOKEN = os.environ.get('DWAVE_API_TOKEN')
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class CQM(Solver):
|
|
17
|
+
"""
|
|
18
|
+
Class for solving a problem using the Constrained Quadratic Model (CQM) approach.
|
|
19
|
+
|
|
20
|
+
Attributes
|
|
21
|
+
----------
|
|
22
|
+
problem : Problem
|
|
23
|
+
The problem to be solved.
|
|
24
|
+
time : float
|
|
25
|
+
The maximum time allowed for the CQM solver.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
def __init__(self, problem: Problem, time: float) -> None:
|
|
29
|
+
"""
|
|
30
|
+
Parameters
|
|
31
|
+
----------
|
|
32
|
+
problem : Problem
|
|
33
|
+
The problem to be solved.
|
|
34
|
+
time : float
|
|
35
|
+
The maximum time allowed for the CQM solver.
|
|
36
|
+
"""
|
|
37
|
+
self.problem: Problem = problem
|
|
38
|
+
self.time: float = time
|
|
39
|
+
|
|
40
|
+
def solve(self, params_inits: dict[str, Any] = {}) -> Any:
|
|
41
|
+
"""
|
|
42
|
+
Solve the problem using the CQM approach.
|
|
43
|
+
|
|
44
|
+
Parameters
|
|
45
|
+
----------
|
|
46
|
+
params_inits : dict[str, Any], optional
|
|
47
|
+
Initial parameters for the optimization. Default is None.
|
|
48
|
+
hyper_optimizer : Optimizer, optional
|
|
49
|
+
Hyperparameter optimizer. Default is None.
|
|
50
|
+
|
|
51
|
+
Returns
|
|
52
|
+
-------
|
|
53
|
+
Any
|
|
54
|
+
The solution to the problem.
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
converter = Converter()
|
|
58
|
+
cqm = converter.to_cqm(self.problem)
|
|
59
|
+
sampler = LeapHybridCQMSampler(token=DWAVE_API_TOKEN)
|
|
60
|
+
solutions = sampler.sample_cqm(cqm, self.time)
|
|
61
|
+
correct_solutions = [
|
|
62
|
+
s for s in solutions
|
|
63
|
+
if len(cqm.violations(s, skip_satisfied=True)) == 0
|
|
64
|
+
]
|
|
65
|
+
|
|
66
|
+
return correct_solutions[0]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .dqm import DQM
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
from dwave.system import LeapHybridDQMSampler
|
|
5
|
+
from QHyper.problems.base import Problem
|
|
6
|
+
from QHyper.solvers.base import Solver
|
|
7
|
+
from QHyper.solvers.converter import Converter
|
|
8
|
+
|
|
9
|
+
DWAVE_API_TOKEN = os.environ.get("DWAVE_API_TOKEN")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class DQM(Solver):
|
|
13
|
+
def __init__(self, problem: Problem, time: float) -> None:
|
|
14
|
+
self.problem: Problem = problem
|
|
15
|
+
self.time: float = time
|
|
16
|
+
|
|
17
|
+
def solve(self, params_inits: dict[str, Any] = {}) -> Any:
|
|
18
|
+
converter = Converter()
|
|
19
|
+
dqm = converter.to_dqm(self.problem)
|
|
20
|
+
sampler = LeapHybridDQMSampler(token=DWAVE_API_TOKEN)
|
|
21
|
+
sampleset = sampler.sample_dqm(dqm, self.time)
|
|
22
|
+
|
|
23
|
+
return sampleset
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .gurobi import Gurobi
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
import gurobipy as gp
|
|
4
|
+
from QHyper.problems.base import Problem
|
|
5
|
+
from QHyper.solvers.base import Solver
|
|
6
|
+
from QHyper.solvers.converter import QUBO
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def calc(vars: dict[str, Any], poly_dict: QUBO) -> Any:
|
|
10
|
+
cost_function: float = 0
|
|
11
|
+
for key, value in poly_dict.items():
|
|
12
|
+
tmp = 1
|
|
13
|
+
for k in key:
|
|
14
|
+
tmp *= vars[k]
|
|
15
|
+
cost_function += tmp * value
|
|
16
|
+
return cost_function
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class Gurobi(Solver): # todo works only for quadratic expressions
|
|
20
|
+
def __init__(self, problem: Problem) -> None:
|
|
21
|
+
self.problem: Problem = problem
|
|
22
|
+
|
|
23
|
+
def solve(self, params_inits: dict[str, Any]) -> Any:
|
|
24
|
+
name = params_inits.get("name", "name")
|
|
25
|
+
gpm = gp.Model(name)
|
|
26
|
+
if "MIPGap" in params_inits:
|
|
27
|
+
gpm.Params.MIPGap = params_inits["MIPGap"]
|
|
28
|
+
|
|
29
|
+
vars = {
|
|
30
|
+
str(var_name): gpm.addVar(vtype=gp.GRB.BINARY, name=str(var_name))
|
|
31
|
+
for var_name in self.problem.variables
|
|
32
|
+
}
|
|
33
|
+
# gpm.update()
|
|
34
|
+
|
|
35
|
+
objective_function = calc(
|
|
36
|
+
vars, self.problem.objective_function.as_dict()
|
|
37
|
+
)
|
|
38
|
+
gpm.setObjective(objective_function, gp.GRB.MINIMIZE)
|
|
39
|
+
|
|
40
|
+
for i, constraint in enumerate(self.problem.constraints):
|
|
41
|
+
tmp_constraint = calc(vars, constraint.as_dict())
|
|
42
|
+
gpm.addConstr(tmp_constraint == 0, f"constr_{i}")
|
|
43
|
+
gpm.update()
|
|
44
|
+
|
|
45
|
+
# eq_constraints = self.problem.constraints["=="]
|
|
46
|
+
# for i, constraint in enumerate(eq_constraints):
|
|
47
|
+
# tmp_constraint = calc(vars, constraint.as_dict())
|
|
48
|
+
# gpm.addConstr(tmp_constraint == 0, f"eq_constr_{i}")
|
|
49
|
+
#
|
|
50
|
+
# ltq_constraints = self.problem.constraints["<="]
|
|
51
|
+
# for i, constraint in enumerate(ltq_constraints):
|
|
52
|
+
# tmp_constraint = calc(vars, constraint.as_dict())
|
|
53
|
+
# gpm.addConstr(tmp_constraint <= 0, f"leq_constr_{i}")
|
|
54
|
+
#
|
|
55
|
+
# gtq_constraints = self.problem.constraints["<="]
|
|
56
|
+
# for i, constraint in enumerate(gtq_constraints):
|
|
57
|
+
# tmp_constraint = calc(vars, constraint.as_dict())
|
|
58
|
+
# gpm.addConstr(tmp_constraint >= 0, f"gtq_constr_{i}")
|
|
59
|
+
|
|
60
|
+
gpm.optimize()
|
|
61
|
+
|
|
62
|
+
# print(dir(gpm))
|
|
63
|
+
# print("status ", gpm.Status)
|
|
64
|
+
# print("is optimal? ", GRB.OPTIMAL)
|
|
65
|
+
#
|
|
66
|
+
# allvars = gpm.getVars()
|
|
67
|
+
# for v in allvars[-1:]:
|
|
68
|
+
# print(v.VarName)
|
|
69
|
+
# print(v.X)
|
|
70
|
+
# print(dir(v))
|
|
71
|
+
|
|
72
|
+
allvars = gpm.getVars()
|
|
73
|
+
solution = {}
|
|
74
|
+
for v in allvars:
|
|
75
|
+
solution[v.VarName] = v.X
|
|
76
|
+
|
|
77
|
+
return solution
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from typing import Type
|
|
2
|
+
|
|
3
|
+
# === PQC ===
|
|
4
|
+
|
|
5
|
+
from .pqc.base import PQC
|
|
6
|
+
from .pqc.h_qaoa import HQAOA
|
|
7
|
+
from .pqc.qaoa import QAOA
|
|
8
|
+
from .pqc.wf_qaoa import WFQAOA
|
|
9
|
+
|
|
10
|
+
PQC_BY_NAME: dict[str, Type[PQC]] = {
|
|
11
|
+
'hqaoa': HQAOA,
|
|
12
|
+
'qaoa': QAOA,
|
|
13
|
+
'wfqaoa': WFQAOA
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
# # === Evaluation functions ===
|
|
17
|
+
|
|
18
|
+
# from .eval_funcs.base import EvalFunc
|
|
19
|
+
# from .eval_funcs.expval import ExpVal
|
|
20
|
+
# from .eval_funcs.wfeval import WFEval
|
|
21
|
+
|
|
22
|
+
# EVAL_FUNCS_BY_NAME: dict[str, Type[EvalFunc]] = {
|
|
23
|
+
# 'expval': ExpVal,
|
|
24
|
+
# 'wfeval': WFEval,
|
|
25
|
+
# }
|