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.
Files changed (41) hide show
  1. QHyper/__init__.py +0 -0
  2. QHyper/optimizers/__init__.py +17 -0
  3. QHyper/optimizers/base.py +39 -0
  4. QHyper/optimizers/basinhopping.py +79 -0
  5. QHyper/optimizers/cem.py +128 -0
  6. QHyper/optimizers/qml_gradient_descent.py +72 -0
  7. QHyper/optimizers/random.py +90 -0
  8. QHyper/optimizers/scipy_minimizer.py +65 -0
  9. QHyper/problems/__init__.py +0 -0
  10. QHyper/problems/base.py +62 -0
  11. QHyper/problems/community_detection.py +185 -0
  12. QHyper/problems/knapsack.py +176 -0
  13. QHyper/problems/maxcut.py +37 -0
  14. QHyper/problems/tsp.py +190 -0
  15. QHyper/problems/workflow_scheduling.py +220 -0
  16. QHyper/solvers/__init__.py +14 -0
  17. QHyper/solvers/base.py +86 -0
  18. QHyper/solvers/converter.py +128 -0
  19. QHyper/solvers/cqm/__init__.py +1 -0
  20. QHyper/solvers/cqm/cqm.py +66 -0
  21. QHyper/solvers/dqm/__init__.py +1 -0
  22. QHyper/solvers/dqm/dqm.py +23 -0
  23. QHyper/solvers/gurobi/__init__.py +1 -0
  24. QHyper/solvers/gurobi/gurobi.py +77 -0
  25. QHyper/solvers/vqa/__init__.py +25 -0
  26. QHyper/solvers/vqa/base.py +108 -0
  27. QHyper/solvers/vqa/eval_funcs/__init__.py +0 -0
  28. QHyper/solvers/vqa/eval_funcs/base.py +40 -0
  29. QHyper/solvers/vqa/eval_funcs/wfeval.py +32 -0
  30. QHyper/solvers/vqa/pqc/__init__.py +0 -0
  31. QHyper/solvers/vqa/pqc/base.py +133 -0
  32. QHyper/solvers/vqa/pqc/h_qaoa.py +86 -0
  33. QHyper/solvers/vqa/pqc/mixers.py +12 -0
  34. QHyper/solvers/vqa/pqc/qaoa.py +130 -0
  35. QHyper/solvers/vqa/pqc/wf_qaoa.py +80 -0
  36. QHyper/util.py +4 -0
  37. qhyper-0.dev1.dist-info/LICENSE.txt +21 -0
  38. qhyper-0.dev1.dist-info/METADATA +26 -0
  39. qhyper-0.dev1.dist-info/RECORD +41 -0
  40. qhyper-0.dev1.dist-info/WHEEL +5 -0
  41. qhyper-0.dev1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,185 @@
1
+ from dataclasses import dataclass, field
2
+ from typing import Any, Iterable, Tuple, cast
3
+
4
+ import networkx as nx
5
+ import numpy as np
6
+ import sympy
7
+ from QHyper.hyperparameter_gen.parser import Expression
8
+ from QHyper.problems.base import Problem
9
+ from QHyper.util import VARIABLES
10
+ from sympy.core.expr import Expr
11
+
12
+
13
+ @dataclass
14
+ class Network:
15
+ graph: nx.Graph
16
+ resolution: float = field(default=1)
17
+ modularity_matrix: np.ndarray = field(init=False)
18
+ weight: str | None = field(default=None)
19
+
20
+ def __post_init__(self) -> None:
21
+ self.modularity_matrix = self.calculate_modularity_matrix()
22
+
23
+ def calculate_modularity_matrix(self) -> np.ndarray:
24
+ adj_matrix: np.ndarray = nx.to_numpy_array(
25
+ self.graph, weight=self.weight
26
+ )
27
+ degree_matrix: np.ndarray = adj_matrix.sum(axis=1)
28
+ m: int = np.sum(degree_matrix)
29
+ return (
30
+ adj_matrix
31
+ - self.resolution * np.outer(degree_matrix, degree_matrix) / m
32
+ )
33
+
34
+
35
+ class KarateClubNetwork(Network):
36
+ def __init__(self, resolution: float = 1):
37
+ super().__init__(nx.karate_club_graph(), resolution=resolution)
38
+
39
+
40
+ class BrainNetwork(Network):
41
+ def __init__(
42
+ self,
43
+ input_data_dir: str,
44
+ input_data_name: str,
45
+ delimiter: str = " ",
46
+ resolution: int = 1,
47
+ ):
48
+ adj_matrix = np.genfromtxt(
49
+ f"{input_data_dir}/{input_data_name}.csv", delimiter=delimiter
50
+ )
51
+ super().__init__(
52
+ nx.from_numpy_matrix(adj_matrix), resolution=resolution
53
+ )
54
+
55
+
56
+ class CommunityDetectionProblem(Problem):
57
+ """
58
+ Problem class instance
59
+ - objective function for network community detection
60
+
61
+ Attributes:
62
+ ----------
63
+ cases: int
64
+ number of communities into which the graph shall be divided
65
+ (default 2)
66
+ G: networkx graph
67
+ networkx implementation of graph
68
+ B: networkx modularity matrix
69
+ networkx implementation of modularity matrix
70
+
71
+ objective_function : Expression
72
+ objective function in SymPy syntax
73
+ constraints : list[Expression]
74
+ list of problem constraints in SymPy syntax, (default [])
75
+ variables : int
76
+ number of qubits in the circuit, equal to number of nodes
77
+ in the graph
78
+ """
79
+
80
+ def __init__(self, network_data: Network, communities: int = 2) -> None:
81
+ """
82
+ Parameters
83
+ ----------
84
+ network_data: Network
85
+ representation of a network with graph and modularity matrix
86
+ communities: int
87
+ number of communities into which the graph shall be divided
88
+ (default 2)
89
+ """
90
+ self.G: nx.Graph = network_data.graph
91
+ self.B: np.ndarray = network_data.modularity_matrix
92
+ if communities < 1:
93
+ raise Exception(
94
+ "Number of communities must be greater than or equal to 1"
95
+ )
96
+
97
+ self.cases: int = communities
98
+ self.variables: tuple[
99
+ sympy.Symbol
100
+ ] = self._encode_discretes_to_one_hots()
101
+ self._set_objective_function()
102
+ self._set_one_hot_constraints(communities)
103
+
104
+ def _encode_discrete_to_one_hot(
105
+ self, discrete_variable: sympy.Symbol, case_value: int
106
+ ) -> sympy.Symbol:
107
+ discrete_id = int(str(discrete_variable)[1:])
108
+ id = discrete_id * self.cases + case_value
109
+ return sympy.symbols(f"s{id}")
110
+
111
+ def _encode_discretes_to_one_hots(self) -> tuple[sympy.Symbol]:
112
+ one_hots: tuple[sympy.Symbol] = sympy.symbols(" ".join([
113
+ str(self._encode_discrete_to_one_hot(var, case_val))
114
+ for var in self._get_discrete_variable_representation()
115
+ for case_val in range(self.cases)
116
+ ]))
117
+ return one_hots
118
+
119
+ def iter_variables_cases(self) -> Iterable[Tuple[sympy.Symbol, ...]]:
120
+ """s -> (s0,s1,s2,...sn-1), (sn,sn+1,sn+2,...s2n-1), ..."""
121
+ return zip(*[iter(self.variables)] * self.cases)
122
+
123
+ def _get_discrete_variable_representation(
124
+ self,
125
+ ) -> tuple[sympy.Symbol] | Any:
126
+ return sympy.symbols(
127
+ " ".join([f"x{i}" for i in range(len(self.G.nodes))])
128
+ )
129
+
130
+ def _set_objective_function(self) -> None:
131
+ equation: dict[VARIABLES, float] = {}
132
+ for i in self.G.nodes:
133
+ for j in range(i + 1, len(self.G.nodes)):
134
+ for case_val in range(self.cases):
135
+ x_i, x_j = sympy.symbols(f"x{i}"), sympy.symbols(f"x{j}")
136
+ s_i = str(self._encode_discrete_to_one_hot(x_i, case_val))
137
+ s_j = str(self._encode_discrete_to_one_hot(x_j, case_val))
138
+ equation[(s_i, s_j)] = self.B[i, j]
139
+
140
+ equation = {key: -1 * val for key, val in equation.items()}
141
+
142
+ self.objective_function = Expression(equation)
143
+
144
+ def _set_one_hot_constraints(self, communities: int) -> None:
145
+ ONE_HOT_CONST = -1
146
+ self.constraints: list[Expression] = []
147
+ # In the case of 1-to-1 mapping between discrete
148
+ # and binary variable values no one-hot constraints
149
+ if communities == ONE_HOT_CONST * -1:
150
+ return
151
+
152
+ dummies: Iterable[Tuple[sympy.Symbol, ...]]
153
+ for dummies in self.iter_variables_cases():
154
+ expression: Expr = cast(Expr, 0)
155
+ dummy: sympy.Symbol
156
+ for dummy in dummies:
157
+ expression += dummy
158
+ expression += ONE_HOT_CONST
159
+ self.constraints.append(Expression(expression))
160
+
161
+ def decode_solution(self, solution: dict) -> dict:
162
+ ONE_HOT_VALUE = 1.0
163
+ decoded_solution: dict = {}
164
+
165
+ for variable, value in solution.items():
166
+ id = int(variable[1:])
167
+ if value == ONE_HOT_VALUE:
168
+ case_value = id % self.cases
169
+ variable_id = id // self.cases
170
+ decoded_solution[variable_id] = case_value
171
+
172
+ return self.sort_decoded_solution(decoded_solution)
173
+
174
+ def sort_encoded_solution(self, encoded_solution: dict) -> dict:
175
+ return {
176
+ str(k): encoded_solution[str(k)]
177
+ for k in self.variables
178
+ if str(k) in encoded_solution
179
+ }
180
+
181
+ def sort_decoded_solution(self, decoded_solution: dict) -> dict:
182
+ keyorder = [int(str(v)[1:]) for v in self.variables]
183
+ return {
184
+ k: decoded_solution[k] for k in keyorder if k in decoded_solution
185
+ }
@@ -0,0 +1,176 @@
1
+ import random
2
+ import sympy
3
+ from collections import namedtuple
4
+
5
+ from typing import cast
6
+
7
+ from .base import Problem
8
+ from QHyper.hyperparameter_gen.parser import Expression
9
+
10
+ Item = namedtuple('Item', "weight value")
11
+
12
+
13
+ class Knapsack:
14
+ """Knapsack class
15
+
16
+ Attributes
17
+ ----------
18
+ max_weight: int
19
+ maximum weight of an item
20
+ max_item_value: int
21
+ maximum value of an item
22
+ items: list[Item]
23
+ list of items
24
+ """
25
+
26
+ def __init__(
27
+ self,
28
+ max_weight: int,
29
+ max_item_value: int = 10,
30
+ items_amount: int = 0,
31
+ items: list[tuple[int, int]] = []
32
+ ) -> None:
33
+ """
34
+ Parameters
35
+ ----------
36
+ max_weight: int
37
+ maximum weight of an item
38
+ max_item_value: int
39
+ maximum value of an item (default 10)
40
+ items_amount: int
41
+ items amount, used only for random knapsack (default 0)
42
+ items: list[tuple[int, int]]
43
+ set items in knapsack (default [])
44
+ """
45
+ self.items: list[Item] = []
46
+ self.max_weight: int = max_weight
47
+ self.max_item_value: int = max_item_value
48
+ if items:
49
+ self.set_knapsack(items)
50
+ else:
51
+ self.generate_knapsack(items_amount)
52
+
53
+ def generate_knapsack(self, items_amount: int) -> None:
54
+ for _ in range(items_amount):
55
+ self.items.append(Item(
56
+ random.randint(1, self.max_weight),
57
+ random.randint(1, self.max_item_value)
58
+ ))
59
+
60
+ def set_knapsack(self, items: list[tuple[int, int]]) -> None:
61
+ self.items = [Item(weight, value) for weight, value in items]
62
+
63
+ def __len__(self) -> int:
64
+ return len(self.items)
65
+
66
+
67
+ class KnapsackProblem(Problem):
68
+ """Objective function and constraints for the knapsack problem
69
+
70
+ Attributes
71
+ ----------
72
+ objective_function : Expression
73
+ objective function in SymPy syntax wrapped in Expression class
74
+ constraints : list[Expression]
75
+ list of constraints in SymPy syntax wrapped in Expression class
76
+ variables : int
77
+ number of qubits in the circuit, equals to sum of the number
78
+ of items in the knapsack the max weight of a knapsack
79
+ knapsack: Knapsack
80
+ Knapsack instance
81
+ """
82
+
83
+ def __init__(
84
+ self,
85
+ max_weight: int,
86
+ max_item_value: int = 10,
87
+ items_amount: int = 0,
88
+ items: list[tuple[int, int]] = []
89
+ ) -> None:
90
+ """
91
+ Parameters
92
+ ----------
93
+ max_weight: int
94
+ maximum weight of an item
95
+ max_item_value: int
96
+ maximum value of an item (default 10)
97
+ items_amount: int
98
+ items amount, used only for random knapsack (default 0)
99
+ items: list[tuple[int, int]]
100
+ set items in knapsack (default [])
101
+ """
102
+ self.knapsack = Knapsack(
103
+ max_weight, max_item_value, items_amount, items)
104
+ # self.variables = len(self.knapsack) + self.knapsack.max_weight
105
+ self.variables: tuple[sympy.Symbol] = sympy.symbols(' '.join(
106
+ [f'x{i}' for i
107
+ in range(len(self.knapsack) + self.knapsack.max_weight)]
108
+ ))
109
+ self._set_objective_function()
110
+ self._set_constraints()
111
+
112
+ def _set_objective_function(self) -> None:
113
+ """
114
+ Create the objective functiif items on defined in SymPy syntax
115
+ """
116
+ # xs = [f"x{i}" for i in range(len(self.knapsack))]
117
+ equation = cast(sympy.Expr, 0)
118
+ for i, x in enumerate(self.variables[:len(self.knapsack)]):
119
+ equation += self.knapsack.items[i].value*x
120
+ equation *= -1
121
+
122
+ self.objective_function: Expression = Expression(equation)
123
+
124
+ def _set_constraints(self) -> None:
125
+ """
126
+ Create constraints defined in SymPy syntax
127
+ """
128
+ xs = [self.variables[i] for i in range(len(self.knapsack))]
129
+ ys = [self.variables[i] for i in range(
130
+ len(self.knapsack), len(self.knapsack) + self.knapsack.max_weight)]
131
+ self.constraints: list[Expression] = []
132
+ equation = cast(sympy.Expr, 1)
133
+ for y in ys:
134
+ equation -= y
135
+ # equation = equation
136
+ self.constraints.append(Expression(equation))
137
+ equation = cast(sympy.Expr, 0)
138
+ for i, y in enumerate(ys):
139
+ equation += (i + 1)*y
140
+ for i, x in enumerate(xs):
141
+ equation += -(self.knapsack.items[i].weight)*x
142
+ # equation = equation
143
+ self.constraints.append(Expression(equation))
144
+
145
+ def get_score(self, result: str, penalty: float = 0) -> float:
146
+ """Returns score of the provided outcome in bits
147
+
148
+ Parameters
149
+ ----------
150
+ result : str
151
+ outcome as a string of zeros and ones
152
+ penalty : float
153
+ penalty for incorrect results (default 0)
154
+
155
+ Returns
156
+ -------
157
+ float
158
+ Returns negated sum of value of picked items or 0 if knapsack
159
+ isn't correct
160
+ """
161
+ sum = 0
162
+ weight = 0
163
+ for i, item in enumerate(self.knapsack.items):
164
+ if result[i] == '1':
165
+ sum += item.value
166
+ weight += item.weight
167
+ if weight > self.knapsack.max_weight:
168
+ return penalty
169
+
170
+ for i in range(self.knapsack.max_weight):
171
+ if result[i + len(self.knapsack)] == '1' and i + 1 != weight:
172
+ return penalty
173
+ if weight != 0 and result[weight + len(self.knapsack) - 1] != '1':
174
+ return penalty
175
+
176
+ return -sum
@@ -0,0 +1,37 @@
1
+ from typing import cast
2
+
3
+ import sympy
4
+ from QHyper.hyperparameter_gen.parser import Expression
5
+
6
+ from .base import Problem
7
+
8
+
9
+ class MaxCutProblem(Problem):
10
+ def __init__(self, edges: list[tuple[int, int]]) -> None:
11
+ self.edges = edges
12
+ self.variables = sympy.symbols(
13
+ " ".join(
14
+ [f"x{i}" for i in range(max(v for edge in edges for v in edge))]
15
+ )
16
+ )
17
+
18
+ self._set_objective_function()
19
+ self.constraints = []
20
+
21
+ def _set_objective_function(self) -> None:
22
+ equation = cast(sympy.Expr, 0)
23
+
24
+ for e in self.edges:
25
+ x_i, x_j = self.variables[e[0] - 1], self.variables[e[1] - 1]
26
+ equation -= x_i * (1 - x_j) + x_j * (1 - x_i)
27
+
28
+ self.objective_function: Expression = Expression(equation)
29
+
30
+ def get_score(self, result: str, penalty: float = 0) -> float:
31
+ sum = 0
32
+
33
+ for e in self.edges:
34
+ x_i, x_j = int(result[e[0] - 1]), int(result[e[1] - 1])
35
+ sum += x_i * (1 - x_j) + x_j * (1 - x_i)
36
+
37
+ return sum
QHyper/problems/tsp.py ADDED
@@ -0,0 +1,190 @@
1
+ import itertools
2
+
3
+ import numpy as np
4
+ import sympy
5
+
6
+ from typing import cast
7
+
8
+ from .base import Problem
9
+ from ..hyperparameter_gen.parser import Expression
10
+
11
+
12
+ class TSP:
13
+ """Traveling Salesman Problem
14
+
15
+ Attributes
16
+ ----------
17
+ number_of_cities : int
18
+ cities count
19
+ coords_range : tuple[int, int]
20
+ range of coordinates of the cities
21
+ cities_coords : list[tuple[float, float]]
22
+ coordinates of the cities
23
+ distance_matrix : list[list[float]]
24
+ matrix of distances between cities
25
+ normalized_distance_matrix : list[list[float]]
26
+ normalized to (0, 1] matrix of distances between cities
27
+ """
28
+
29
+ def __init__(
30
+ self,
31
+ number_of_cities: int,
32
+ coords_range: tuple[int, int] = (0, 10000)
33
+ ) -> None:
34
+ """
35
+ Parameters
36
+ ----------
37
+ number_of_cities : int
38
+ cities count
39
+ coords_range : tuple[float, float]
40
+ range of coordinates of the cities
41
+ """
42
+ self.number_of_cities: int = number_of_cities
43
+ self.coords_range: tuple[int, int] = coords_range
44
+ self.cities_coords: list[tuple[float, float]] = self.get_cities()
45
+ self.distance_matrix: list[
46
+ list[float]] = self.calculate_distance_matrix()
47
+ self.normalized_distance_matrix: list[
48
+ list[float]] = self.normalize_distance_matrix()
49
+
50
+ def get_cities(self) -> list[tuple[float, float]]:
51
+ cities_coords = np.random.randint(
52
+ self.coords_range[0], self.coords_range[1],
53
+ size=(self.number_of_cities, 2))
54
+ return cast(list[tuple[float, float]], cities_coords)
55
+
56
+ def calculate_distance_between_points(
57
+ self, point_A: tuple[float, float], point_B: tuple[float, float]
58
+ ) -> float:
59
+ return cast(
60
+ float,
61
+ np.sqrt((point_A[0] - point_B[0]) ** 2
62
+ + (point_A[1] - point_B[1]) ** 2)
63
+ )
64
+
65
+ def calculate_distance_matrix(self) -> list[list[float]]:
66
+ distance_matrix = np.zeros(
67
+ (self.number_of_cities, self.number_of_cities))
68
+ for i in range(self.number_of_cities):
69
+ for j in range(i, self.number_of_cities):
70
+ distance_matrix[i][j] = self.calculate_distance_between_points(
71
+ self.cities_coords[i], self.cities_coords[j])
72
+ distance_matrix[j][i] = distance_matrix[i][j]
73
+ return cast(list[list[float]], distance_matrix)
74
+
75
+ def normalize_distance_matrix(self) -> list[list[float]]:
76
+ return cast(
77
+ list[list[float]],
78
+ np.divide(self.distance_matrix, np.max(self.distance_matrix))
79
+ )
80
+
81
+
82
+ class TSPProblem(Problem):
83
+ """Class defining objective function and constraints for TSP problem
84
+
85
+ Attributes
86
+ ----------
87
+ objective_function : Expression
88
+ objective function in SymPy syntax
89
+ constraints : list[Expression]
90
+ list of constraints in SymPy syntax
91
+ variables : int
92
+ number of qubits in the circuit, equals to number of cities
93
+ to the power of 2
94
+ tsp_instance: TSP
95
+ TSP problem instace
96
+ """
97
+
98
+ def __init__(
99
+ self,
100
+ number_of_cities: int
101
+ ) -> None:
102
+ """
103
+ Parameters
104
+ ----------
105
+ number_of_cities : int
106
+ number of cities
107
+ """
108
+
109
+ self.tsp_instance = TSP(number_of_cities)
110
+ self.variables: tuple[sympy.Symbol] = sympy.symbols(
111
+ ' '.join([f'x{i}' for i in range(number_of_cities ** 2)]))
112
+ self._set_objective_function()
113
+ self._set_constraints()
114
+
115
+ def _calc_bit(self, i: int, t: int) -> int:
116
+ return i + t * self.tsp_instance.number_of_cities
117
+
118
+ def _set_objective_function(self) -> None:
119
+ equation = cast(sympy.Expr, 0)
120
+ for i, j in itertools.permutations(
121
+ range(0, self.tsp_instance.number_of_cities), 2
122
+ ):
123
+ curr = 0
124
+ for t in range(self.tsp_instance.number_of_cities):
125
+ inc_t = t + 1
126
+ if inc_t == self.tsp_instance.number_of_cities:
127
+ inc_t = 0
128
+ curr += (
129
+ self.variables[self._calc_bit(i, t)]
130
+ * self.variables[self._calc_bit(j, inc_t)]
131
+ )
132
+ equation += (
133
+ self.tsp_instance.normalized_distance_matrix[i][j] * curr
134
+ )
135
+ self.objective_function: Expression = Expression(equation)
136
+
137
+ def _set_constraints(self) -> None:
138
+ self.constraints: list[Expression] = []
139
+ for i in range(self.tsp_instance.number_of_cities):
140
+ equation = cast(sympy.Expr, 1)
141
+ for t in range(self.tsp_instance.number_of_cities):
142
+ equation -= self.variables[self._calc_bit(i, t)]
143
+ self.constraints.append(Expression(equation))
144
+
145
+ for t in range(self.tsp_instance.number_of_cities):
146
+ equation = cast(sympy.Expr, 1)
147
+ for i in range(self.tsp_instance.number_of_cities):
148
+ equation -= self.variables[self._calc_bit(i, t)]
149
+ self.constraints.append(Expression(equation))
150
+
151
+ def _get_distance(self, key: str) -> float:
152
+ results = np.array_split(list(key), self.tsp_instance.number_of_cities)
153
+ dist: float = 0
154
+ tab = []
155
+ for result in results:
156
+ tab.append(list(result).index('1'))
157
+
158
+ for i in range(len(tab)):
159
+ j = i - 1
160
+ dist += (
161
+ self.tsp_instance.normalized_distance_matrix[tab[i]][tab[j]]
162
+ )
163
+ return dist
164
+
165
+ def _valid(self, result: str) -> bool:
166
+ result_bools = np.reshape(
167
+ list(result),
168
+ (-1, self.tsp_instance.number_of_cities)
169
+ ).astype(np.bool8)
170
+ return cast(bool, (
171
+ result_bools.sum(0) == 1).all()
172
+ and (result_bools.sum(1) == 1).all()
173
+ )
174
+
175
+ def get_score(self, result: str, penalty: float = 0) -> float:
176
+ """Returns length of the route based on provided outcome in bits.
177
+
178
+ Parameters
179
+ ----------
180
+ result : str
181
+ route as a string of zeros and ones
182
+
183
+ Returns
184
+ -------
185
+ float
186
+ Returns length of the route, or 0 if route wasn't correct
187
+ """
188
+ if not self._valid(result):
189
+ return penalty # Bigger value that possible distance
190
+ return self._get_distance(result)