randommm commited on
Commit
02085f0
·
1 Parent(s): f22afc5
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. facility_location/__pycache__/__init__.cpython-310.pyc +0 -0
  2. facility_location/__pycache__/__init__.cpython-37.pyc +0 -0
  3. facility_location/__pycache__/__init__.cpython-39.pyc +0 -0
  4. facility_location/__pycache__/eval.cpython-310.pyc +0 -0
  5. facility_location/__pycache__/eval.cpython-39.pyc +0 -0
  6. facility_location/__pycache__/multi_eval.cpython-310.pyc +0 -0
  7. facility_location/__pycache__/multi_eval.cpython-39.pyc +0 -0
  8. facility_location/__pycache__/train.cpython-310.pyc +0 -0
  9. facility_location/__pycache__/train.cpython-37.pyc +0 -0
  10. facility_location/__pycache__/train.cpython-39.pyc +0 -0
  11. facility_location/agent/__pycache__/__init__.cpython-39.pyc +0 -0
  12. facility_location/agent/__pycache__/features_extractor.cpython-39.pyc +0 -0
  13. facility_location/agent/__pycache__/policy.cpython-39.pyc +0 -0
  14. facility_location/agent/__pycache__/solver.cpython-39.pyc +0 -0
  15. facility_location/agent/ga.py +86 -0
  16. facility_location/agent/heuristic.py +72 -0
  17. facility_location/agent/metaheuristic.py +218 -0
  18. facility_location/agent/tests/ga.ipynb +0 -0
  19. facility_location/agent/tests/solver.ipynb +142 -0
  20. facility_location/cfg/2-nearest.yaml +61 -0
  21. facility_location/cfg/3-nearest.yaml +63 -0
  22. facility_location/cfg/NY.yaml +65 -0
  23. facility_location/cfg/dg.yaml +63 -0
  24. facility_location/cfg/gainloss.yaml +63 -0
  25. facility_location/cfg/multi.yaml +69 -0
  26. facility_location/cfg/plot.yaml +4 -4
  27. facility_location/cfg/popstar.yaml +63 -0
  28. facility_location/cfg/scale1.yaml +63 -0
  29. facility_location/cfg/scale5.yaml +63 -0
  30. facility_location/cfg/tabu0.yaml +63 -0
  31. facility_location/cfg/tabu5.yaml +63 -0
  32. facility_location/cfg/uniform.yaml +63 -0
  33. facility_location/cfg/uniform_debug.yaml +64 -0
  34. facility_location/env/__pycache__/__init__.cpython-39.pyc +0 -0
  35. facility_location/env/__pycache__/facility_location_client.cpython-310.pyc +0 -0
  36. facility_location/env/__pycache__/facility_location_client.cpython-39.pyc +0 -0
  37. facility_location/env/__pycache__/obs_extractor.cpython-310.pyc +0 -0
  38. facility_location/env/__pycache__/obs_extractor.cpython-39.pyc +0 -0
  39. facility_location/env/__pycache__/pmp.cpython-310.pyc +0 -0
  40. facility_location/env/__pycache__/pmp.cpython-39.pyc +0 -0
  41. facility_location/env/facility_location_client.py +49 -42
  42. facility_location/env/obs_extractor.py +19 -1
  43. facility_location/env/pmp.py +217 -114
  44. facility_location/env/tests/p-median.ipynb +0 -0
  45. facility_location/env/tests/render.ipynb +0 -0
  46. facility_location/env/utils/env_test.ipynb +0 -0
  47. facility_location/eval.py +234 -0
  48. facility_location/multi_eval.py +126 -15
  49. facility_location/solutions.pkl +0 -3
  50. facility_location/test.ipynb +425 -0
facility_location/__pycache__/__init__.cpython-310.pyc ADDED
Binary file (144 Bytes). View file
 
facility_location/__pycache__/__init__.cpython-37.pyc ADDED
Binary file (138 Bytes). View file
 
facility_location/__pycache__/__init__.cpython-39.pyc CHANGED
Binary files a/facility_location/__pycache__/__init__.cpython-39.pyc and b/facility_location/__pycache__/__init__.cpython-39.pyc differ
 
facility_location/__pycache__/eval.cpython-310.pyc ADDED
Binary file (6.27 kB). View file
 
facility_location/__pycache__/eval.cpython-39.pyc ADDED
Binary file (5.23 kB). View file
 
facility_location/__pycache__/multi_eval.cpython-310.pyc ADDED
Binary file (5.57 kB). View file
 
facility_location/__pycache__/multi_eval.cpython-39.pyc DELETED
Binary file (3.13 kB)
 
facility_location/__pycache__/train.cpython-310.pyc ADDED
Binary file (8.45 kB). View file
 
facility_location/__pycache__/train.cpython-37.pyc ADDED
Binary file (7.06 kB). View file
 
facility_location/__pycache__/train.cpython-39.pyc ADDED
Binary file (7.71 kB). View file
 
facility_location/agent/__pycache__/__init__.cpython-39.pyc CHANGED
Binary files a/facility_location/agent/__pycache__/__init__.cpython-39.pyc and b/facility_location/agent/__pycache__/__init__.cpython-39.pyc differ
 
facility_location/agent/__pycache__/features_extractor.cpython-39.pyc CHANGED
Binary files a/facility_location/agent/__pycache__/features_extractor.cpython-39.pyc and b/facility_location/agent/__pycache__/features_extractor.cpython-39.pyc differ
 
facility_location/agent/__pycache__/policy.cpython-39.pyc CHANGED
Binary files a/facility_location/agent/__pycache__/policy.cpython-39.pyc and b/facility_location/agent/__pycache__/policy.cpython-39.pyc differ
 
facility_location/agent/__pycache__/solver.cpython-39.pyc CHANGED
Binary files a/facility_location/agent/__pycache__/solver.cpython-39.pyc and b/facility_location/agent/__pycache__/solver.cpython-39.pyc differ
 
facility_location/agent/ga.py ADDED
@@ -0,0 +1,86 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ import pygad
3
+
4
+ from facility_location.env import EvalPMPEnv
5
+ from facility_location.utils import Config
6
+
7
+
8
+ class PMPGA:
9
+ def __init__(self, cfg: Config, env: EvalPMPEnv):
10
+ ga_specs = cfg.ga_specs
11
+ self._num_generations = ga_specs['num_generations']
12
+ self._num_parents_mating = ga_specs['num_parents_mating']
13
+ self._sol_per_pop = ga_specs['sol_per_pop']
14
+ self._parent_selection_type = ga_specs['parent_selection_type']
15
+ self._crossover_probability = ga_specs['crossover_probability']
16
+ self._mutation_probability = ga_specs['mutation_probability']
17
+
18
+ self.env = env
19
+ self.seed = cfg.seed
20
+ self._np_random = np.random.default_rng(cfg.seed)
21
+
22
+ def solve(self) -> np.ndarray:
23
+ _, _, n, p = self.env.get_instance()
24
+
25
+ def fitness_func(solution: np.ndarray, solution_idx: int) -> float:
26
+ solution = solution.astype(bool)
27
+ reward = self.env.evaluate(solution)
28
+ fitness = -reward
29
+ return fitness
30
+
31
+ def crossover_func(parents, offspring_size, ga_instance):
32
+ offsprings = []
33
+ idx = 0
34
+ while len(offsprings) != offspring_size[0]:
35
+ offspring = np.zeros(n, dtype=np.int32)
36
+
37
+ parent1 = parents[idx % parents.shape[0], :].copy()
38
+ parent2 = parents[(idx + 1) % parents.shape[0], :].copy()
39
+ facility_locations = np.arange(n)[(parent1 + parent2) > 0]
40
+ random_indices = self._np_random.choice(facility_locations, p, replace=False)
41
+ offspring[random_indices] = 1
42
+ offsprings.append(offspring)
43
+
44
+ idx += 1
45
+
46
+ return np.array(offsprings)
47
+
48
+ def mutation_func(offsprings, ga_instance):
49
+
50
+ for offspring_idx in range(offsprings.shape[0]):
51
+ offspring = offsprings[offspring_idx]
52
+ facility_locations = np.arange(n)[offspring == 1]
53
+ vacant_locations = np.arange(n)[offspring == 0]
54
+ old_facility_location = self._np_random.choice(facility_locations)
55
+ new_facility_location = self._np_random.choice(vacant_locations)
56
+
57
+ offsprings[offspring_idx, old_facility_location] = 0
58
+ offsprings[offspring_idx, new_facility_location] = 1
59
+
60
+ return offsprings
61
+
62
+ initial_population = self._generate_initial_population(n, p)
63
+ ga_instance = pygad.GA(num_generations=self._num_generations,
64
+ num_parents_mating=self._num_parents_mating,
65
+ fitness_func=fitness_func,
66
+ initial_population=initial_population,
67
+ sol_per_pop=self._sol_per_pop,
68
+ gene_type=np.int32,
69
+ parent_selection_type=self._parent_selection_type,
70
+ crossover_type=crossover_func,
71
+ crossover_probability=self._crossover_probability,
72
+ mutation_type=mutation_func,
73
+ mutation_probability=self._mutation_probability,
74
+ stop_criteria="saturate_20",
75
+ random_seed=self.seed)
76
+ ga_instance.run()
77
+ best_solution, _, _ = ga_instance.best_solution()
78
+ best_solution = best_solution.astype(bool)
79
+ return best_solution
80
+
81
+ def _generate_initial_population(self, n: int, p: int) -> np.ndarray:
82
+ initial_population = np.zeros((self._sol_per_pop, n), dtype=np.int32)
83
+ for i in range(self._sol_per_pop):
84
+ random_indices = self._np_random.choice(n, p, replace=False)
85
+ initial_population[i, random_indices] = 1
86
+ return initial_population
facility_location/agent/heuristic.py ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import subprocess
2
+ import tempfile
3
+
4
+ import numpy as np
5
+
6
+ from facility_location.env import EvalPMPEnv
7
+
8
+
9
+ class HeuristicRandom:
10
+ def __init__(self, seed: int, env: EvalPMPEnv):
11
+ self._np_random = np.random.default_rng(seed)
12
+
13
+ self.env = env
14
+
15
+ def solve(self):
16
+ _, _, n, p = self.env.get_instance()
17
+ solution = np.zeros(n, dtype=bool)
18
+ solution[self._np_random.choice(n, p, replace=False)] = True
19
+ return solution
20
+
21
+
22
+ class HeuristicGreedy:
23
+ def __init__(self, env: EvalPMPEnv):
24
+ self.env = env
25
+
26
+ def solve(self):
27
+ solution = self.env.get_initial_solution()
28
+ return solution
29
+
30
+
31
+ class HeuristicFastInterchange:
32
+ def __init__(self, env: EvalPMPEnv):
33
+ self.env = env
34
+
35
+ def solve(self):
36
+ temp_input_file = tempfile.NamedTemporaryFile(mode='w', suffix='.pmm')
37
+ temp_initsol_file = tempfile.NamedTemporaryFile(mode='w')
38
+ temp_output_file = tempfile.NamedTemporaryFile(mode='r')
39
+ _, _, n, p = self.env.get_instance()
40
+ _, cost_matrix = self.env.get_distance_and_cost()
41
+ initial_solution = self.env.get_initial_solution()
42
+ initial_solution = np.where(initial_solution)[0] + 1
43
+ label_initial_solution = np.column_stack([np.zeros(len(initial_solution)), initial_solution])
44
+ i, j = np.indices(cost_matrix.shape)
45
+ triplets = np.column_stack([ar.ravel() for ar in (i+1, j+1, cost_matrix)])
46
+ label_triplets = np.column_stack([np.zeros(len(triplets)), triplets])
47
+ try:
48
+ np.savetxt(temp_input_file.name, label_triplets, fmt='%d %d %d %.8f',
49
+ delimiter=' ',
50
+ header=f'p {n} {n}',
51
+ comments='')
52
+ np.savetxt(temp_initsol_file.name, label_initial_solution, fmt='%d %d', delimiter=' ')
53
+ subprocess.run(["thirdparty/popstar/popstar", temp_input_file.name,
54
+ "-p", f"{p}",
55
+ "-output", temp_output_file.name,
56
+ "-nograsp",
57
+ "-run_ls",
58
+ "-inputsol", temp_initsol_file.name,
59
+ "-ch", "rgreedy:1",
60
+ "-elite", "0"],
61
+ stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT)
62
+ fi_solution = np.loadtxt(temp_output_file.name, skiprows=4, max_rows=p,
63
+ dtype={'names': ('facility', 'index'),
64
+ 'formats': ('S1', 'i4')})
65
+ solution = np.full(n, False)
66
+ solution[fi_solution['index'] - 1] = True
67
+ finally:
68
+ temp_input_file.close()
69
+ temp_initsol_file.close()
70
+ temp_output_file.close()
71
+
72
+ return solution
facility_location/agent/metaheuristic.py ADDED
@@ -0,0 +1,218 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import random
2
+ import subprocess
3
+ import tempfile
4
+
5
+ import numpy as np
6
+
7
+ from facility_location.env import EvalPMPEnv
8
+ from facility_location.utils import Config
9
+
10
+
11
+ class TabuSearch:
12
+ def __init__(self, cfg: Config, env: EvalPMPEnv):
13
+ ts_specs = cfg.ts_specs
14
+ self.max_steps_scale = ts_specs['max_steps_scale']
15
+ self.stable_iterations_scale = ts_specs['stable_iterations_scale']
16
+
17
+ self.env = env
18
+
19
+ def init_variables(self, n: int, p: int):
20
+ self.max_iterations = max(self.max_steps_scale * n, 100)
21
+ self.stable_iterations = round(self.stable_iterations_scale * self.max_iterations)
22
+ self.iteration = 0
23
+ self.best_value = np.inf
24
+ self.slack = 0
25
+ self.add_time = np.full(n, -np.inf)
26
+ self.freq = np.zeros(n)
27
+ self.S = np.full(n, False)
28
+ self.NS = np.full(n, True)
29
+ self.k = self.distances.max()
30
+ self.last_improvement = self.iteration
31
+ self.tabu_time = random.randint(1, p + 1)
32
+
33
+ def solve(self):
34
+ _, self.demands, self.n, self.p = self.env.get_instance()
35
+ self.distances, self.cost_matrix = self.env.get_distance_and_cost()
36
+ self.init_variables(self.n, self.p)
37
+ _, solution = self.run()
38
+ return solution
39
+
40
+ def run(self):
41
+ while np.count_nonzero(self.S) < self.p:
42
+ new_value = self.add()
43
+ self.best_value = new_value
44
+
45
+ while self.iteration < self.max_iterations:
46
+ new_value = self.choose_move()
47
+ self.iteration += 1
48
+
49
+ if np.count_nonzero(self.S) == self.p and new_value < self.best_value:
50
+ self.best_value = new_value
51
+ self.slack = 0
52
+ self.last_improvement = self.iteration
53
+ else:
54
+ iteration_since_last_improvement = self.iteration - self.last_improvement
55
+ if iteration_since_last_improvement % (self.stable_iterations * 2) == 0:
56
+ self.slack += 1
57
+ if iteration_since_last_improvement % round(self.stable_iterations / 2) == 0:
58
+ self.tabu_time = random.randint(1, self.p + 1)
59
+ if np.count_nonzero(self.S) == self.p and iteration_since_last_improvement >= self.stable_iterations:
60
+ self.iteration = self.max_iterations
61
+
62
+ return self.best_value, self.S
63
+
64
+ def evaluate(self, v_candidate, m_type):
65
+ if m_type == 'ADD':
66
+ self.S[v_candidate] = True
67
+ self.NS[v_candidate] = False
68
+ else:
69
+ self.S[v_candidate] = False
70
+ self.NS[v_candidate] = True
71
+
72
+ cost = self.env.evaluate(self.S)
73
+ if m_type == 'ADD':
74
+ v_candidate_index_in_S = np.where(np.arange(self.n)[self.S] == v_candidate)[0][0]
75
+ assigned_customers = self.cost_matrix[:, self.S].argmin(axis=-1) == v_candidate_index_in_S
76
+ penalty = self.k * self.freq[v_candidate] * self.demands[assigned_customers].sum()
77
+ cost += penalty
78
+
79
+ if m_type == 'ADD':
80
+ self.S[v_candidate] = False
81
+ self.NS[v_candidate] = True
82
+ else:
83
+ self.S[v_candidate] = True
84
+ self.NS[v_candidate] = False
85
+
86
+ return cost
87
+
88
+ def is_tabu(self, v):
89
+ return self.add_time[v] >= self.iteration - self.tabu_time
90
+
91
+ def flip_coin(self):
92
+ return random.random() < 0.5
93
+
94
+ def add(self):
95
+ new_value = np.inf
96
+ best_candidate = -1
97
+ candidates = np.where(self.NS)[0]
98
+ for v in candidates:
99
+ if self.is_tabu(v):
100
+ continue
101
+ value = self.evaluate(v, 'ADD')
102
+ if value < new_value:
103
+ new_value = value
104
+ best_candidate = v
105
+
106
+ if best_candidate >= 0:
107
+ self.add_time[best_candidate] = self.iteration
108
+ self.S[best_candidate] = True
109
+ self.NS[best_candidate] = False
110
+ self.freq[best_candidate] += 1
111
+
112
+ return new_value
113
+
114
+ def aspiration_criteria(self, value):
115
+ return value < self.best_value
116
+
117
+ def drop(self):
118
+ new_value = np.inf
119
+ best_candidate = -1
120
+ candidates = np.where(self.S)[0]
121
+ for v in candidates:
122
+ value = self.evaluate(v, 'DROP')
123
+ if (not self.is_tabu(v) or self.aspiration_criteria(value)) and value < new_value:
124
+ new_value = value
125
+ best_candidate = v
126
+
127
+ if best_candidate >= 0:
128
+ self.NS[best_candidate] = True
129
+ self.S[best_candidate] = False
130
+
131
+ return new_value
132
+
133
+ def choose_move(self):
134
+ if np.count_nonzero(self.S) < self.p - self.slack:
135
+ return self.add()
136
+ elif np.count_nonzero(self.S) > self.p + self.slack:
137
+ return self.drop()
138
+ elif self.flip_coin() and np.count_nonzero(self.S) > 0:
139
+ return self.drop()
140
+ else:
141
+ return self.add()
142
+
143
+
144
+ class VNS:
145
+ def __init__(self, env: EvalPMPEnv):
146
+ self.env = env
147
+
148
+ def solve(self):
149
+ temp_input_file = tempfile.NamedTemporaryFile(mode='w', suffix='.pmm')
150
+ temp_output_file = tempfile.NamedTemporaryFile(mode='r')
151
+ _, _, n, p = self.env.get_instance()
152
+ _, cost_matrix = self.env.get_distance_and_cost()
153
+ i, j = np.indices(cost_matrix.shape)
154
+ triplets = np.column_stack([ar.ravel() for ar in (i+1, j+1, cost_matrix)])
155
+ label_triplets = np.column_stack([np.zeros(len(triplets)), triplets])
156
+ try:
157
+ np.savetxt(temp_input_file.name, label_triplets, fmt='%d %d %d %.8f',
158
+ delimiter=' ',
159
+ header=f'p {n} {n}',
160
+ comments='')
161
+ subprocess.run(["thirdparty/popstar/popstar", temp_input_file.name,
162
+ "-p", f"{p}",
163
+ "-output", temp_output_file.name,
164
+ "-nograsp",
165
+ "-run_vns",
166
+ "-ch", "rgreedy:1",
167
+ "-elite", "0"],
168
+ stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT)
169
+ vns_solution = np.loadtxt(temp_output_file.name, skiprows=4, max_rows=p,
170
+ dtype={'names': ('facility', 'index'),
171
+ 'formats': ('S1', 'i4')})
172
+ solution = np.full(n, False)
173
+ solution[vns_solution['index'] - 1] = True
174
+ finally:
175
+ temp_input_file.close()
176
+ temp_output_file.close()
177
+
178
+ return solution
179
+
180
+
181
+ class POPSTAR:
182
+ def __init__(self, cfg: Config, env: EvalPMPEnv):
183
+ popstar_specs = cfg.popstar_specs
184
+ self.graspit = popstar_specs['graspit']
185
+ self.elite = popstar_specs['elite']
186
+
187
+ self.env = env
188
+
189
+ def solve(self):
190
+ temp_input_file = tempfile.NamedTemporaryFile(mode='w', suffix='.pmm')
191
+ temp_output_file = tempfile.NamedTemporaryFile(mode='r')
192
+ _, _, n, p = self.env.get_instance()
193
+ _, cost_matrix = self.env.get_distance_and_cost()
194
+ i, j = np.indices(cost_matrix.shape)
195
+ triplets = np.column_stack([ar.ravel() for ar in (i+1, j+1, cost_matrix)])
196
+ label_triplets = np.column_stack([np.zeros(len(triplets)), triplets])
197
+ try:
198
+ np.savetxt(temp_input_file.name, label_triplets, fmt='%d %d %d %.8f',
199
+ delimiter=' ',
200
+ header=f'p {n} {n}',
201
+ comments='')
202
+ subprocess.run(["thirdparty/popstar/popstar", temp_input_file.name,
203
+ "-p", f"{p}",
204
+ "-output", temp_output_file.name,
205
+ "-graspit", f"{self.graspit}",
206
+ "-elite", f"{self.elite}"],
207
+ stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT)
208
+ popstar_solution = np.loadtxt(temp_output_file.name, skiprows=4, max_rows=p,
209
+ dtype={'names': ('facility', 'index'),
210
+ 'formats': ('S1', 'i4')})
211
+ solution = np.full(n, False)
212
+ solution[popstar_solution['index'] - 1] = True
213
+ finally:
214
+ temp_input_file.close()
215
+ temp_output_file.close()
216
+
217
+ return solution
218
+
facility_location/agent/tests/ga.ipynb ADDED
The diff for this file is too large to render. See raw diff
 
facility_location/agent/tests/solver.ipynb ADDED
@@ -0,0 +1,142 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "code",
5
+ "execution_count": 1,
6
+ "id": "5880eb74",
7
+ "metadata": {},
8
+ "outputs": [],
9
+ "source": [
10
+ "import numpy as np\n",
11
+ "from sklearn.metrics import pairwise_distances\n",
12
+ "import time\n",
13
+ "from tqdm import tqdm\n",
14
+ "\n",
15
+ "from spopt.locate import PMedian\n",
16
+ "import pulp"
17
+ ]
18
+ },
19
+ {
20
+ "cell_type": "code",
21
+ "execution_count": 2,
22
+ "id": "abaedea7",
23
+ "metadata": {},
24
+ "outputs": [],
25
+ "source": [
26
+ "rng = np.random.default_rng()"
27
+ ]
28
+ },
29
+ {
30
+ "cell_type": "code",
31
+ "execution_count": 3,
32
+ "id": "569623ca",
33
+ "metadata": {},
34
+ "outputs": [],
35
+ "source": [
36
+ "def pulp_solve(points, demands, p, solver):\n",
37
+ " distance_matrix = pairwise_distances(points)\n",
38
+ " cost_matrix = distance_matrix * demands[:, None]\n",
39
+ " pmedian_from_cost_matrix = PMedian.from_cost_matrix(cost_matrix, demands, p_facilities=p)\n",
40
+ " pmedian_from_cost_matrix = pmedian_from_cost_matrix.solve(solver)\n",
41
+ " return np.array([len(temp) > 0 for temp in pmedian_from_cost_matrix.fac2cli], dtype=bool)"
42
+ ]
43
+ },
44
+ {
45
+ "cell_type": "code",
46
+ "execution_count": 11,
47
+ "id": "a67e61dc",
48
+ "metadata": {},
49
+ "outputs": [
50
+ {
51
+ "name": "stderr",
52
+ "output_type": "stream",
53
+ "text": [
54
+ "100%|██████████| 2/2 [00:19<00:00, 9.79s/it]"
55
+ ]
56
+ },
57
+ {
58
+ "name": "stdout",
59
+ "output_type": "stream",
60
+ "text": [
61
+ "time: 9.795565128326416\n"
62
+ ]
63
+ },
64
+ {
65
+ "name": "stderr",
66
+ "output_type": "stream",
67
+ "text": [
68
+ "\n"
69
+ ]
70
+ }
71
+ ],
72
+ "source": [
73
+ "solver = pulp.PULP_CBC_CMD(msg=False)\n",
74
+ "solver = pulp.GLPK_CMD(msg=False)\n",
75
+ "solver = pulp.GUROBI(msg=False)\n",
76
+ "#solver = pulp.GUROBI_CMD(msg=False)\n",
77
+ "n = 200\n",
78
+ "p = 4\n",
79
+ "num_exp = 2\n",
80
+ "all_points = rng.uniform(size=(num_exp, n, 2))\n",
81
+ "all_demands = rng.random(size=(num_exp, n))\n",
82
+ "start_time = time.time()\n",
83
+ "for idx in tqdm(range(num_exp)):\n",
84
+ " points = all_points[idx]\n",
85
+ " demands = all_demands[idx]\n",
86
+ " solution = pulp_solve(points, demands, p, solver)\n",
87
+ "print(f'time: {(time.time() - start_time)/num_exp}')"
88
+ ]
89
+ },
90
+ {
91
+ "cell_type": "code",
92
+ "execution_count": 8,
93
+ "id": "679b6f4b",
94
+ "metadata": {},
95
+ "outputs": [
96
+ {
97
+ "name": "stdout",
98
+ "output_type": "stream",
99
+ "text": [
100
+ "solvers: ['GLPK_CMD', 'PYGLPK', 'CPLEX_CMD', 'CPLEX_PY', 'GUROBI', 'GUROBI_CMD', 'MOSEK', 'XPRESS', 'XPRESS', 'XPRESS_PY', 'PULP_CBC_CMD', 'COIN_CMD', 'COINMP_DLL', 'CHOCO_CMD', 'MIPCL_CMD', 'SCIP_CMD', 'HiGHS_CMD']\n",
101
+ "available solvers: ['GLPK_CMD', 'GUROBI', 'GUROBI_CMD', 'PULP_CBC_CMD']\n"
102
+ ]
103
+ }
104
+ ],
105
+ "source": [
106
+ "solver_list = pulp.listSolvers()\n",
107
+ "available_solver_list = pulp.listSolvers(onlyAvailable=True)\n",
108
+ "print(f'solvers: {solver_list}')\n",
109
+ "print(f'available solvers: {available_solver_list}')"
110
+ ]
111
+ },
112
+ {
113
+ "cell_type": "code",
114
+ "execution_count": null,
115
+ "id": "143a6eb9",
116
+ "metadata": {},
117
+ "outputs": [],
118
+ "source": []
119
+ }
120
+ ],
121
+ "metadata": {
122
+ "kernelspec": {
123
+ "display_name": "Python 3",
124
+ "language": "python",
125
+ "name": "python3"
126
+ },
127
+ "language_info": {
128
+ "codemirror_mode": {
129
+ "name": "ipython",
130
+ "version": 3
131
+ },
132
+ "file_extension": ".py",
133
+ "mimetype": "text/x-python",
134
+ "name": "python",
135
+ "nbconvert_exporter": "python",
136
+ "pygments_lexer": "ipython3",
137
+ "version": "3.9.7"
138
+ }
139
+ },
140
+ "nbformat": 4,
141
+ "nbformat_minor": 5
142
+ }
facility_location/cfg/2-nearest.yaml ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # env
2
+ env_specs:
3
+ min_n: 20
4
+ max_n: 50
5
+ min_p_ratio: 0.1
6
+ max_p_ratio: 0.4
7
+ max_steps_scale: 1
8
+ tabu_time: 3
9
+ tabu_stable_steps_scale: 0.1
10
+ popstar: false
11
+
12
+ # evaluation
13
+ eval_specs:
14
+ seed: 12345
15
+ val_num_cases: 100
16
+ test_num_cases: 100
17
+ val_np: !!python/tuple [50, 10]
18
+ test_np:
19
+ - !!python/tuple [50, 5]
20
+ - !!python/tuple [100, 10]
21
+ - !!python/tuple [400, 50]
22
+
23
+ # agent
24
+ agent_specs:
25
+ policy_feature_dim: 32
26
+ value_feature_dim: 32
27
+ policy_hidden_units: !!python/tuple [32, 32, 1]
28
+ value_hidden_units: !!python/tuple [32, 32, 1]
29
+
30
+ # mlp
31
+ mlp_specs:
32
+ hidden_units: !!python/tuple [32, 32]
33
+
34
+ gnn_specs:
35
+ num_gnn_layers: 2
36
+ node_dim: 32
37
+
38
+
39
+ # ts
40
+ ts_specs:
41
+ max_steps_scale: 2
42
+ stable_iterations_scale: 0.2
43
+
44
+
45
+ # popstar
46
+ popstar_specs:
47
+ graspit: 32
48
+ elite: 10
49
+
50
+
51
+ # ga
52
+ ga_specs:
53
+ num_generations: 100
54
+ num_parents_mating: 50
55
+ sol_per_pop: 100
56
+ parent_selection_type: sss
57
+ crossover_probability: 0.8
58
+ mutation_probability: 0.1
59
+
60
+
61
+
facility_location/cfg/3-nearest.yaml ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # env
2
+ env_specs:
3
+ region:
4
+ min_n: 20
5
+ max_n: 50
6
+ min_p_ratio: 0.1
7
+ max_p_ratio: 0.4
8
+ max_steps_scale: 3
9
+ tabu_time: 3
10
+ tabu_stable_steps_scale: 0.1
11
+ popstar: false
12
+
13
+ # evaluation
14
+ eval_specs:
15
+ region:
16
+ seed: 12345
17
+ val_num_cases: 100
18
+ test_num_cases: 100
19
+ val_np: !!python/tuple [50, 10]
20
+ test_np:
21
+ - !!python/tuple [50, 5]
22
+ - !!python/tuple [100, 10]
23
+ - !!python/tuple [400, 50]
24
+
25
+ # agent
26
+ agent_specs:
27
+ policy_feature_dim: 32
28
+ value_feature_dim: 32
29
+ policy_hidden_units: !!python/tuple [32, 32, 1]
30
+ value_hidden_units: !!python/tuple [32, 32, 1]
31
+
32
+ # mlp
33
+ mlp_specs:
34
+ hidden_units: !!python/tuple [32, 32]
35
+
36
+ gnn_specs:
37
+ num_gnn_layers: 2
38
+ node_dim: 32
39
+
40
+
41
+ # ts
42
+ ts_specs:
43
+ max_steps_scale: 2
44
+ stable_iterations_scale: 0.2
45
+
46
+
47
+ # popstar
48
+ popstar_specs:
49
+ graspit: 32
50
+ elite: 10
51
+
52
+
53
+ # ga
54
+ ga_specs:
55
+ num_generations: 100
56
+ num_parents_mating: 50
57
+ sol_per_pop: 100
58
+ parent_selection_type: sss
59
+ crossover_probability: 0.8
60
+ mutation_probability: 0.1
61
+
62
+
63
+
facility_location/cfg/NY.yaml ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # env
2
+ env_specs:
3
+ region: NY
4
+ min_n: 50
5
+ max_n: 299
6
+ min_p_ratio: 0.05
7
+ max_p_ratio: 0.0936455
8
+ max_steps_scale: 3
9
+ tabu_time: 2
10
+ tabu_stable_steps_scale: 0.2
11
+ popstar: false
12
+
13
+ # evaluation
14
+ eval_specs:
15
+ region: NY
16
+ seed: 12345
17
+ max_nodes: 2488
18
+ max_edges: 5000
19
+ val_num_cases: 1
20
+ test_num_cases: 1
21
+ val_np: !!python/tuple [299, 28]
22
+ test_np:
23
+ - !!python/tuple [50, 5]
24
+ - !!python/tuple [100, 10]
25
+ - !!python/tuple [400, 50]
26
+
27
+ # agent
28
+ agent_specs:
29
+ policy_feature_dim: 32
30
+ value_feature_dim: 32
31
+ policy_hidden_units: !!python/tuple [32, 32, 1]
32
+ value_hidden_units: !!python/tuple [32, 32, 1]
33
+
34
+ # mlp
35
+ mlp_specs:
36
+ hidden_units: !!python/tuple [32, 32]
37
+
38
+ gnn_specs:
39
+ num_gnn_layers: 2
40
+ node_dim: 32
41
+
42
+
43
+ # ts
44
+ ts_specs:
45
+ max_steps_scale: 2
46
+ stable_iterations_scale: 0.2
47
+
48
+
49
+ # popstar
50
+ popstar_specs:
51
+ graspit: 32
52
+ elite: 10
53
+
54
+
55
+ # ga
56
+ ga_specs:
57
+ num_generations: 100
58
+ num_parents_mating: 50
59
+ sol_per_pop: 100
60
+ parent_selection_type: sss
61
+ crossover_probability: 0.8
62
+ mutation_probability: 0.1
63
+
64
+
65
+
facility_location/cfg/dg.yaml ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # env
2
+ env_specs:
3
+ region:
4
+ min_n: 20
5
+ max_n: 50
6
+ min_p_ratio: 0.1
7
+ max_p_ratio: 0.4
8
+ max_steps_scale: 0.1
9
+ tabu_time: 1
10
+ tabu_stable_steps_scale: 0.2
11
+ popstar: false
12
+
13
+ # evaluation
14
+ eval_specs:
15
+ region: BO
16
+ seed: 12345
17
+ val_num_cases: 100
18
+ test_num_cases: 100
19
+ val_np: !!python/tuple [50,5]
20
+ test_np:
21
+ - !!python/tuple [50, 5]
22
+ - !!python/tuple [100, 10]
23
+ - !!python/tuple [400, 50]
24
+
25
+ # agent
26
+ agent_specs:
27
+ policy_feature_dim: 32
28
+ value_feature_dim: 32
29
+ policy_hidden_units: !!python/tuple [32, 32, 1]
30
+ value_hidden_units: !!python/tuple [32, 32, 1]
31
+
32
+ # mlp
33
+ mlp_specs:
34
+ hidden_units: !!python/tuple [32, 32]
35
+
36
+ gnn_specs:
37
+ num_gnn_layers: 2
38
+ node_dim: 32
39
+
40
+
41
+ # ts
42
+ ts_specs:
43
+ max_steps_scale: 2
44
+ stable_iterations_scale: 0.2
45
+
46
+
47
+ # popstar
48
+ popstar_specs:
49
+ graspit: 32
50
+ elite: 10
51
+
52
+
53
+ # ga
54
+ ga_specs:
55
+ num_generations: 100
56
+ num_parents_mating: 50
57
+ sol_per_pop: 100
58
+ parent_selection_type: sss
59
+ crossover_probability: 0.8
60
+ mutation_probability: 0.1
61
+
62
+
63
+
facility_location/cfg/gainloss.yaml ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # env
2
+ env_specs:
3
+ region:
4
+ min_n: 20
5
+ max_n: 50
6
+ min_p_ratio: 0.1
7
+ max_p_ratio: 0.4
8
+ max_steps_scale: 3
9
+ tabu_time: 3
10
+ tabu_stable_steps_scale: 0.1
11
+ popstar: false
12
+
13
+ # evaluation
14
+ eval_specs:
15
+ region:
16
+ seed: 12345
17
+ val_num_cases: 100
18
+ test_num_cases: 100
19
+ val_np: !!python/tuple [50, 10]
20
+ test_np:
21
+ - !!python/tuple [50, 5]
22
+ - !!python/tuple [100, 10]
23
+ - !!python/tuple [400, 50]
24
+
25
+ # agent
26
+ agent_specs:
27
+ policy_feature_dim: 32
28
+ value_feature_dim: 32
29
+ policy_hidden_units: !!python/tuple [32, 32, 1]
30
+ value_hidden_units: !!python/tuple [32, 32, 1]
31
+
32
+ # mlp
33
+ mlp_specs:
34
+ hidden_units: !!python/tuple [32, 32]
35
+
36
+ gnn_specs:
37
+ num_gnn_layers: 2
38
+ node_dim: 32
39
+
40
+
41
+ # ts
42
+ ts_specs:
43
+ max_steps_scale: 2
44
+ stable_iterations_scale: 0.2
45
+
46
+
47
+ # popstar
48
+ popstar_specs:
49
+ graspit: 32
50
+ elite: 10
51
+
52
+
53
+ # ga
54
+ ga_specs:
55
+ num_generations: 100
56
+ num_parents_mating: 50
57
+ sol_per_pop: 100
58
+ parent_selection_type: sss
59
+ crossover_probability: 0.8
60
+ mutation_probability: 0.1
61
+
62
+
63
+
facility_location/cfg/multi.yaml ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # env
2
+ env_specs:
3
+ region:
4
+ min_n: 20
5
+ max_n: 50
6
+ min_p_ratio: 0.1
7
+ max_p_ratio: 0.4
8
+ max_steps_scale: 3
9
+ tabu_time: 3
10
+ tabu_stable_steps_scale: 0.1
11
+ popstar: false
12
+
13
+ multi:
14
+ nps: [(100,10),(100,20),(100,30)]
15
+ number: True
16
+ conflict: False
17
+
18
+ # evaluation
19
+ eval_specs:
20
+ region:
21
+ seed: 12345
22
+ val_num_cases: 100
23
+ test_num_cases: 100
24
+ val_np: !!python/tuple [50, 10]
25
+ test_np:
26
+ - !!python/tuple [50, 5]
27
+ - !!python/tuple [100, 10]
28
+ - !!python/tuple [400, 50]
29
+
30
+
31
+ # agent
32
+ agent_specs:
33
+ policy_feature_dim: 32
34
+ value_feature_dim: 32
35
+ policy_hidden_units: !!python/tuple [32, 32, 1]
36
+ value_hidden_units: !!python/tuple [32, 32, 1]
37
+
38
+ # mlp
39
+ mlp_specs:
40
+ hidden_units: !!python/tuple [32, 32]
41
+
42
+ gnn_specs:
43
+ num_gnn_layers: 2
44
+ node_dim: 32
45
+
46
+
47
+ # ts
48
+ ts_specs:
49
+ max_steps_scale: 2
50
+ stable_iterations_scale: 0.2
51
+
52
+
53
+ # popstar
54
+ popstar_specs:
55
+ graspit: 32
56
+ elite: 10
57
+
58
+
59
+ # ga
60
+ ga_specs:
61
+ num_generations: 100
62
+ num_parents_mating: 50
63
+ sol_per_pop: 100
64
+ parent_selection_type: sss
65
+ crossover_probability: 0.8
66
+ mutation_probability: 0.1
67
+
68
+
69
+
facility_location/cfg/plot.yaml CHANGED
@@ -1,18 +1,18 @@
1
-
2
  env_specs:
3
  region:
4
  min_n: 20
5
  max_n: 50
6
  min_p_ratio: 0.1
7
  max_p_ratio: 0.4
8
- max_steps_scale: 0.5
9
- tabu_time: 3
10
  tabu_stable_steps_scale: 0.2
11
  popstar: false
12
 
13
  # evaluation
14
  eval_specs:
15
- region:
16
  seed: 12345
17
  max_nodes: 2488
18
  max_edges: 5000
 
1
+ # env
2
  env_specs:
3
  region:
4
  min_n: 20
5
  max_n: 50
6
  min_p_ratio: 0.1
7
  max_p_ratio: 0.4
8
+ max_steps_scale: 3
9
+ tabu_time: 1
10
  tabu_stable_steps_scale: 0.2
11
  popstar: false
12
 
13
  # evaluation
14
  eval_specs:
15
+ region: test
16
  seed: 12345
17
  max_nodes: 2488
18
  max_edges: 5000
facility_location/cfg/popstar.yaml ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # env
2
+ env_specs:
3
+ region:
4
+ min_n: 20
5
+ max_n: 50
6
+ min_p_ratio: 0.1
7
+ max_p_ratio: 0.4
8
+ max_steps_scale: 3
9
+ tabu_time: 3
10
+ tabu_stable_steps_scale: 0.1
11
+ popstar: False
12
+
13
+ # evaluation
14
+ eval_specs:
15
+ region:
16
+ seed: 12345
17
+ val_num_cases: 100
18
+ test_num_cases: 100
19
+ val_np: !!python/tuple [50, 10]
20
+ test_np:
21
+ - !!python/tuple [50, 5]
22
+ - !!python/tuple [100, 10]
23
+ - !!python/tuple [400, 50]
24
+
25
+ # agent
26
+ agent_specs:
27
+ policy_feature_dim: 32
28
+ value_feature_dim: 32
29
+ policy_hidden_units: !!python/tuple [32, 32, 1]
30
+ value_hidden_units: !!python/tuple [32, 32, 1]
31
+
32
+ # mlp
33
+ mlp_specs:
34
+ hidden_units: !!python/tuple [32, 32]
35
+
36
+ gnn_specs:
37
+ num_gnn_layers: 2
38
+ node_dim: 32
39
+
40
+
41
+ # ts
42
+ ts_specs:
43
+ max_steps_scale: 2
44
+ stable_iterations_scale: 0.2
45
+
46
+
47
+ # popstar
48
+ popstar_specs:
49
+ graspit: 32
50
+ elite: 10
51
+
52
+
53
+ # ga
54
+ ga_specs:
55
+ num_generations: 100
56
+ num_parents_mating: 50
57
+ sol_per_pop: 100
58
+ parent_selection_type: sss
59
+ crossover_probability: 0.8
60
+ mutation_probability: 0.1
61
+
62
+
63
+
facility_location/cfg/scale1.yaml ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # env
2
+ env_specs:
3
+ region:
4
+ min_n: 20
5
+ max_n: 50
6
+ min_p_ratio: 0.1
7
+ max_p_ratio: 0.4
8
+ max_steps_scale: 5
9
+ tabu_time: 1
10
+ tabu_stable_steps_scale: 0.1
11
+ popstar: false
12
+
13
+ # evaluation
14
+ eval_specs:
15
+ region:
16
+ seed: 12345
17
+ val_num_cases: 100
18
+ test_num_cases: 100
19
+ val_np: !!python/tuple [50, 10]
20
+ test_np:
21
+ - !!python/tuple [50, 5]
22
+ - !!python/tuple [100, 10]
23
+ - !!python/tuple [400, 50]
24
+
25
+ # agent
26
+ agent_specs:
27
+ policy_feature_dim: 32
28
+ value_feature_dim: 32
29
+ policy_hidden_units: !!python/tuple [32, 32, 1]
30
+ value_hidden_units: !!python/tuple [32, 32, 1]
31
+
32
+ # mlp
33
+ mlp_specs:
34
+ hidden_units: !!python/tuple [32, 32]
35
+
36
+ gnn_specs:
37
+ num_gnn_layers: 2
38
+ node_dim: 32
39
+
40
+
41
+ # ts
42
+ ts_specs:
43
+ max_steps_scale: 2
44
+ stable_iterations_scale: 0.2
45
+
46
+
47
+ # popstar
48
+ popstar_specs:
49
+ graspit: 32
50
+ elite: 10
51
+
52
+
53
+ # ga
54
+ ga_specs:
55
+ num_generations: 100
56
+ num_parents_mating: 50
57
+ sol_per_pop: 100
58
+ parent_selection_type: sss
59
+ crossover_probability: 0.8
60
+ mutation_probability: 0.1
61
+
62
+
63
+
facility_location/cfg/scale5.yaml ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # env
2
+ env_specs:
3
+ region:
4
+ min_n: 20
5
+ max_n: 50
6
+ min_p_ratio: 0.1
7
+ max_p_ratio: 0.4
8
+ max_steps_scale: 5
9
+ tabu_time: 3
10
+ tabu_stable_steps_scale: 0.1
11
+ popstar: false
12
+
13
+ # evaluation
14
+ eval_specs:
15
+ region:
16
+ seed: 12345
17
+ val_num_cases: 100
18
+ test_num_cases: 100
19
+ val_np: !!python/tuple [50, 10]
20
+ test_np:
21
+ - !!python/tuple [50, 5]
22
+ - !!python/tuple [100, 10]
23
+ - !!python/tuple [400, 50]
24
+
25
+ # agent
26
+ agent_specs:
27
+ policy_feature_dim: 32
28
+ value_feature_dim: 32
29
+ policy_hidden_units: !!python/tuple [32, 32, 1]
30
+ value_hidden_units: !!python/tuple [32, 32, 1]
31
+
32
+ # mlp
33
+ mlp_specs:
34
+ hidden_units: !!python/tuple [32, 32]
35
+
36
+ gnn_specs:
37
+ num_gnn_layers: 2
38
+ node_dim: 32
39
+
40
+
41
+ # ts
42
+ ts_specs:
43
+ max_steps_scale: 2
44
+ stable_iterations_scale: 0.2
45
+
46
+
47
+ # popstar
48
+ popstar_specs:
49
+ graspit: 32
50
+ elite: 10
51
+
52
+
53
+ # ga
54
+ ga_specs:
55
+ num_generations: 100
56
+ num_parents_mating: 50
57
+ sol_per_pop: 100
58
+ parent_selection_type: sss
59
+ crossover_probability: 0.8
60
+ mutation_probability: 0.1
61
+
62
+
63
+
facility_location/cfg/tabu0.yaml ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # env
2
+ env_specs:
3
+ region:
4
+ min_n: 20
5
+ max_n: 50
6
+ min_p_ratio: 0.1
7
+ max_p_ratio: 0.4
8
+ max_steps_scale: 3
9
+ tabu_time: 0
10
+ tabu_stable_steps_scale: 0.1
11
+ popstar: false
12
+
13
+ # evaluation
14
+ eval_specs:
15
+ region:
16
+ seed: 12345
17
+ val_num_cases: 100
18
+ test_num_cases: 100
19
+ val_np: !!python/tuple [50, 10]
20
+ test_np:
21
+ - !!python/tuple [50, 5]
22
+ - !!python/tuple [100, 10]
23
+ - !!python/tuple [400, 50]
24
+
25
+ # agent
26
+ agent_specs:
27
+ policy_feature_dim: 32
28
+ value_feature_dim: 32
29
+ policy_hidden_units: !!python/tuple [32, 32, 1]
30
+ value_hidden_units: !!python/tuple [32, 32, 1]
31
+
32
+ # mlp
33
+ mlp_specs:
34
+ hidden_units: !!python/tuple [32, 32]
35
+
36
+ gnn_specs:
37
+ num_gnn_layers: 2
38
+ node_dim: 32
39
+
40
+
41
+ # ts
42
+ ts_specs:
43
+ max_steps_scale: 2
44
+ stable_iterations_scale: 0.2
45
+
46
+
47
+ # popstar
48
+ popstar_specs:
49
+ graspit: 32
50
+ elite: 10
51
+
52
+
53
+ # ga
54
+ ga_specs:
55
+ num_generations: 100
56
+ num_parents_mating: 50
57
+ sol_per_pop: 100
58
+ parent_selection_type: sss
59
+ crossover_probability: 0.8
60
+ mutation_probability: 0.1
61
+
62
+
63
+
facility_location/cfg/tabu5.yaml ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # env
2
+ env_specs:
3
+ region:
4
+ min_n: 20
5
+ max_n: 50
6
+ min_p_ratio: 0.1
7
+ max_p_ratio: 0.4
8
+ max_steps_scale: 3
9
+ tabu_time: 5
10
+ tabu_stable_steps_scale: 0.1
11
+ popstar: false
12
+
13
+ # evaluation
14
+ eval_specs:
15
+ region:
16
+ seed: 12345
17
+ val_num_cases: 100
18
+ test_num_cases: 100
19
+ val_np: !!python/tuple [50, 10]
20
+ test_np:
21
+ - !!python/tuple [50, 5]
22
+ - !!python/tuple [100, 10]
23
+ - !!python/tuple [400, 50]
24
+
25
+ # agent
26
+ agent_specs:
27
+ policy_feature_dim: 32
28
+ value_feature_dim: 32
29
+ policy_hidden_units: !!python/tuple [32, 32, 1]
30
+ value_hidden_units: !!python/tuple [32, 32, 1]
31
+
32
+ # mlp
33
+ mlp_specs:
34
+ hidden_units: !!python/tuple [32, 32]
35
+
36
+ gnn_specs:
37
+ num_gnn_layers: 2
38
+ node_dim: 32
39
+
40
+
41
+ # ts
42
+ ts_specs:
43
+ max_steps_scale: 2
44
+ stable_iterations_scale: 0.2
45
+
46
+
47
+ # popstar
48
+ popstar_specs:
49
+ graspit: 32
50
+ elite: 10
51
+
52
+
53
+ # ga
54
+ ga_specs:
55
+ num_generations: 100
56
+ num_parents_mating: 50
57
+ sol_per_pop: 100
58
+ parent_selection_type: sss
59
+ crossover_probability: 0.8
60
+ mutation_probability: 0.1
61
+
62
+
63
+
facility_location/cfg/uniform.yaml ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # env
2
+ env_specs:
3
+ region:
4
+ min_n: 20
5
+ max_n: 50
6
+ min_p_ratio: 0.1
7
+ max_p_ratio: 0.4
8
+ max_steps_scale: 3
9
+ tabu_time: 3
10
+ tabu_stable_steps_scale: 0.1
11
+ popstar: false
12
+
13
+ # evaluation
14
+ eval_specs:
15
+ region:
16
+ seed: 12345
17
+ val_num_cases: 100
18
+ test_num_cases: 100
19
+ val_np: !!python/tuple [50, 10]
20
+ test_np:
21
+ - !!python/tuple [50, 5]
22
+ - !!python/tuple [100, 10]
23
+ - !!python/tuple [400, 50]
24
+
25
+ # agent
26
+ agent_specs:
27
+ policy_feature_dim: 32
28
+ value_feature_dim: 32
29
+ policy_hidden_units: !!python/tuple [32, 32, 1]
30
+ value_hidden_units: !!python/tuple [32, 32, 1]
31
+
32
+ # mlp
33
+ mlp_specs:
34
+ hidden_units: !!python/tuple [32, 32]
35
+
36
+ gnn_specs:
37
+ num_gnn_layers: 2
38
+ node_dim: 32
39
+
40
+
41
+ # ts
42
+ ts_specs:
43
+ max_steps_scale: 2
44
+ stable_iterations_scale: 0.2
45
+
46
+
47
+ # popstar
48
+ popstar_specs:
49
+ graspit: 32
50
+ elite: 10
51
+
52
+
53
+ # ga
54
+ ga_specs:
55
+ num_generations: 100
56
+ num_parents_mating: 50
57
+ sol_per_pop: 100
58
+ parent_selection_type: sss
59
+ crossover_probability: 0.8
60
+ mutation_probability: 0.1
61
+
62
+
63
+
facility_location/cfg/uniform_debug.yaml ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # env
2
+ env_specs:
3
+ min_n: 20
4
+ max_n: 50
5
+ min_p_ratio: 0.1
6
+ max_p_ratio: 0.4
7
+ max_steps_scale: 2
8
+ tabu_time: 5
9
+ tabu_stable_steps_scale: 0.1
10
+ popstar: false
11
+
12
+ # evaluation
13
+ eval_specs:
14
+ seed: 12345
15
+ val_num_cases: 10
16
+ test_num_cases: 1000
17
+ val_np: !!python/tuple [50, 10]
18
+ test_np:
19
+ - !!python/tuple [50, 5]
20
+ # - !!python/tuple [100, 10]
21
+ # - !!python/tuple [400, 50]
22
+
23
+ # agent
24
+ agent_specs:
25
+ policy_feature_dim: 32
26
+ value_feature_dim: 32
27
+ policy_hidden_units: !!python/tuple [32, 32, 1]
28
+ value_hidden_units: !!python/tuple [32, 32, 1]
29
+
30
+ # mlp
31
+ mlp_specs:
32
+ hidden_units: !!python/tuple [32, 32]
33
+
34
+ gnn_specs:
35
+ num_gnn_layers: 2
36
+ node_dim: 32
37
+
38
+
39
+ # ts
40
+ ts_specs:
41
+ max_steps_scale: 2
42
+ stable_iterations_scale: 0.2
43
+
44
+
45
+ # popstar
46
+ popstar_specs:
47
+ graspit: 32
48
+ elite: 10
49
+
50
+
51
+ # ga
52
+ ga_specs:
53
+ num_generations: 100
54
+ num_parents_mating: 50
55
+ sol_per_pop: 100
56
+ parent_selection_type: sss
57
+ crossover_probability: 0.8
58
+ mutation_probability: 0.1
59
+
60
+ # tabu
61
+ tabu_specs:
62
+ tabu_time: 5
63
+ tabu_stable_steps_scale: 0.1
64
+
facility_location/env/__pycache__/__init__.cpython-39.pyc CHANGED
Binary files a/facility_location/env/__pycache__/__init__.cpython-39.pyc and b/facility_location/env/__pycache__/__init__.cpython-39.pyc differ
 
facility_location/env/__pycache__/facility_location_client.cpython-310.pyc CHANGED
Binary files a/facility_location/env/__pycache__/facility_location_client.cpython-310.pyc and b/facility_location/env/__pycache__/facility_location_client.cpython-310.pyc differ
 
facility_location/env/__pycache__/facility_location_client.cpython-39.pyc CHANGED
Binary files a/facility_location/env/__pycache__/facility_location_client.cpython-39.pyc and b/facility_location/env/__pycache__/facility_location_client.cpython-39.pyc differ
 
facility_location/env/__pycache__/obs_extractor.cpython-310.pyc CHANGED
Binary files a/facility_location/env/__pycache__/obs_extractor.cpython-310.pyc and b/facility_location/env/__pycache__/obs_extractor.cpython-310.pyc differ
 
facility_location/env/__pycache__/obs_extractor.cpython-39.pyc CHANGED
Binary files a/facility_location/env/__pycache__/obs_extractor.cpython-39.pyc and b/facility_location/env/__pycache__/obs_extractor.cpython-39.pyc differ
 
facility_location/env/__pycache__/pmp.cpython-310.pyc CHANGED
Binary files a/facility_location/env/__pycache__/pmp.cpython-310.pyc and b/facility_location/env/__pycache__/pmp.cpython-310.pyc differ
 
facility_location/env/__pycache__/pmp.cpython-39.pyc CHANGED
Binary files a/facility_location/env/__pycache__/pmp.cpython-39.pyc and b/facility_location/env/__pycache__/pmp.cpython-39.pyc differ
 
facility_location/env/facility_location_client.py CHANGED
@@ -21,6 +21,7 @@ class FacilityLocationClient:
21
 
22
  def set_instance(self, points: np.ndarray, demands: np.ndarray, n: int, p: int, real: bool) -> None:
23
  self._points = points
 
24
  self._demands = demands
25
  points_geom = MultiPoint(points)
26
  self._gdf = GeoDataFrame({
@@ -42,6 +43,8 @@ class FacilityLocationClient:
42
  self._loss = np.zeros(self._n)
43
  self._add_time = np.full(self._n, -np.inf)
44
  self._drop_time = np.full(self._n, -np.inf)
 
 
45
  self.reset_tabu_time()
46
 
47
  def get_instance(self) -> Tuple[np.ndarray, np.ndarray, int, int]:
@@ -56,52 +59,48 @@ class FacilityLocationClient:
56
  return avg_distance, avg_cost
57
 
58
  def _construct_static_graph(self) -> None:
 
 
 
59
  self._connection_matrix = kneighbors_graph(self._points, n_neighbors=3, mode="connectivity").toarray()
60
  self._static_graph = nx.from_numpy_matrix(self._connection_matrix)
61
  self._static_edges = np.array(self._static_graph.edges(), dtype=np.int64)
62
 
63
- def _construct_dynamic_graph(self,stage=1) -> None:
64
  t1 = time.time()
65
  try:
66
  solution_distace_min = np.partition(self._distance_matrix[:, self._solution][self._solution, :], 3, axis=-1)[:,2]
67
  except:
 
 
 
 
68
  raise ValueError('stop')
69
  solution_distance_matrix = np.zeros((self._n, self._n))
70
  solution_distance_matrix[:, self._solution] = solution_distace_min
71
  solution_knearest_matrix = np.logical_and(self._distance_matrix < solution_distance_matrix, self._distance_matrix > 0)
72
- if stage == 2:
73
- old_facility_mask, new_facility_mask = self.get_facility_mask()
74
- solution_matrix = np.logical_and(np.logical_and(self._solution, old_facility_mask)[:, None], (np.logical_and(~self._solution, new_facility_mask)[None, :]))
75
- print('solution:',self._solution)
76
- print('old_facility_mask:',old_facility_mask)
77
- print('new_facility_mask:',new_facility_mask)
78
- else:
79
- old_tabu_mask, new_tabu_mask = self.get_tabu_mask(self._t)
80
- solution_matrix = np.logical_and(np.logical_and(self._solution, old_tabu_mask)[:, None], (np.logical_and(~self._solution, new_tabu_mask)[None, :]))
81
- print('solution:',self._solution)
82
- print('old_tabu_mask:',old_tabu_mask)
83
- print('new_tabu_mask:',new_tabu_mask)
84
  solution_matrix = np.logical_or(solution_matrix, solution_matrix.T)
85
  gainloss_matrix = np.logical_and((self._gain[:, None] > self._loss[None, :]), self._loss[None, :] > 0)
86
  graph_matrix = np.logical_and(solution_matrix, np.logical_or(gainloss_matrix, solution_knearest_matrix))
87
 
88
  if not np.any(graph_matrix):
 
 
 
 
 
 
 
 
 
 
89
  if np.any(solution_matrix):
90
  graph_matrix = solution_matrix
91
  if not np.any(graph_matrix):
92
  raise ValueError('Invalid graph_matrix')
93
- else:
94
- if stage==2:
95
- print('[!] No solution_matrix')
96
- print('solution:',self._solution)
97
- print('old_facility_mask:',old_facility_mask)
98
- print('new_facility_mask:',new_facility_mask)
99
- else:
100
- print('[!] No solution_matrix')
101
- print('solution:',self._solution)
102
- print('old_tabu_mask:',old_tabu_mask)
103
- print('new_tabu_mask:',new_tabu_mask)
104
- graph_matrix = self._solution[:, None] ^ self._solution[None, :]
105
  self._dynamic_graph = nx.from_numpy_matrix(graph_matrix)
106
  self._dynamic_edges = np.array(self._dynamic_graph.edges(), dtype=np.int64)
107
 
@@ -115,6 +114,14 @@ class FacilityLocationClient:
115
  def get_dynamic_adjacency_list(self) -> np.ndarray:
116
  return self._dynamic_edges
117
 
 
 
 
 
 
 
 
 
118
  def compute_initial_solution(self) -> Tuple[float, np.ndarray]:
119
  self._solution = np.zeros(self._n, dtype=bool)
120
  p_0 = self._demands.argmax()
@@ -130,12 +137,16 @@ class FacilityLocationClient:
130
 
131
  def compute_obj_value(self) -> float:
132
  obj_value = self._cost_matrix[:, self._solution].min(axis=-1).sum()
 
 
 
 
133
  return obj_value
134
 
135
- def compute_obj_value_from_solution(self, solution, stage=1) -> float:
136
  self._solution = solution
137
  self._init_gain_and_loss()
138
- self._construct_dynamic_graph(stage)
139
  obj_value = self.compute_obj_value()
140
  return obj_value
141
 
@@ -155,9 +166,8 @@ class FacilityLocationClient:
155
  # self._t = t
156
  # return self.compute_obj_value(), self._solution, {}
157
 
158
- def swap(self, facility_pair_index: int, t: int, stage=1) -> Tuple[float, np.ndarray, Dict]:
159
  facility_pair = self._dynamic_edges[facility_pair_index]
160
- print(facility_pair)
161
  facility1 = facility_pair[0]
162
  facility2 = facility_pair[1]
163
 
@@ -168,24 +178,21 @@ class FacilityLocationClient:
168
  new_facility = facility2
169
  old_facility = facility1
170
  else:
 
 
 
 
 
171
  raise ValueError('stop')
172
 
173
  self._solution[old_facility] = False
174
  self._solution[new_facility] = True
175
- if stage == 1:
176
- self._old_facility_mask[new_facility] = False
177
- self._new_facility_mask[old_facility] = True
178
- else:
179
- self._old_facility_mask[new_facility] = False
180
- self._new_facility_mask[old_facility] = False
181
  self._drop_time[old_facility] = t
182
  self._add_time[new_facility] = t
183
  self._t = t
184
- self._solution[old_facility] = False
185
- self._solution[new_facility] = True
186
- print(self._solution,old_facility,new_facility)
187
- self._update_env(new_facility, old_facility, stage)
188
-
189
  # print('st:',self._t)
190
  return self.compute_obj_value(), self._solution, {}
191
 
@@ -244,9 +251,9 @@ class FacilityLocationClient:
244
  self._init_gain_and_loss()
245
  self._construct_dynamic_graph()
246
 
247
- def _update_env(self, insert_facility, remove_facility, stage):
248
  self._update_gain_and_loss(insert_facility, remove_facility)
249
- self._construct_dynamic_graph(stage)
250
 
251
  def _init_gain_and_loss(self):
252
  t1 = time.time()
 
21
 
22
  def set_instance(self, points: np.ndarray, demands: np.ndarray, n: int, p: int, real: bool) -> None:
23
  self._points = points
24
+
25
  self._demands = demands
26
  points_geom = MultiPoint(points)
27
  self._gdf = GeoDataFrame({
 
43
  self._loss = np.zeros(self._n)
44
  self._add_time = np.full(self._n, -np.inf)
45
  self._drop_time = np.full(self._n, -np.inf)
46
+ # self._max_add_tabu_time = min(self._cfg_tabu_time, self._n - self._p - 2)
47
+ # self._max_drop_tabu_time = min(self._cfg_tabu_time, self._p - 2)
48
  self.reset_tabu_time()
49
 
50
  def get_instance(self) -> Tuple[np.ndarray, np.ndarray, int, int]:
 
59
  return avg_distance, avg_cost
60
 
61
  def _construct_static_graph(self) -> None:
62
+ # w = Voronoi_weights(self._points)
63
+ # self._static_graph = w.to_networkx()
64
+ # self._edges = np.array(self._static_graph .edges, dtype=np.int64)
65
  self._connection_matrix = kneighbors_graph(self._points, n_neighbors=3, mode="connectivity").toarray()
66
  self._static_graph = nx.from_numpy_matrix(self._connection_matrix)
67
  self._static_edges = np.array(self._static_graph.edges(), dtype=np.int64)
68
 
69
+ def _construct_dynamic_graph(self) -> None:
70
  t1 = time.time()
71
  try:
72
  solution_distace_min = np.partition(self._distance_matrix[:, self._solution][self._solution, :], 3, axis=-1)[:,2]
73
  except:
74
+ print('np:',self._n, self._p)
75
+ print('sm:',self._solution.sum())
76
+ print('sol:',np.where(self._solution))
77
+ print('t:',self._t)
78
  raise ValueError('stop')
79
  solution_distance_matrix = np.zeros((self._n, self._n))
80
  solution_distance_matrix[:, self._solution] = solution_distace_min
81
  solution_knearest_matrix = np.logical_and(self._distance_matrix < solution_distance_matrix, self._distance_matrix > 0)
82
+ old_tabu_mask, new_tabu_mask = self.get_tabu_mask(self._t)
83
+ solution_matrix = np.logical_and(np.logical_and(self._solution, old_tabu_mask)[:, None], (np.logical_and(~self._solution, new_tabu_mask)[None, :]))
 
 
 
 
 
 
 
 
 
 
84
  solution_matrix = np.logical_or(solution_matrix, solution_matrix.T)
85
  gainloss_matrix = np.logical_and((self._gain[:, None] > self._loss[None, :]), self._loss[None, :] > 0)
86
  graph_matrix = np.logical_and(solution_matrix, np.logical_or(gainloss_matrix, solution_knearest_matrix))
87
 
88
  if not np.any(graph_matrix):
89
+ print('Warning: graph_matrix is empty!')
90
+ print('np:',self._n, self._p)
91
+ print('sm:',solution_matrix.sum())
92
+ print('glm:',gainloss_matrix.sum())
93
+ print('skm:',solution_knearest_matrix.sum())
94
+ print('sol:',np.where(self._solution))
95
+ print('old:',np.where(~old_tabu_mask))
96
+ print('new:',np.where(~new_tabu_mask))
97
+ print('t:',self._t)
98
+
99
  if np.any(solution_matrix):
100
  graph_matrix = solution_matrix
101
  if not np.any(graph_matrix):
102
  raise ValueError('Invalid graph_matrix')
103
+
 
 
 
 
 
 
 
 
 
 
 
104
  self._dynamic_graph = nx.from_numpy_matrix(graph_matrix)
105
  self._dynamic_edges = np.array(self._dynamic_graph.edges(), dtype=np.int64)
106
 
 
114
  def get_dynamic_adjacency_list(self) -> np.ndarray:
115
  return self._dynamic_edges
116
 
117
+ # def get_degree(self) -> np.ndarray:
118
+ # return np.array(self._static_graph .degree)[:, 1]
119
+
120
+ # def get_centrality(self) -> Tuple[np.ndarray, np.ndarray]:
121
+ # closeness = np.array(list(nx.closeness_centrality(self._static_graph).values()))
122
+ # betweenness = np.array(list(nx.betweenness_centrality(self._static_graph).values()))
123
+ # return closeness, betweenness
124
+
125
  def compute_initial_solution(self) -> Tuple[float, np.ndarray]:
126
  self._solution = np.zeros(self._n, dtype=bool)
127
  p_0 = self._demands.argmax()
 
137
 
138
  def compute_obj_value(self) -> float:
139
  obj_value = self._cost_matrix[:, self._solution].min(axis=-1).sum()
140
+ # import pickle
141
+ # name = sum(self._solution)
142
+ # pickle.dump(self._solution, open(f'/data2/suhongyuan/flp/data/solution/{name}.pkl', 'wb'))
143
+ # print('save')
144
  return obj_value
145
 
146
+ def compute_obj_value_from_solution(self, solution) -> float:
147
  self._solution = solution
148
  self._init_gain_and_loss()
149
+ self._construct_dynamic_graph()
150
  obj_value = self.compute_obj_value()
151
  return obj_value
152
 
 
166
  # self._t = t
167
  # return self.compute_obj_value(), self._solution, {}
168
 
169
+ def swap(self, facility_pair_index: int, t: int) -> Tuple[float, np.ndarray, Dict]:
170
  facility_pair = self._dynamic_edges[facility_pair_index]
 
171
  facility1 = facility_pair[0]
172
  facility2 = facility_pair[1]
173
 
 
178
  new_facility = facility2
179
  old_facility = facility1
180
  else:
181
+ print(np.where(self._solution))
182
+ warn_msg = f'Facility pair {facility_pair} is not a valid pair.'
183
+ print(warn_msg)
184
+ print(self._solution[facility1], self._solution[facility2])
185
+ print(self._dynamic_graph.has_edge(facility1, facility2))
186
  raise ValueError('stop')
187
 
188
  self._solution[old_facility] = False
189
  self._solution[new_facility] = True
190
+ self._old_facility_mask[new_facility] = True
191
+ self._new_facility_mask[old_facility] = True
 
 
 
 
192
  self._drop_time[old_facility] = t
193
  self._add_time[new_facility] = t
194
  self._t = t
195
+ self._update_env(new_facility, old_facility)
 
 
 
 
196
  # print('st:',self._t)
197
  return self.compute_obj_value(), self._solution, {}
198
 
 
251
  self._init_gain_and_loss()
252
  self._construct_dynamic_graph()
253
 
254
+ def _update_env(self, insert_facility, remove_facility):
255
  self._update_gain_and_loss(insert_facility, remove_facility)
256
+ self._construct_dynamic_graph()
257
 
258
  def _init_gain_and_loss(self):
259
  t1 = time.time()
facility_location/env/obs_extractor.py CHANGED
@@ -29,8 +29,11 @@ class ObsExtractor:
29
  virtual_node_x = 0.5
30
  virtual_node_y = 0.5
31
  virtual_node_demand = 1
 
32
  virtual_node_avg_distance = 0
33
  virtual_node_avg_cost = 0
 
 
34
  self._virtual_dynamic_node_feature = np.array([
35
  virtual_node_facility,
36
  virtual_node_distance_min,
@@ -44,8 +47,11 @@ class ObsExtractor:
44
  virtual_node_x,
45
  virtual_node_y,
46
  virtual_node_demand,
 
47
  virtual_node_avg_distance,
48
  virtual_node_avg_cost,
 
 
49
  ], dtype=np.float32)
50
  self._virtual_node_feature = np.concatenate([
51
  self._virtual_dynamic_node_feature,
@@ -73,15 +79,23 @@ class ObsExtractor:
73
  print(n, self._node_range)
74
  # raise ValueError('The number of nodes exceeds the maximum limit.')
75
  self._n = n
 
 
76
  avg_distance, avg_cost = self._flc.get_avg_distance_and_cost()
77
  avg_distance = avg_distance / np.max(avg_distance)
78
  avg_cost = avg_cost / np.max(avg_cost)
 
 
 
79
  self._static_node_features = np.stack([
80
  xy[:, 0],
81
  xy[:, 1],
82
  demands,
 
83
  avg_distance,
84
  avg_cost,
 
 
85
  ], axis=-1).astype(np.float32)
86
  static_adjacency_list = self._flc.get_static_adjacency_list()
87
 
@@ -105,6 +119,8 @@ class ObsExtractor:
105
  def get_obs(self, t: int) -> Dict:
106
  obs_nodes, obs_static_edges, obs_dynamic_edges, \
107
  obs_node_mask, obs_static_edge_mask, obs_dynamic_edges_mask = self._get_obs_graph()
 
 
108
  obs = {
109
  'node_features': obs_nodes,
110
  'static_adjacency_list': obs_static_edges,
@@ -112,8 +128,9 @@ class ObsExtractor:
112
  'node_mask': obs_node_mask,
113
  'static_edge_mask': obs_static_edge_mask,
114
  'dynamic_edge_mask': obs_dynamic_edges_mask,
 
 
115
  }
116
-
117
  return obs
118
 
119
  def _get_obs_graph(self) -> Tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]:
@@ -149,6 +166,7 @@ class ObsExtractor:
149
  # return obs_nodes, obs_static_edges, obs_node_mask, obs_edge_mask
150
 
151
  def _get_obs_action_mask(self, t: int) -> Tuple[np.ndarray, np.ndarray]:
 
152
  old_facility_mask, new_facility_mask = self._flc.get_facility_mask()
153
  old_tabu_mask, new_tabu_mask = self._flc.get_tabu_mask(t)
154
  self._old_facility_mask[1:self._n+1] = np.logical_and(old_facility_mask, old_tabu_mask)
 
29
  virtual_node_x = 0.5
30
  virtual_node_y = 0.5
31
  virtual_node_demand = 1
32
+ # virtual_node_degree = 1
33
  virtual_node_avg_distance = 0
34
  virtual_node_avg_cost = 0
35
+ # virtual_node_closeness_centrality = 1
36
+ # virtual_node_betweenness_centrality = 1
37
  self._virtual_dynamic_node_feature = np.array([
38
  virtual_node_facility,
39
  virtual_node_distance_min,
 
47
  virtual_node_x,
48
  virtual_node_y,
49
  virtual_node_demand,
50
+ # virtual_node_degree,
51
  virtual_node_avg_distance,
52
  virtual_node_avg_cost,
53
+ # virtual_node_closeness_centrality,
54
+ # virtual_node_betweenness_centrality,
55
  ], dtype=np.float32)
56
  self._virtual_node_feature = np.concatenate([
57
  self._virtual_dynamic_node_feature,
 
79
  print(n, self._node_range)
80
  # raise ValueError('The number of nodes exceeds the maximum limit.')
81
  self._n = n
82
+ # degree = self._flc.get_degree()
83
+ # degree = degree/np.max(degree)
84
  avg_distance, avg_cost = self._flc.get_avg_distance_and_cost()
85
  avg_distance = avg_distance / np.max(avg_distance)
86
  avg_cost = avg_cost / np.max(avg_cost)
87
+ # closeness_centrality, betweenness_centrality = self._flc.get_centrality()
88
+ # closeness_centrality = closeness_centrality/np.max(closeness_centrality)
89
+ # betweenness_centrality = betweenness_centrality/np.max(betweenness_centrality)
90
  self._static_node_features = np.stack([
91
  xy[:, 0],
92
  xy[:, 1],
93
  demands,
94
+ # degree,
95
  avg_distance,
96
  avg_cost,
97
+ # closeness_centrality,
98
+ # betweenness_centrality,
99
  ], axis=-1).astype(np.float32)
100
  static_adjacency_list = self._flc.get_static_adjacency_list()
101
 
 
119
  def get_obs(self, t: int) -> Dict:
120
  obs_nodes, obs_static_edges, obs_dynamic_edges, \
121
  obs_node_mask, obs_static_edge_mask, obs_dynamic_edges_mask = self._get_obs_graph()
122
+ # obs_old_facility_mask, obs_new_facility_mask = self._get_obs_action_mask(t)
123
+
124
  obs = {
125
  'node_features': obs_nodes,
126
  'static_adjacency_list': obs_static_edges,
 
128
  'node_mask': obs_node_mask,
129
  'static_edge_mask': obs_static_edge_mask,
130
  'dynamic_edge_mask': obs_dynamic_edges_mask,
131
+ # 'old_facility_mask': obs_old_facility_mask,
132
+ # 'new_facility_mask': obs_new_facility_mask,
133
  }
 
134
  return obs
135
 
136
  def _get_obs_graph(self) -> Tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]:
 
166
  # return obs_nodes, obs_static_edges, obs_node_mask, obs_edge_mask
167
 
168
  def _get_obs_action_mask(self, t: int) -> Tuple[np.ndarray, np.ndarray]:
169
+ # facility_mask = self._flc.get_current_solution()
170
  old_facility_mask, new_facility_mask = self._flc.get_facility_mask()
171
  old_tabu_mask, new_tabu_mask = self._flc.get_tabu_mask(t)
172
  self._old_facility_mask[1:self._n+1] = np.logical_and(old_facility_mask, old_tabu_mask)
facility_location/env/pmp.py CHANGED
@@ -13,10 +13,6 @@ from numpy import ndarray
13
  from facility_location.utils.config import Config
14
  from facility_location.env.facility_location_client import FacilityLocationClient
15
  from facility_location.env.obs_extractor import ObsExtractor
16
- from stable_baselines3 import PPO
17
- from stable_baselines3.common.vec_env import DummyVecEnv
18
- from facility_location.agent import MaskedFacilityLocationActorCriticPolicy
19
- from facility_location.utils.policy import get_policy_kwargs
20
 
21
 
22
  class PMPEnv(gym.Env):
@@ -54,8 +50,11 @@ class PMPEnv(gym.Env):
54
  'node_mask': gym.spaces.Box(low=0, high=1, shape=(self._node_range,), dtype=np.bool),
55
  'static_edge_mask': gym.spaces.Box(low=0, high=1, shape=(self._edge_range,), dtype=np.bool),
56
  'dynamic_edge_mask': gym.spaces.Box(low=0, high=1, shape=(self._edge_range,), dtype=np.bool),
 
 
57
  })
58
  if not self._popstar:
 
59
  self.action_space = gym.spaces.Discrete(self._node_range ** 2)
60
  else:
61
  self.action_space = gym.spaces.Discrete(self._node_range ** 2)
@@ -72,6 +71,7 @@ class PMPEnv(gym.Env):
72
 
73
  def get_reward(self) -> float:
74
  reward = self._obj_value[self._t - 1] - self._obj_value[self._t]
 
75
  return reward
76
 
77
  def _transform_action(self, action: np.ndarray) -> np.ndarray:
@@ -83,6 +83,8 @@ class PMPEnv(gym.Env):
83
  def step(self, action: np.ndarray):
84
  if self._done:
85
  raise RuntimeError('Action taken after episode is done.')
 
 
86
  obj_value, solution, info = self._flc.swap(action, self._t)
87
  self._t += 1
88
  self._done = (self._t == self._max_steps)
@@ -214,22 +216,41 @@ class PMPEnv(gym.Env):
214
  class EvalPMPEnv(PMPEnv):
215
  def __init__(self,
216
  cfg: Config,
217
- positions, demands, n, p, boost=False):
218
- self._eval_np = (n,p)
 
 
 
 
 
 
 
 
 
 
 
 
219
  self._eval_seed = cfg.eval_specs['seed']
220
- self._boost = boost
221
- self.points = positions
222
- self.demands = demands
223
- self._n = n
224
- self._p = p
225
-
226
  super().__init__(cfg)
227
 
228
  def _set_node_edge_range(self) -> None:
229
  n, p = self._eval_np
230
-
231
- self._node_range = n + 2
232
- self._edge_range = n * p
 
 
 
 
 
 
 
 
 
 
 
233
 
234
  def get_eval_num_cases(self) -> int:
235
  return self._eval_num_cases
@@ -240,6 +261,17 @@ class EvalPMPEnv(PMPEnv):
240
  def reset_instance_id(self) -> None:
241
  self._instance_id = 0
242
 
 
 
 
 
 
 
 
 
 
 
 
243
  def step(self, action: np.ndarray):
244
  if self._done:
245
  raise RuntimeError('Action taken after episode is done.')
@@ -254,14 +286,27 @@ class EvalPMPEnv(PMPEnv):
254
  self._best_solution = solution
255
  self._last_best_t = self._t
256
  elif (self._t - self._last_best_t) % self._tabu_stable_steps == 0:
257
- self._flc.reset_tabu_time()
258
- print(self._t, self._max_steps)
 
 
 
 
 
 
 
 
 
 
 
259
 
260
  return self._get_obs(self._t), reward, self._done, False, info
261
 
262
  def get_reward(self) -> float:
263
  if self._done:
264
- reward = -np.min(self._obj_value)
 
 
265
  else:
266
  reward = 0.0
267
 
@@ -269,28 +314,52 @@ class EvalPMPEnv(PMPEnv):
269
 
270
  def get_best_solution(self) -> np.ndarray:
271
  return self._best_solution
 
 
 
 
 
 
 
 
272
 
273
- def reset(self, seed = 0) -> Dict:
274
- self._flc.set_instance(self.points, self.demands, self._n, self._p, False)
275
- return self.prepare(self._n, self._p, self._boost), {}
 
 
 
 
 
 
 
 
 
 
 
 
276
 
277
- def prepare(self, n: int, p: int, boost: bool) -> Dict:
278
- initial_obj_value, initial_solution = self._flc.compute_initial_solution()
279
- self._obs_extractor.reset()
280
- self._done = False
281
- self._t = 0
282
- self._max_steps = max(int(p * self._max_steps_scale), 5)
283
- if boost:
284
- self._max_steps = max(int(self._max_steps_scale / 10), 5)
285
- self._obj_value = np.zeros(self._max_steps + 1)
286
- self._obj_value[0] = initial_obj_value
287
- self._solution = np.zeros((self._max_steps + 1, n), dtype=bool)
288
- self._solution[0] = initial_solution
289
- self._best_solution = initial_solution
290
- self._best_obj_value = initial_obj_value
291
- self._last_best_t = 0
292
- self._tabu_stable_steps = max(1, round(self._max_steps * self._tabu_stable_steps_scale))
293
- return self._get_obs(self._t)
 
 
 
 
294
 
295
  def get_instance(self) -> Tuple[np.ndarray, np.ndarray, int, int]:
296
  points, demands, n, p = self._flc.get_instance()
@@ -304,21 +373,20 @@ class EvalPMPEnv(PMPEnv):
304
  obj_value = self._flc.compute_obj_value()
305
  return obj_value
306
 
 
 
 
 
 
307
  class MULTIPMP(PMPEnv):
308
  EPSILON = 1e-6
309
  def __init__(self,
310
- cfg,
311
- data_npy,
312
- boost = False):
313
  self.cfg = cfg
314
- self.data_npy = data_npy
315
- self._boost = boost
316
- self._all_points, self._all_demands, self._n, self._all_p = self._load_multi_facility_data(data_npy)
317
- self.boost = boost
318
- self._all_solutions = self._load_multi_facility_solutions(boost)
319
- print('all_solutions:', self._all_solutions)
320
  self._final_solutions = list(self._all_solutions)
321
- self._num_types = len(self._all_p)
322
  self._current_type = 0
323
  self._all_max_steps, self._old_mask, self._new_mask = self._get_max_steps()
324
  super().__init__(cfg)
@@ -328,15 +396,9 @@ class MULTIPMP(PMPEnv):
328
  self._edge_range = self._n * max(self._all_p)
329
 
330
  def step(self, action: np.ndarray):
331
- if self._num_types == 1:
332
- reward = self.get_reward()
333
- self._done = True
334
- pickle.dump(self._final_solutions, open('./facility_location/solutions.pkl', 'wb'))
335
- return self._get_obs(self._t), reward, self._done, False, {}
336
-
337
  if self._done:
338
  raise RuntimeError('Action taken after episode is done.')
339
- obj_value, solution, info = self._flc.swap(action, self._t, stage=2)
340
  self._t += 1
341
  self._done = (self._t == self._all_max_steps[-1] and self._current_type == len(self._all_max_steps) - 1)
342
  self._obj_value[self._t] = obj_value
@@ -355,20 +417,16 @@ class MULTIPMP(PMPEnv):
355
  self._final_solutions[self._current_type] = solution
356
  self._update_type()
357
 
358
- if self._done:
359
- pickle.dump(self._final_solutions, open('./facility_location/solutions.pkl', 'wb'))
360
-
361
  return self._get_obs(self._t), reward, self._done, False, info
362
 
363
  def reset(self, seed = 0) -> Optional[Dict]:
364
  self._current_type = 0
365
- points = self._all_points
366
- demands = self._all_demands[:,0]
367
  n = self._n
368
  p = self._all_p[0]
369
  solution = self._all_solutions[0]
370
  self._multi_obj = 0
371
-
372
  self._flc.set_instance(points, demands, n, p, True)
373
 
374
  return self.prepare(n, p, solution), {}
@@ -376,11 +434,10 @@ class MULTIPMP(PMPEnv):
376
  def _update_type(self):
377
  if self._current_type >= self._num_types:
378
  raise RuntimeError('Action taken after episode is done.')
 
379
  if self._current_type < self._num_types - 1:
380
- print(f'current type: {self._current_type}')
381
- print(self._num_types)
382
- points = self._all_points
383
- demands = self._all_demands[:,self._current_type]
384
  n = self._n
385
  p = self._all_p[self._current_type]
386
  solution = self._all_solutions[self._current_type]
@@ -388,18 +445,13 @@ class MULTIPMP(PMPEnv):
388
  self.prepare(n, p, solution)
389
 
390
  def prepare(self, n: int, p: int, solution: list) -> Dict:
 
 
391
  self._obs_extractor.reset()
392
  self._done = False
393
  self._t = 0
394
- if len(self._all_p) > 1:
395
- self._max_steps = self._all_max_steps[self._current_type]
396
- self._flc.init_facility_mask(self._old_mask[self._current_type], self._new_mask[self._current_type])
397
- else:
398
- self._max_steps = 0
399
-
400
- initial_solution = solution
401
- initial_obj_value = self._flc.compute_obj_value_from_solution(initial_solution,stage=2)
402
-
403
  self._obj_value = np.zeros(self._max_steps + 1)
404
  self._obj_value[0] = initial_obj_value
405
  self._solution = np.zeros((self._max_steps + 1, n), dtype=bool)
@@ -411,7 +463,6 @@ class MULTIPMP(PMPEnv):
411
  return self._get_obs(self._t)
412
 
413
  def _get_max_steps(self) -> list:
414
- print(self._all_solutions)
415
  tmp_all_solitions = list(self._all_solutions)
416
  count_true = [sum(s) for s in zip(*tmp_all_solitions)]
417
  max_steps = []
@@ -426,7 +477,6 @@ class MULTIPMP(PMPEnv):
426
  max_steps.append(len(old))
427
  for i in old:
428
  count_true[i] = count_true[i] - 1
429
- print(max_steps, old_idx, new_idx)
430
  return max_steps, old_idx, new_idx
431
 
432
  def _generate_new_instance(self) -> Tuple[np.ndarray, np.ndarray, int, int]:
@@ -440,41 +490,38 @@ class MULTIPMP(PMPEnv):
440
  demands = self._np_random.random(size=(n,))
441
  return points, demands, n, p
442
 
443
- def _load_multi_facility_data(self, data_npy) -> Tuple[np.ndarray, np.ndarray]:
444
- data = data_npy.split('\n')
445
- n = len(data)
446
- p = int((len(data[0].split(' '))-2) / 2)
447
-
448
- positions = []
449
- demands = []
450
- actual_facilities = []
451
- ps = []
452
- for row in data:
453
- row = row.split(' ')
454
- row = [x for x in row if len(x)]
455
- positions.append([float(row[0]), float(row[1])])
456
-
457
- demand = []
458
- for i in range(2, 2+p):
459
- demand.append(float(row[i]))
460
- demands.append(demand)
461
 
462
- actual_facility = []
463
- for i in range(2+p, 2+2*p):
464
- actual_facility.append(bool(int(float(row[i]))))
465
- actual_facilities.append(actual_facility)
466
-
467
- positions = np.array(positions)
468
- positions = np.deg2rad(positions)
469
- demands = np.array(demands)
470
- actual_facilities = np.array(actual_facilities)
471
- ps = actual_facilities.sum(axis=0)
472
 
473
- return positions, demands, n, ps
474
 
475
- def _load_multi_facility_solutions(self, boost) -> list:
476
- def load_model(positions, demands, n, p, boost):
477
- eval_env = EvalPMPEnv(self.cfg, positions, demands, n, p, boost)
478
  eval_env = DummyVecEnv([lambda: eval_env])
479
 
480
  policy_kwargs = get_policy_kwargs(self.cfg)
@@ -496,12 +543,8 @@ class MULTIPMP(PMPEnv):
496
  return eval_env.get_attr('_best_solution')[0]
497
 
498
  multi_solutions = []
499
- for i in range(len(self._all_p)):
500
- positions = self._all_points
501
- demands = self._all_demands[:,i]
502
- n = self._n
503
- p = self._all_p[i]
504
- model, env = load_model(positions,demands,n,p,boost)
505
  multi_solutions.append(get_optimal_solution(model, env))
506
 
507
  return multi_solutions
@@ -513,4 +556,64 @@ class MULTIPMP(PMPEnv):
513
  reward = 0.0
514
  return reward
515
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
516
 
 
13
  from facility_location.utils.config import Config
14
  from facility_location.env.facility_location_client import FacilityLocationClient
15
  from facility_location.env.obs_extractor import ObsExtractor
 
 
 
 
16
 
17
 
18
  class PMPEnv(gym.Env):
 
50
  'node_mask': gym.spaces.Box(low=0, high=1, shape=(self._node_range,), dtype=np.bool),
51
  'static_edge_mask': gym.spaces.Box(low=0, high=1, shape=(self._edge_range,), dtype=np.bool),
52
  'dynamic_edge_mask': gym.spaces.Box(low=0, high=1, shape=(self._edge_range,), dtype=np.bool),
53
+ # 'old_facility_mask': gym.spaces.Box(low=0, high=1, shape=(self._node_range,), dtype=np.bool),
54
+ # 'new_facility_mask': gym.spaces.Box(low=0, high=1, shape=(self._node_range,), dtype=np.bool),
55
  })
56
  if not self._popstar:
57
+ # self.action_space = gym.spaces.MultiDiscrete([self._node_range, self._node_range])
58
  self.action_space = gym.spaces.Discrete(self._node_range ** 2)
59
  else:
60
  self.action_space = gym.spaces.Discrete(self._node_range ** 2)
 
71
 
72
  def get_reward(self) -> float:
73
  reward = self._obj_value[self._t - 1] - self._obj_value[self._t]
74
+ # print(reward)
75
  return reward
76
 
77
  def _transform_action(self, action: np.ndarray) -> np.ndarray:
 
83
  def step(self, action: np.ndarray):
84
  if self._done:
85
  raise RuntimeError('Action taken after episode is done.')
86
+ # action = self._transform_action(action)
87
+ # obj_value, solution, info = self._flc.swap(action[0], action[1], self._t)
88
  obj_value, solution, info = self._flc.swap(action, self._t)
89
  self._t += 1
90
  self._done = (self._t == self._max_steps)
 
216
  class EvalPMPEnv(PMPEnv):
217
  def __init__(self,
218
  cfg: Config,
219
+ mode: Text,
220
+ eval_np: Tuple[int, int]):
221
+ print('eval_np:', eval_np)
222
+ if mode == 'val':
223
+ self._eval_num_cases = cfg.eval_specs['val_num_cases']
224
+ self._reward_coef = -1.0
225
+ elif mode == 'test':
226
+ self._eval_num_cases = cfg.eval_specs['test_num_cases']
227
+ self._reward_coef = 1.0
228
+ else:
229
+ raise ValueError(f'Unknown mode {mode}.')
230
+ self._mode = mode
231
+ self._eval_np = eval_np
232
+
233
  self._eval_seed = cfg.eval_specs['seed']
234
+ self._generate_eval_cases()
235
+ print('eval')
 
 
 
 
236
  super().__init__(cfg)
237
 
238
  def _set_node_edge_range(self) -> None:
239
  n, p = self._eval_np
240
+ if self.cfg.eval_specs['region'] is not None:
241
+ if self._mode == 'val':
242
+ self._node_range = n + 2
243
+ self._edge_range = n * p
244
+ elif self._mode == 'test':
245
+ self._node_range = n + 2
246
+ self._edge_range = n * p
247
+ # if self._edge_range > 1e5:
248
+ # self._edge_range = int(1e5)
249
+ else:
250
+ self._node_range = n + 2
251
+ self._edge_range = n * p * 4
252
+ # if self._edge_range > 5e3:
253
+ # self._edge_range = int(5e3)
254
 
255
  def get_eval_num_cases(self) -> int:
256
  return self._eval_num_cases
 
261
  def reset_instance_id(self) -> None:
262
  self._instance_id = 0
263
 
264
+ def _generate_eval_cases(self) -> None:
265
+ eval_np = self._eval_np
266
+ self._eval_n = eval_np[0]
267
+ self._eval_p = eval_np[1]
268
+
269
+ self._seed(self._eval_seed)
270
+ self._all_eval_points = self._np_random.uniform(size=(self._eval_num_cases + 1, self._eval_n, 2))
271
+ self._all_eval_demands = self._np_random.random(size=(self._eval_num_cases + 1, self._eval_n))
272
+
273
+ self._instance_id = 0
274
+
275
  def step(self, action: np.ndarray):
276
  if self._done:
277
  raise RuntimeError('Action taken after episode is done.')
 
286
  self._best_solution = solution
287
  self._last_best_t = self._t
288
  elif (self._t - self._last_best_t) % self._tabu_stable_steps == 0:
289
+ self._flc.reset_tabu_time()
290
+
291
+ # if self._done:
292
+ # plt.figure()
293
+ # plt.plot(self._obj_value)
294
+ # #save_path = self.cfg.load_model_path.split('/')[:-1].join('/') + f'/eval_{self._eval_np[0]}_{self._eval_np[1]}.png'
295
+ # save_path_list = self.cfg.load_model_path.split('/')[:-1] + [f'eval_{self._eval_np[0]}_{self._eval_np[1]}_{self._instance_id}.png']
296
+ # pickle_save_path_list = self.cfg.load_model_path.split('/')[:-1] + [f'eval_{self._eval_np[0]}_{self._eval_np[1]}_{self._instance_id}.pkl']
297
+ # save_path = '/'.join(save_path_list)
298
+ # print(save_path)
299
+ # plt.savefig(save_path)
300
+ # pickle.dump(self._obj_value, open('/'.join(pickle_save_path_list), 'wb'))
301
+
302
 
303
  return self._get_obs(self._t), reward, self._done, False, info
304
 
305
  def get_reward(self) -> float:
306
  if self._done:
307
+ reward = self._reward_coef * np.min(self._obj_value)
308
+ min_index = np.argmin(self._obj_value)
309
+ print(min_index/len(self._obj_value))
310
  else:
311
  reward = 0.0
312
 
 
314
 
315
  def get_best_solution(self) -> np.ndarray:
316
  return self._best_solution
317
+
318
+ def _generate_new_instance(self) -> Tuple[np.ndarray, np.ndarray, int, int]:
319
+ n = self._eval_n
320
+ p = self._eval_p
321
+ points = self._all_eval_points[self._instance_id]
322
+ demands = self._all_eval_demands[self._instance_id]
323
+ self._instance_id += 1
324
+ return points, demands, n, p
325
 
326
+ def _use_real_instance(self) -> Tuple[ndarray, ndarray, int, int]:
327
+ n = self._eval_n
328
+ p = self._eval_p
329
+ data_file = './data/{}/pkl/{}_{}.pkl'.format(self.cfg.eval_specs['region'], n, p)
330
+ print('eval:', data_file)
331
+ with open(data_file, 'rb') as f:
332
+ np_data = pickle.load(f)
333
+ info = np_data[1]
334
+ points = np.array([info[cbg]['pos'] for cbg in info])
335
+ demands = np.array([info[cbg]['demand'] for cbg in info])
336
+
337
+ self._instance_id += 1
338
+ # return super()._use_real_instance()
339
+
340
+ return points, demands, n, p
341
 
342
+ def reset(self, seed = 0) -> Dict:
343
+ if self._instance_id > self._eval_num_cases:
344
+ raise RuntimeError('No more cases to evaluate.')
345
+ if self._mode == 'test' or self._mode == 'val':
346
+ if self._eval_region is None:
347
+ points, demands, n, p = self._generate_new_instance()
348
+ self._flc.set_instance(points, demands, n, p, False)
349
+ else:
350
+ points, demands, n, p = self._use_real_instance()
351
+ self._flc.set_instance(points, demands, n, p, True)
352
+ else:
353
+ points, demands, n, p = self._generate_new_instance()
354
+ self._flc.set_instance(points, demands, n, p, False)
355
+
356
+ return self.prepare(n, p), {}
357
+
358
+ def prepare(self, n: int, p: int) -> Optional[Dict]:
359
+ if self.cfg.agent not in ['heuristic-greedy', 'heuristic-fastinterchange', 'ppo-random', 'rl-mlp', 'rl-gnn', 'rl-agnn']:
360
+ return None
361
+ else:
362
+ return super().prepare(n, p)
363
 
364
  def get_instance(self) -> Tuple[np.ndarray, np.ndarray, int, int]:
365
  points, demands, n, p = self._flc.get_instance()
 
373
  obj_value = self._flc.compute_obj_value()
374
  return obj_value
375
 
376
+ from stable_baselines3 import PPO
377
+ from stable_baselines3.common.vec_env import DummyVecEnv
378
+ from facility_location.agent import MaskedFacilityLocationActorCriticPolicy
379
+ from facility_location.utils.policy import get_policy_kwargs
380
+
381
  class MULTIPMP(PMPEnv):
382
  EPSILON = 1e-6
383
  def __init__(self,
384
+ cfg: Config):
 
 
385
  self.cfg = cfg
386
+ self._all_points, self._all_demands, self._n, self._all_p = self._load_multi_facility_data()
387
+ self._all_solutions = self._load_multi_facility_solutions()
 
 
 
 
388
  self._final_solutions = list(self._all_solutions)
389
+ self._num_types = len(self._all_p)
390
  self._current_type = 0
391
  self._all_max_steps, self._old_mask, self._new_mask = self._get_max_steps()
392
  super().__init__(cfg)
 
396
  self._edge_range = self._n * max(self._all_p)
397
 
398
  def step(self, action: np.ndarray):
 
 
 
 
 
 
399
  if self._done:
400
  raise RuntimeError('Action taken after episode is done.')
401
+ obj_value, solution, info = self._flc.swap(action, self._t)
402
  self._t += 1
403
  self._done = (self._t == self._all_max_steps[-1] and self._current_type == len(self._all_max_steps) - 1)
404
  self._obj_value[self._t] = obj_value
 
417
  self._final_solutions[self._current_type] = solution
418
  self._update_type()
419
 
 
 
 
420
  return self._get_obs(self._t), reward, self._done, False, info
421
 
422
  def reset(self, seed = 0) -> Optional[Dict]:
423
  self._current_type = 0
424
+ points = self._all_points[0]
425
+ demands = self._all_demands[0]
426
  n = self._n
427
  p = self._all_p[0]
428
  solution = self._all_solutions[0]
429
  self._multi_obj = 0
 
430
  self._flc.set_instance(points, demands, n, p, True)
431
 
432
  return self.prepare(n, p, solution), {}
 
434
  def _update_type(self):
435
  if self._current_type >= self._num_types:
436
  raise RuntimeError('Action taken after episode is done.')
437
+ self._current_type += 1
438
  if self._current_type < self._num_types - 1:
439
+ points = self._all_points[self._current_type]
440
+ demands = self._all_demands[self._current_type]
 
 
441
  n = self._n
442
  p = self._all_p[self._current_type]
443
  solution = self._all_solutions[self._current_type]
 
445
  self.prepare(n, p, solution)
446
 
447
  def prepare(self, n: int, p: int, solution: list) -> Dict:
448
+ initial_solution = solution
449
+ initial_obj_value = self._flc.compute_obj_value_from_solution(initial_solution)
450
  self._obs_extractor.reset()
451
  self._done = False
452
  self._t = 0
453
+ self._max_steps = self._all_max_steps[self._current_type]
454
+ self._flc.init_facility_mask(self._old_mask[self._current_type], self._new_mask[self._current_type])
 
 
 
 
 
 
 
455
  self._obj_value = np.zeros(self._max_steps + 1)
456
  self._obj_value[0] = initial_obj_value
457
  self._solution = np.zeros((self._max_steps + 1, n), dtype=bool)
 
463
  return self._get_obs(self._t)
464
 
465
  def _get_max_steps(self) -> list:
 
466
  tmp_all_solitions = list(self._all_solutions)
467
  count_true = [sum(s) for s in zip(*tmp_all_solitions)]
468
  max_steps = []
 
477
  max_steps.append(len(old))
478
  for i in old:
479
  count_true[i] = count_true[i] - 1
 
480
  return max_steps, old_idx, new_idx
481
 
482
  def _generate_new_instance(self) -> Tuple[np.ndarray, np.ndarray, int, int]:
 
490
  demands = self._np_random.random(size=(n,))
491
  return points, demands, n, p
492
 
493
+ def _load_multi_facility_data(self) -> Tuple[np.ndarray, np.ndarray]:
494
+ data_path = './data/{}/pkl'.format(self.cfg.eval_specs['region'])
495
+ file_list = os.listdir(data_path)
496
+ n = int(file_list[0].split('_')[0])
497
+ all_p = []
498
+ all_points = []
499
+ all_demands = []
500
+ for f in file_list:
501
+ p = int(f.split('_')[1].split('.')[0])
502
+ with open(os.path.join(data_path, f), 'rb') as ff:
503
+ np_data = pickle.load(ff)
504
+ info = np_data[1]
505
+ points = np.array([info[cbg]['pos'] for cbg in info])
506
+ demands = np.array([info[cbg]['demand'] for cbg in info])
507
+ all_p.append(p)
508
+ all_points.append(points)
509
+ all_demands.append(demands)
 
510
 
511
+ all_p = np.array(all_p)
512
+ all_points = np.array(all_points)
513
+ all_demands = np.array(all_demands)
514
+ all_demands_sum = np.sum(all_demands, axis=1)
515
+ new_index = np.argsort(all_demands_sum)
516
+ all_p = all_p[new_index]
517
+ all_points = all_points[new_index]
518
+ all_demands = all_demands[new_index]
 
 
519
 
520
+ return all_points, all_demands, n, all_p
521
 
522
+ def _load_multi_facility_solutions(self) -> list:
523
+ def load_model(eval_np):
524
+ eval_env = EvalPMPEnv(self.cfg, 'test', eval_np)
525
  eval_env = DummyVecEnv([lambda: eval_env])
526
 
527
  policy_kwargs = get_policy_kwargs(self.cfg)
 
543
  return eval_env.get_attr('_best_solution')[0]
544
 
545
  multi_solutions = []
546
+ for p in self._all_p:
547
+ model, env = load_model((self._n,p))
 
 
 
 
548
  multi_solutions.append(get_optimal_solution(model, env))
549
 
550
  return multi_solutions
 
556
  reward = 0.0
557
  return reward
558
 
559
+
560
+ # def __init__(self, cfg: Config, eval_np: list):
561
+ # self.cfg = cfg
562
+ # self.model_path = cfg.load_model_path
563
+ # self.eval_np_list = eval_np
564
+ # self._number = cfg.multi['number']
565
+ # self._conflict = cfg.multi['conflict']
566
+
567
+
568
+ # super().__init__(cfg)
569
+
570
+
571
+ # def load_model(eval_np):
572
+ # eval_env = EvalPMPEnv(self.cfg, 'test', eval_np)
573
+ # eval_env = DummyVecEnv([lambda: eval_env])
574
+
575
+ # policy_kwargs = get_policy_kwargs(self.cfg)
576
+ # test_model = PPO(MaskedFacilityLocationActorCriticPolicy,
577
+ # eval_env,
578
+ # verbose=1,
579
+ # policy_kwargs=policy_kwargs,
580
+ # device='cuda:1')
581
+ # train_model = PPO.load(self.model_path)
582
+ # test_model = test_model.set_parameters(train_model.get_parameters())
583
+ # return test_model, eval_env
584
+
585
+ # def get_optimal_solution(model, eval_env):
586
+ # obs = eval_env.reset()
587
+ # done = False
588
+
589
+ # while not done:
590
+ # action, _ = model.predict(obs, deterministic=True)
591
+ # obs, _, done, _, info = eval_env.step(action)
592
+
593
+ # return eval_env.get_best_solution()
594
+
595
+
596
+ # self.init_solutions = []
597
+ # for np in self.eval_np_list:
598
+ # model, env = load_model(np)
599
+ # self.init_solutions.append(get_optimal_solution(model, env))
600
+
601
+ # super.__init__(cfg)
602
+
603
+
604
+
605
+ # def reset(self, seed = 0) -> Dict:
606
+ # if self._instance_id > self._eval_num_cases:
607
+ # raise RuntimeError('No more cases to evaluate.')
608
+ # for env in self.envs:
609
+ # obs, _ = env.reset()
610
+ # return obs, _
611
+
612
+ # def get_reward(self) -> float:
613
+ # reward = 0.0
614
+ # if self._done:
615
+ # for env in self.envs:
616
+ # reward += env.get_reward()
617
+ # return reward
618
+
619
 
facility_location/env/tests/p-median.ipynb ADDED
The diff for this file is too large to render. See raw diff
 
facility_location/env/tests/render.ipynb ADDED
The diff for this file is too large to render. See raw diff
 
facility_location/env/utils/env_test.ipynb ADDED
The diff for this file is too large to render. See raw diff
 
facility_location/eval.py ADDED
@@ -0,0 +1,234 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import pickle
3
+
4
+ import setproctitle
5
+ from absl import app, flags
6
+ import time
7
+ import random
8
+ from typing import Tuple, Union, Text
9
+
10
+ import numpy as np
11
+ import torch as th
12
+
13
+ import sys
14
+ import gymnasium
15
+ sys.modules["gym"] = gymnasium
16
+
17
+ from stable_baselines3.common.evaluation import evaluate_policy
18
+ from stable_baselines3 import PPO
19
+ from stable_baselines3.common.monitor import Monitor
20
+ from stable_baselines3.common.vec_env import DummyVecEnv, VecEnvWrapper
21
+
22
+ from facility_location.agent.solver import PMPSolver
23
+ from facility_location.agent.ga import PMPGA
24
+ from facility_location.agent.heuristic import HeuristicRandom, HeuristicGreedy, HeuristicFastInterchange
25
+ from facility_location.agent.metaheuristic import TabuSearch, POPSTAR, VNS
26
+ from facility_location.env import EvalPMPEnv
27
+ from facility_location.utils import Config
28
+ from facility_location.agent import MaskedFacilityLocationActorCriticPolicy
29
+ from facility_location.utils.policy import get_policy_kwargs
30
+
31
+ import warnings
32
+ warnings.filterwarnings('ignore')
33
+
34
+ flags.DEFINE_string('cfg', None, 'Configuration file.')
35
+ flags.DEFINE_integer('global_seed', None, 'Used in env and weight initialization, does not impact action sampling.')
36
+ flags.DEFINE_string('root_dir', '/data2/suhongyuan/flp', 'Root directory for writing '
37
+ 'logs/summaries/checkpoints.')
38
+ flags.DEFINE_bool('tmp', False, 'Whether to use temporary storage.')
39
+ flags.DEFINE_enum('agent', None,
40
+ ['solver-gurobi', 'solver-gurobi-cmd', 'solver-pulp-cbc-cmd', 'solver-glpk-cmd', 'solver-mosek',
41
+ 'heuristic-random', 'heuristic-greedy', 'heuristic-fastinterchange',
42
+ 'metaheuristic-ts', 'metaheuristic-vns', 'metaheuristic-popstar',
43
+ 'ga',
44
+ 'ppo-random',
45
+ 'rl-mlp', 'rl-gnn', 'rl-agnn'],
46
+ 'Agent type.')
47
+ flags.DEFINE_string('model_path', None, 'Path to saved mode to evaluate.')
48
+
49
+ FLAGS = flags.FLAGS
50
+
51
+
52
+ AGENT = Union[PMPSolver, HeuristicRandom, HeuristicGreedy, HeuristicFastInterchange,
53
+ TabuSearch, VNS, POPSTAR, PMPGA, PPO]
54
+ BASELINE = Union[PMPSolver, HeuristicRandom, HeuristicGreedy, HeuristicFastInterchange,
55
+ TabuSearch, VNS, POPSTAR, PMPGA]
56
+
57
+
58
+ def get_model(cfg: Config,
59
+ env: Union[VecEnvWrapper, DummyVecEnv, EvalPMPEnv],
60
+ device: str) -> PPO:
61
+ policy_kwargs = get_policy_kwargs(cfg)
62
+ model = PPO(MaskedFacilityLocationActorCriticPolicy,
63
+ env,
64
+ verbose=1,
65
+ policy_kwargs=policy_kwargs,
66
+ device=device)
67
+ return model
68
+
69
+
70
+ def get_agent(cfg: Config,
71
+ env: Union[VecEnvWrapper, DummyVecEnv, EvalPMPEnv],
72
+ model_path: Text) -> AGENT:
73
+ if cfg.agent.startswith('solver'):
74
+ if cfg.agent == 'solver-gurobi':
75
+ agent = PMPSolver('GUROBI', env)
76
+ elif cfg.agent == 'solver-gurobi-cmd':
77
+ agent = PMPSolver('GUROBI_CMD', env)
78
+ elif cfg.agent == 'solver-pulp-cbc-cmd':
79
+ agent = PMPSolver('PULP_CBC_CMD', env)
80
+ elif cfg.agent == 'solver-glpk-cmd':
81
+ agent = PMPSolver('GLPK_CMD', env)
82
+ elif cfg.agent == 'solver-mosek':
83
+ agent = PMPSolver('MOSEK', env)
84
+ else:
85
+ raise ValueError(f'Agent {cfg.agent} not supported.')
86
+ elif cfg.agent.startswith('heuristic'):
87
+ if cfg.agent == 'heuristic-random':
88
+ agent = HeuristicRandom(cfg.seed, env)
89
+ elif cfg.agent == 'heuristic-greedy':
90
+ agent = HeuristicGreedy(env)
91
+ elif cfg.agent == 'heuristic-fastinterchange':
92
+ agent = HeuristicFastInterchange(env)
93
+ else:
94
+ raise ValueError(f'Agent {cfg.agent} not supported.')
95
+ elif cfg.agent.startswith('metaheuristic'):
96
+ if cfg.agent == 'metaheuristic-ts':
97
+ agent = TabuSearch(cfg, env)
98
+ elif cfg.agent == 'metaheuristic-vns':
99
+ agent = VNS(env)
100
+ elif cfg.agent == 'metaheuristic-popstar':
101
+ agent = POPSTAR(cfg, env)
102
+ else:
103
+ raise ValueError(f'Agent {cfg.agent} not supported.')
104
+ elif cfg.agent == 'ga':
105
+ agent = PMPGA(cfg, env)
106
+ elif cfg.agent == 'ppo-random':
107
+ agent = PPO("MultiInputPolicy", env, verbose=1)
108
+ elif cfg.agent in ['rl-mlp', 'rl-gnn', 'rl-agnn']:
109
+ test_model = get_model(cfg, env, device='cuda:3')
110
+ trained_model = PPO.load(model_path)
111
+ test_model.set_parameters(trained_model.get_parameters())
112
+ agent = test_model
113
+ else:
114
+ raise ValueError(f'Agent {cfg.agent} not supported.')
115
+ return agent
116
+
117
+
118
+ def evaluate(agent: AGENT,
119
+ env: Union[VecEnvWrapper, DummyVecEnv, EvalPMPEnv],
120
+ num_cases: int,
121
+ return_episode_rewards: bool):
122
+ if isinstance(agent, PPO):
123
+ return evaluate_ppo(agent, env, num_cases, return_episode_rewards=return_episode_rewards)
124
+ else:
125
+ return evaluate_baseline(agent, env, num_cases)
126
+
127
+ from stable_baselines3.common.callbacks import BaseCallback
128
+
129
+
130
+ def evaluate_ppo(agent: PPO, env: EvalPMPEnv, num_cases: int, return_episode_rewards: bool) -> Tuple[float, float]:
131
+ # class BestSolutionCallback(BaseCallback):
132
+ # def __init__(self, env, verbose=0):
133
+ # super(BestSolutionCallback, self).__init__(verbose)
134
+ # self.eval_env = env
135
+ # self.best_solution = None
136
+ # self.best_reward = float('-inf')
137
+
138
+ # def _on_rollout_end(self) -> None:
139
+ # current_obj_value = np.min(self.model.env._obj_value)
140
+ # current_solution = self.model.env._best_solution
141
+
142
+ # if current_obj_value < self.best_obj_value:
143
+ # self.best_obj_value = current_obj_value
144
+ # self.best_solution = current_solution
145
+
146
+ # best_solution_callback = BestSolutionCallback(env)
147
+ rewards, _ = evaluate_policy(agent, env, n_eval_episodes=num_cases, return_episode_rewards=return_episode_rewards)
148
+ # best_solution = best_solution_callback.best_solution
149
+
150
+ return rewards
151
+
152
+
153
+ def evaluate_baseline(
154
+ agent: BASELINE,
155
+ env: EvalPMPEnv,
156
+ num_cases: int):
157
+ rewards = np.zeros(num_cases)
158
+ for case_idx in range(num_cases):
159
+ env.reset()
160
+ solution = agent.solve()
161
+ reward = env.evaluate(solution)
162
+ rewards[case_idx] = reward
163
+ return rewards
164
+
165
+ def calculate_gap(gurobi_obj, method_obj):
166
+ method_obj = np.array(method_obj)
167
+ gap = (method_obj - gurobi_obj) / gurobi_obj
168
+ mean_gap = np.mean(gap)
169
+ std_gap = np.std(gap)
170
+
171
+ return mean_gap, std_gap
172
+
173
+
174
+ def main(_):
175
+ setproctitle.setproctitle('rl@suhy')
176
+
177
+ th.manual_seed(FLAGS.global_seed)
178
+ np.random.seed(FLAGS.global_seed)
179
+ random.seed(FLAGS.global_seed)
180
+
181
+ cfg = Config(FLAGS.cfg, FLAGS.global_seed, FLAGS.tmp, FLAGS.root_dir, FLAGS.agent, model_path=FLAGS.model_path)
182
+
183
+ if cfg.eval_specs['region'] is None:
184
+ eval_np = cfg.eval_specs['test_np']
185
+ else:
186
+ eval_path = './data/{}/pkl'.format(cfg.eval_specs['region'])
187
+ files = os.listdir(eval_path)
188
+ eval_np = []
189
+
190
+ for f in files:
191
+ eval_np.append(tuple(map(int, f.split('.')[0].split('_'))))
192
+ eval_np = sorted(eval_np, key=lambda x: (x[0], x[1]))
193
+
194
+ for (n, p) in eval_np:
195
+ print(f'case ({n}, {p}):')
196
+ eval_env = EvalPMPEnv(cfg, 'test', (n, p))
197
+ eval_num_cases = eval_env.get_eval_num_cases()
198
+
199
+ if cfg.agent in ['rl-mlp', 'rl-gnn', 'rl-agnn']:
200
+ eval_env = Monitor(eval_env)
201
+ eval_env = DummyVecEnv([lambda: eval_env])
202
+ model_path = os.path.join(cfg.root_dir, 'output', FLAGS.model_path)
203
+
204
+ else:
205
+ model_path = None
206
+
207
+ agent = get_agent(cfg, eval_env, model_path)
208
+
209
+ start_time = time.time()
210
+ episode_rewards = evaluate(agent, eval_env, eval_num_cases, return_episode_rewards=True)
211
+ eval_time = time.time() - start_time
212
+
213
+ if cfg.agent == 'solver-gurobi':
214
+ pickle.dump(episode_rewards, open(f'gurobi_result/{n}_{p}.pkl', 'wb'))
215
+ else:
216
+ try:
217
+ gurobi_obj = pickle.load(open(f'gurobi_result/{n}_{p}.pkl', 'rb'))
218
+ mean_gap, std_gap = calculate_gap(gurobi_obj, episode_rewards)
219
+ print(f'\t mean gap: {mean_gap}')
220
+ print(f'\t std gap: {std_gap}')
221
+ except:
222
+ pass
223
+
224
+ print(f'\t time: {eval_time / eval_num_cases}')
225
+
226
+
227
+ if __name__ == '__main__':
228
+ flags.mark_flags_as_required([
229
+ 'cfg',
230
+ 'global_seed',
231
+ 'agent'
232
+ ])
233
+ app.run(main)
234
+
facility_location/multi_eval.py CHANGED
@@ -20,6 +20,9 @@ from stable_baselines3.common.monitor import Monitor
20
  from stable_baselines3.common.vec_env import DummyVecEnv, VecEnvWrapper
21
 
22
  from facility_location.agent.solver import PMPSolver
 
 
 
23
  from facility_location.env import EvalPMPEnv, MULTIPMP
24
  from facility_location.utils import Config
25
  from facility_location.agent import MaskedFacilityLocationActorCriticPolicy
@@ -28,8 +31,29 @@ from facility_location.utils.policy import get_policy_kwargs
28
  import warnings
29
  warnings.filterwarnings('ignore')
30
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
 
32
- AGENT = Union[PMPSolver, PPO]
33
 
34
  def get_model(cfg: Config,
35
  env: Union[VecEnvWrapper, DummyVecEnv, EvalPMPEnv],
@@ -46,8 +70,43 @@ def get_model(cfg: Config,
46
  def get_agent(cfg: Config,
47
  env: Union[VecEnvWrapper, DummyVecEnv, EvalPMPEnv],
48
  model_path: Text) -> AGENT:
49
- if cfg.agent in ['rl-mlp', 'rl-gnn', 'rl-agnn']:
50
- test_model = get_model(cfg, env, device='cuda:0')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51
  trained_model = PPO.load(model_path)
52
  test_model.set_parameters(trained_model.get_parameters())
53
  agent = test_model
@@ -63,7 +122,7 @@ def evaluate(agent: AGENT,
63
  if isinstance(agent, PPO):
64
  return evaluate_ppo(agent, env, num_cases, return_episode_rewards=return_episode_rewards)
65
  else:
66
- raise ValueError(f'Agent {agent} not supported.')
67
 
68
  from stable_baselines3.common.callbacks import BaseCallback
69
 
@@ -72,25 +131,77 @@ def evaluate_ppo(agent: PPO, env: EvalPMPEnv, num_cases: int, return_episode_rew
72
  rewards, _ = evaluate_policy(agent, env, n_eval_episodes=num_cases, return_episode_rewards=return_episode_rewards)
73
  return rewards
74
 
 
 
 
 
 
 
 
 
 
 
 
75
 
76
- def main(data_npy, boost=False):
77
- th.manual_seed(0)
78
- np.random.seed(0)
79
- random.seed(0)
80
- model_path = './facility_location/best_model.zip'
81
-
82
- cfg = Config('plot', 0, False, '/data2/suhongyuan/flp', 'rl-gnn', model_path=model_path)
83
 
84
- eval_env = MULTIPMP(cfg, data_npy, boost)
85
- eval_env = Monitor(eval_env)
86
- eval_env = DummyVecEnv([lambda: eval_env])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
87
  agent = get_agent(cfg, eval_env, model_path)
88
  start_time = time.time()
89
- _ = evaluate(agent, eval_env, 1, return_episode_rewards=True)
90
  eval_time = time.time() - start_time
 
 
 
 
 
 
 
 
 
 
91
  print(f'\t time: {eval_time}')
92
 
93
 
94
  if __name__ == '__main__':
 
 
 
 
 
95
  app.run(main)
96
 
 
20
  from stable_baselines3.common.vec_env import DummyVecEnv, VecEnvWrapper
21
 
22
  from facility_location.agent.solver import PMPSolver
23
+ from facility_location.agent.ga import PMPGA
24
+ from facility_location.agent.heuristic import HeuristicRandom, HeuristicGreedy, HeuristicFastInterchange
25
+ from facility_location.agent.metaheuristic import TabuSearch, POPSTAR, VNS
26
  from facility_location.env import EvalPMPEnv, MULTIPMP
27
  from facility_location.utils import Config
28
  from facility_location.agent import MaskedFacilityLocationActorCriticPolicy
 
31
  import warnings
32
  warnings.filterwarnings('ignore')
33
 
34
+ flags.DEFINE_string('cfg', None, 'Configuration file.')
35
+ flags.DEFINE_integer('global_seed', None, 'Used in env and weight initialization, does not impact action sampling.')
36
+ flags.DEFINE_string('root_dir', '/data2/suhongyuan/flp', 'Root directory for writing '
37
+ 'logs/summaries/checkpoints.')
38
+ flags.DEFINE_bool('tmp', False, 'Whether to use temporary storage.')
39
+ flags.DEFINE_enum('agent', None,
40
+ ['solver-gurobi', 'solver-gurobi-cmd', 'solver-pulp-cbc-cmd', 'solver-glpk-cmd', 'solver-mosek',
41
+ 'heuristic-random', 'heuristic-greedy', 'heuristic-fastinterchange',
42
+ 'metaheuristic-ts', 'metaheuristic-vns', 'metaheuristic-popstar',
43
+ 'ga',
44
+ 'ppo-random',
45
+ 'rl-mlp', 'rl-gnn', 'rl-agnn'],
46
+ 'Agent type.')
47
+ flags.DEFINE_string('model_path', None, 'Path to saved mode to evaluate.')
48
+
49
+ FLAGS = flags.FLAGS
50
+
51
+
52
+ AGENT = Union[PMPSolver, HeuristicRandom, HeuristicGreedy, HeuristicFastInterchange,
53
+ TabuSearch, VNS, POPSTAR, PMPGA, PPO]
54
+ BASELINE = Union[PMPSolver, HeuristicRandom, HeuristicGreedy, HeuristicFastInterchange,
55
+ TabuSearch, VNS, POPSTAR, PMPGA]
56
 
 
57
 
58
  def get_model(cfg: Config,
59
  env: Union[VecEnvWrapper, DummyVecEnv, EvalPMPEnv],
 
70
  def get_agent(cfg: Config,
71
  env: Union[VecEnvWrapper, DummyVecEnv, EvalPMPEnv],
72
  model_path: Text) -> AGENT:
73
+ if cfg.agent.startswith('solver'):
74
+ if cfg.agent == 'solver-gurobi':
75
+ agent = PMPSolver('GUROBI', env)
76
+ elif cfg.agent == 'solver-gurobi-cmd':
77
+ agent = PMPSolver('GUROBI_CMD', env)
78
+ elif cfg.agent == 'solver-pulp-cbc-cmd':
79
+ agent = PMPSolver('PULP_CBC_CMD', env)
80
+ elif cfg.agent == 'solver-glpk-cmd':
81
+ agent = PMPSolver('GLPK_CMD', env)
82
+ elif cfg.agent == 'solver-mosek':
83
+ agent = PMPSolver('MOSEK', env)
84
+ else:
85
+ raise ValueError(f'Agent {cfg.agent} not supported.')
86
+ elif cfg.agent.startswith('heuristic'):
87
+ if cfg.agent == 'heuristic-random':
88
+ agent = HeuristicRandom(cfg.seed, env)
89
+ elif cfg.agent == 'heuristic-greedy':
90
+ agent = HeuristicGreedy(env)
91
+ elif cfg.agent == 'heuristic-fastinterchange':
92
+ agent = HeuristicFastInterchange(env)
93
+ else:
94
+ raise ValueError(f'Agent {cfg.agent} not supported.')
95
+ elif cfg.agent.startswith('metaheuristic'):
96
+ if cfg.agent == 'metaheuristic-ts':
97
+ agent = TabuSearch(cfg, env)
98
+ elif cfg.agent == 'metaheuristic-vns':
99
+ agent = VNS(env)
100
+ elif cfg.agent == 'metaheuristic-popstar':
101
+ agent = POPSTAR(cfg, env)
102
+ else:
103
+ raise ValueError(f'Agent {cfg.agent} not supported.')
104
+ elif cfg.agent == 'ga':
105
+ agent = PMPGA(cfg, env)
106
+ elif cfg.agent == 'ppo-random':
107
+ agent = PPO("MultiInputPolicy", env, verbose=1)
108
+ elif cfg.agent in ['rl-mlp', 'rl-gnn', 'rl-agnn']:
109
+ test_model = get_model(cfg, env, device='cuda:3')
110
  trained_model = PPO.load(model_path)
111
  test_model.set_parameters(trained_model.get_parameters())
112
  agent = test_model
 
122
  if isinstance(agent, PPO):
123
  return evaluate_ppo(agent, env, num_cases, return_episode_rewards=return_episode_rewards)
124
  else:
125
+ return evaluate_baseline(agent, env, num_cases)
126
 
127
  from stable_baselines3.common.callbacks import BaseCallback
128
 
 
131
  rewards, _ = evaluate_policy(agent, env, n_eval_episodes=num_cases, return_episode_rewards=return_episode_rewards)
132
  return rewards
133
 
134
+ def evaluate_baseline(
135
+ agent: BASELINE,
136
+ env: EvalPMPEnv,
137
+ num_cases: int):
138
+ rewards = np.zeros(num_cases)
139
+ for case_idx in range(num_cases):
140
+ env.reset()
141
+ solution = agent.solve()
142
+ reward = env.evaluate(solution)
143
+ rewards[case_idx] = reward
144
+ return rewards
145
 
146
+ def calculate_gap(gurobi_obj, method_obj):
147
+ method_obj = np.array(method_obj)
148
+ gap = (method_obj - gurobi_obj) / gurobi_obj
149
+ mean_gap = np.mean(gap)
150
+ std_gap = np.std(gap)
 
 
151
 
152
+ return mean_gap, std_gap
153
+
154
+
155
+ def main(_):
156
+ setproctitle.setproctitle('rl@suhy')
157
+
158
+ th.manual_seed(FLAGS.global_seed)
159
+ np.random.seed(FLAGS.global_seed)
160
+ random.seed(FLAGS.global_seed)
161
+
162
+ cfg = Config(FLAGS.cfg, FLAGS.global_seed, FLAGS.tmp, FLAGS.root_dir, FLAGS.agent, model_path=FLAGS.model_path)
163
+
164
+ # if cfg.eval_specs['region'] is None:
165
+ # eval_np = cfg.eval_specs['test_np']
166
+ # else:
167
+ # eval_path = './data/{}/pkl'.format(cfg.eval_specs['region'])
168
+ # files = os.listdir(eval_path)
169
+ # eval_np = []
170
+
171
+ # for f in files:
172
+ # eval_np.append(tuple(map(int, f.split('.')[0].split('_'))))
173
+ # eval_np = sorted(eval_np, key=lambda x: (x[0], x[1]))
174
+ eval_env = MULTIPMP(cfg)
175
+
176
+ if cfg.agent in ['rl-mlp', 'rl-gnn', 'rl-agnn']:
177
+ eval_env = Monitor(eval_env)
178
+ eval_env = DummyVecEnv([lambda: eval_env])
179
+ model_path = os.path.join(cfg.root_dir, 'output', FLAGS.model_path)
180
+ else:
181
+ model_path = None
182
+
183
  agent = get_agent(cfg, eval_env, model_path)
184
  start_time = time.time()
185
+ episode_rewards = evaluate(agent, eval_env, 1, return_episode_rewards=True)
186
  eval_time = time.time() - start_time
187
+
188
+
189
+ # if cfg.agent == 'solver-gurobi':
190
+ # pickle.dump(episode_rewards, open(f'gurobi_result/{n}_{p}.pkl', 'wb'))
191
+ # else:
192
+ # gurobi_obj = pickle.load(open(f'gurobi_result/{n}_{p}.pkl', 'rb'))
193
+ # mean_gap, std_gap = calculate_gap(gurobi_obj, episode_rewards)
194
+ # print(f'\t mean gap: {mean_gap}')
195
+ # print(f'\t std gap: {std_gap}')
196
+ print(f'\t reward: {episode_rewards}')
197
  print(f'\t time: {eval_time}')
198
 
199
 
200
  if __name__ == '__main__':
201
+ flags.mark_flags_as_required([
202
+ 'cfg',
203
+ 'global_seed',
204
+ 'agent'
205
+ ])
206
  app.run(main)
207
 
facility_location/solutions.pkl DELETED
@@ -1,3 +0,0 @@
1
- version https://git-lfs.github.com/spec/v1
2
- oid sha256:eb82aae0d86e760c3a7899f3e8b031d2a7b707d88188a47bca1c673bfb9a1d03
3
- size 156
 
 
 
 
facility_location/test.ipynb ADDED
@@ -0,0 +1,425 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "code",
5
+ "execution_count": 2,
6
+ "metadata": {},
7
+ "outputs": [
8
+ {
9
+ "name": "stdout",
10
+ "output_type": "stream",
11
+ "text": [
12
+ "[[0. 0. 0. ... 0. 0. 0.]\n",
13
+ " [0. 0. 0. ... 0. 0. 0.]\n",
14
+ " [0. 0. 0. ... 0. 0. 0.]\n",
15
+ " ...\n",
16
+ " [0. 0. 0. ... 0. 0. 0.]\n",
17
+ " [0. 0. 0. ... 0. 0. 0.]\n",
18
+ " [0. 0. 0. ... 0. 0. 0.]]\n",
19
+ "[0, 1.3065473509668304, 0, 0, 0, 0, 0, 0, 0, 0, 1.0119895121655276, 0, 0, 0, 1.8436509672479273, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1.1267322635704136, 0, 0, 0, 0, 0, 0, 0, 0, 1.7347805051324074, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1.8483172051875574, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1.5343659059282944, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1.3037638449483402, 0, 0, 0, 0, 1.3504727008855124, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1.2921978917216865, 0, 0, 0, 1.108479378407742, 1.653393678507642, 0, 0, 1.0881684264537401, 0, 0, 0, 0, 1.0623021918455655, 0, 1.875191256591029, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1.6073341680760638, 0, 0, 0, 1.9805523895709318, 0, 0, 1.403634942312521, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1.5219421212115591, 0, 1.138657038124882, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1.7630849587454187, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1.9569291222415044, 0, 0, 1.4085844528393465, 0, 0, 0, 0, 0, 1.0585786345260078, 0, 0, 0, 0, 1.736349229376354, 0, 0, 0, 1.8004691226858753, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1.9779176202803515, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1.2449110835085255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1.1868774604112011, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1.076155348166279, 0, 1.7387446698801625, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1.9171840290289772, 0, 0, 0, 0, 0, 0, 0, 0, 1.716298965206781, 0, 0, 0, 0, 1.1447802494381323, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1.0112323315963594, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1.3254928410498104, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1.8373649059065984, 0, 0, 1.5804061346245062, 0, 0, 1.1179927616272543, 1.9732771314115762, 0, 0, 0, 1.0791197901446883, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1.4381135247848582, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1.9562907397004219, 0, 0, 0, 0, 0, 0, 1.7499225320939327, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1.9541760847800393, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1.679649307061875, 0, 0, 0, 1.2385975866230923, 0, 0, 0, 0, 0, 0, 1.9803317984826894, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1.3671411900289383, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1.8917328894219043, 0, 0, 0, 1.7099108098933784, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1.615200505489025, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1.9019674243157896, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1.983515852879571, 0, 0, 1.5882742759320192, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1.1412517855055038, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1.8094776841552782, 0, 0, 0, 0, 0, 0, 0, 0, 1.021378272713097, 0, 0, 0, 0, 1.9429265858537925, 0, 0, 0, 1.7935205973004997, 0, 0, 1.6377902540912914, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1.8366946607857926, 1.4781106136146915, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1.1568968466422547, 1.1573005183296878, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1.9279504608405298, 0, 0, 0, 0, 0, 0, 0, 0, 1.5247602593849523, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1.0022484037743946, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1.676358929570334, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1.3344155679542609, 0, 1.0228587657502164, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1.4178207321282081, 0, 0, 0, 1.3399631548961843, 0, 0, 0, 0, 1.2014533629641875, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1.655018985449563, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1.6369690332038644, 1.4444089312928114, 0, 0, 0, 0, 0, 0, 0, 1.5973146475663211, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1.2790129140270188, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1.157209813742849, 0, 0, 1.8558075169258035, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1.1418489101240186, 0, 0, 0, 1.6728632989913017, 0, 0, 0, 0, 1.9733370583105247, 0, 0, 0, 0, 0, 0, 0, 0, 1.2162257115893476, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1.8103211556841596, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1.4623508697193839, 0, 0, 0, 0, 0, 0, 1.3820356674348546, 0, 0, 0, 0, 0, 0, 0, 0, 1.3796803102948338, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1.8783449770955891, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1.8283355432966881, 0, 1.0616043707520344, 1.0338315463576362, 0, 0, 0, 0, 0, 1.8075056506854377, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1.2382511252086272, 0]\n",
20
+ "[0.9041395630814669, 0, 0.08074497862228569, 0.8222853202667209, 0.11502836231273406, 0.12388377758142011, 0.9935610059555571, 0.9500722273849179, 0.2274986811231311, 0.9331586568638208, 0, 0.10197305464416428, 0.8335838629139186, 0.29722262440052416, 0, 0.5570156829360735, 0.5113093524387131, 0.3945705316981616, 0.700834556583633, 0.5959949562111623, 0.9194833740276643, 0.03639432225067596, 0.8619375383697131, 0.40098857082359296, 0.901096383302275, 0.3326271601473464, 0.40648530281448214, 0.19179973417364282, 0.7384218162811385, 0.606170113450668, 0.2034400951553571, 0.7360049055396741, 0.4377924152683431, 0.701894568371572, 0.8384179992672554, 0.36009447101265624, 0.057089388308546374, 0.6471204277567344, 0.5804477088982322, 0.22360844388185153, 0.3907312862968938, 0.8129163728117023, 0.24849371594354686, 0.6143476258617578, 0.964437866304353, 0.4779139542289089, 0.9551424774494233, 0, 0.6587018840348203, 0.7177205710440928, 0.6803073279158656, 0.7596716797549441, 0.2731038875379401, 0.9963450007000443, 0.9338437488860837, 0.6672254590384904, 0, 0.9766142306013038, 0.3336436044180108, 0.8552466414429304, 0.3773010552363938, 0.7982391639938732, 0.15713379655188064, 0.649499265386598, 0.01111038028608069, 0.7359429126326681, 0.3697736282904457, 0.5188659999559949, 0.7312843130094226, 0.6283878578882, 0.5858647019277301, 0.2568404400053579, 0.26637932073974946, 0.08311060097066814, 0.6823101071566782, 0.5117924556973708, 0.7727964428980895, 0.4871347548357592, 0.23944186531581768, 0, 0.4488843107933801, 0.17050240445830211, 0.32758814288161064, 0.9684889557655156, 0.18713628646961955, 0.23512499010283083, 0.6908607124959899, 0.16292687259130212, 0.5908977261164322, 0.2628512420925919, 0.12637272083797602, 0.8524141068494073, 0.40985597531278284, 0.7007238582874573, 0.27977563104156644, 0.8377433260012307, 0.3438029194739557, 0.5029709995253282, 0.13963145534906607, 0.7768401182629143, 0.8888622066634125, 0.5957917668638463, 0, 0.8976957143183216, 0.6196252423577024, 0.6671880681983002, 0.006747063804729003, 0.7333057215752168, 0.6237054709832023, 0.0735936501961536, 0.8078692846944199, 0.29362729097622287, 0, 0.777762899494489, 0.029937563731620598, 0.9423786649864392, 0.8500751414105719, 0, 0.18379075956506563, 0.7771642104693584, 0.2755344339426994, 0.5844115961137218, 0.08557307910973255, 0.6576189712202234, 0.07014222670294057, 0.059942478070522554, 0.21400722036017483, 0.8715741716698897, 0.41683913664736416, 0.34908168135405493, 0.6741233809948677, 0.04643162148635738, 0.41054193179223764, 0, 0.45598799782701915, 0.8055978593685426, 0.9253146540063065, 0, 0, 0.7792616871813225, 0.6943143812539746, 0, 0.1034712744967976, 0.04313120226573264, 0.2751339614831234, 0.37529960202516477, 0, 0.6041632897991356, 0, 0.4377224992464337, 0.848388109456763, 0.7489141481736912, 0.9320581102000423, 0.34383612164271826, 0.8958464142568214, 0.03235173543474967, 0.4298588684324347, 0.7419659608225888, 0.2786508676578431, 0.7371351320924223, 0.8471415929527776, 0.42416311347299496, 0, 0.7054829673373973, 0.7285328523901956, 0.9173548974710188, 0, 0.559696900477303, 0.03522671001076627, 0, 0.9459404485454181, 0.13167389783915762, 0.5403952264688044, 0.4885489232108575, 0.9738268207931763, 0.17282404625847003, 0.9887621562669708, 0.9004641077939243, 0.3973853074935946, 0.8228046331189511, 0, 0.339142358676055, 0, 0.10991076610928008, 0.25036814672138286, 0.748226432068904, 0.27994481089578416, 0.2950666458236678, 0.9318863921563708, 0.37473607423307753, 0.6294638737449255, 0.011647920169922, 0.5818789092384103, 0.20693661728415869, 0.06620671237320896, 0.7561448831436088, 0.8691939036953821, 0.2840024317972576, 0.6977782413872717, 0.6464567063052008, 0.8599739468079631, 0.8903974329001684, 0, 0.1251244494656777, 0.5152328277644084, 0.5180129200004933, 0.8192211258668435, 0.7623893292489352, 0.3511103448182544, 0.9811217812918138, 0.08109105051697707, 0.5215649551033613, 0, 0.08182277889955691, 0.008911978495510176, 0, 0.20936169974897945, 0.9606710222581915, 0.5403241486294552, 0.5500537132411906, 0.9249699069700033, 0, 0.15251204099627724, 0.7105410992845586, 0.5554157563643312, 0.5038499237684088, 0, 0.7346017403533128, 0.25681088681861497, 0.4552903297148797, 0, 0.5324962682506602, 0.3578657866335586, 0.2377353950948864, 0.8740207917956883, 0.7878675498728824, 0.9106232602973864, 0.7399305059477627, 0.5230520293454254, 0.7975270964647226, 0.4494205530516001, 0.7861022185941772, 0.8860061656787065, 0.298510561162151, 0.07792259650385036, 0.6736325460609645, 0.8779086216893747, 0.8496589885454549, 0.12043620656745824, 0.6543672435497736, 0.2856518449684723, 0.7427512962401306, 0.545746723389937, 0.1614960444663296, 0.9985130190537231, 0.5378096758880552, 0.9209592614862138, 0.5492905986503386, 0.22402881267169894, 0.4727283833715792, 0.08284898363778104, 0.6700098678404831, 0.8263663177408445, 0.347954172253078, 0.793957838138496, 0.47686802583853194, 0.027731088228663436, 0.4106428230860101, 0.8580325647370642, 0.30910601836282603, 0.18961134904524657, 0.8618852602762438, 0.14544712498446244, 0.055424540254410126, 0.48040718196410115, 0.8814855320488243, 0.1916350237168961, 0.48463807674801196, 0, 0.8773885694204007, 0.13331555808834306, 0.6792724057017732, 0.5409699556538768, 0.29660415998011946, 0.15066922145256256, 0.4420588634148688, 0.5753406008853933, 0.3319637395192826, 0.2047850794839846, 0.6288737583881022, 0.5907498395934051, 0.8833944488752735, 0.751144322223856, 0.28728278022586895, 0.12124339212180901, 0.03534714877108136, 0.9872456004096749, 0.3877636751200386, 0.42973253874392237, 0, 0.8067921395105366, 0.9683986536960479, 0.01158396954883445, 0.4696422078397753, 0.31360953027159777, 0.9489858352484257, 0.870737321574568, 0.3072468655227133, 0.09829232669040133, 0, 0.8527033281738108, 0.14097552735219554, 0.9030446674312727, 0.5512484360945659, 0.6034029834956395, 0.9409162663369613, 0.06821104586006099, 0.264181560575097, 0.38505376572100125, 0, 0.07142820128527083, 0, 0.8890022111455872, 0.21925741439376267, 0.7040650671360058, 0.4694101807181672, 0.10864738888572867, 0.49977630026616204, 0.5463131559326317, 0.6312467471741934, 0.14410010930014328, 0.28547049694329496, 0.2521182154499988, 0.10754814331182128, 0.6803197530037096, 0.3445923137962905, 0, 0.3943182102707181, 0.9401367431475006, 0.08922097422544173, 0.5122897539003748, 0.160490512561834, 0.9826345903226602, 0.3352822674749646, 0.1809449426346067, 0, 0.07070862051524007, 0.43700917176439535, 0.4019675833768357, 0.5858729584018305, 0, 0.4323754653350079, 0.5435447823573529, 0.5249053364543692, 0.2760924417134334, 0.7918107455486161, 0.3508338752689446, 0.5772386163105846, 0.7836279026145342, 0.5623593108720227, 0.8941759904811595, 0.616344543050291, 0.024039831287841595, 0.8091744939059091, 0.20219385173577875, 0.7352105259606261, 0.7291878413577416, 0.9690568869515104, 0.8639890441392747, 0.26775948568076724, 0.6456125369468928, 0.8633019490227369, 0.46019650427695513, 0.13733931953796363, 0.7009943564550838, 0, 0.8765208071298392, 0.3510481090866573, 0.952741243119236, 0.6085370909733463, 0.060591668877822635, 0.593458806949922, 0.32999824176331993, 0.2005563715407065, 0.6356515525522672, 0.5313310156503008, 0, 0.6526157579542673, 0.16528648691346326, 0.5880637184852238, 0.8403160384815712, 0.7753748108425729, 0.43479104149265213, 0.8075093032887632, 0.24882971412296206, 0.993218223804026, 0.4075145035481623, 0.659057426502469, 0.7096298365432919, 0.38871327370864295, 0, 0.0059850553267807305, 0.0051590124390130665, 0, 0.4339201810744312, 0.6589340924156193, 0, 0, 0.0183747786202757, 0.641728966555246, 0.13788554402047293, 0, 0.04142787222083466, 0.5437210792143448, 0.6682354838913858, 0.4863337541023133, 0.2516239362588567, 0.08413190053059949, 0.9676267580134232, 0.22575297983288822, 0.1244255557152607, 0.8224185681282771, 0.17799363953067182, 0, 0.5025441510675511, 0.5019710265230332, 0.9357275145619265, 0.15836873585980826, 0.5713881585816664, 0.22827904180589342, 0.4775693009965608, 0.6086289931020366, 0.2583546327316848, 0.5103052594959362, 0.7145900448524471, 0.10295397178917542, 0, 0.09275601610146023, 0.6991098891594635, 0.7029764190768392, 0.6212182473669104, 0.5820433796886109, 0.5388736199502143, 0, 0.9944249844077415, 0.40857486403028265, 0.9661269137950269, 0.9403489056304841, 0.6726652292399451, 0.9051335183708998, 0.3164921310764257, 0.9023366050597892, 0.30852888389712896, 0.1533769072061496, 0.05989727443934789, 0.1843064816232104, 0.1798016691253399, 0, 0.6178075591514937, 0.3686226710481947, 0.6184461049971367, 0.8851501336979073, 0.06093147298207546, 0.6256799742565118, 0.7849115120346345, 0.3784104526133262, 0.9371851888020702, 0.684972759585991, 0.7949878962983897, 0.8616055799031592, 0.479746692653625, 0.6124871523062442, 0.10666483557632933, 0.006180345888404548, 0.6735977991008641, 0.10158871183327556, 0.591704934660402, 0.2312087408602581, 0.9815148154265422, 0.8485224448763321, 0.6455132893667194, 0.30405268130718377, 0, 0.6843958862522528, 0.9202546611995617, 0.9155463294525774, 0, 0.2465322719342622, 0.7116900962619911, 0.6852435432761507, 0.36921139065044417, 0.24079805201788684, 0.5279979865872105, 0, 0.4580950598339274, 0.09416817268177735, 0.24485156044670375, 0.6795116598241068, 0.10926978632127848, 0.4291183769079828, 0.3173187695944222, 0.33484985397163214, 0.038161498557721774, 0, 0.8512067172888305, 0.3269439340369171, 0.09063192594923575, 0.21357890043557815, 0.477913461503943, 0.562248630937037, 0.8926437627426074, 0.6478237578129892, 0.48206217605588086, 0.34727516061731833, 0.9133360767841633, 0.9965298351093996, 0.9310818327939521, 0.9203270594874299, 0.427661452528471, 0.08481245152119843, 0.27825465814929184, 0.338289782286603, 0.8310840927876677, 0, 0.9361644149777257, 0.46751272795172805, 0.06916297361837564, 0, 0.2667695364435009, 0.3914438449163915, 0.9089951984770299, 0.26705023736611455, 0.7140484248458, 0.9328715783389795, 0.8784764173296057, 0.43864057572631243, 0.497365474780739, 0, 0.39495852791800456, 0.32556251832409067, 0.04145996851406375, 0.5321255615302387, 0.4745736483959867, 0.8077031225529914, 0.7727182252723452, 0.32350776752668176, 0.8516700737916549, 0.8905590837613401, 0.5935164487881724, 0.34323812504670614, 0.3762190625604799, 0, 0.7122030878318626, 0.7201016629223704, 0.5032402348210122, 0.6459771004940253, 0.28291065795682, 0.9807675129793004, 0.904801321395868, 0.7249312328310027, 0.08797612394667331, 0.21652646788592023, 0.2502698372860521, 0.5271991408875268, 0.3904368367161952, 0, 0.36508174527578696, 0.28549760122806267, 0, 0.6530637713991875, 0.07111060472758746, 0.539713440473812, 0.06379108642341613, 0.4609514585175071, 0.17100240601976935, 0.20616576051652735, 0.6187094701051248, 0.6269357691081149, 0, 0.5281274232165335, 0.13783184906931645, 0.6935570838981316, 0.4056335243279451, 0.7133338668936207, 0.7475108839851734, 0.17357481507151973, 0.5188185089754186, 0.9707935671973664, 0, 0.6135368531681027, 0.20463806463336487, 0.7988704077506983, 0.12569583554972774, 0.9029179052812085, 0.20153927911653413, 0.4953392643904281, 0.31390765664891884, 0, 0.3309013777524318, 0.6270666932243821, 0.7796749710315518, 0.9056785444042665, 0, 0.9531026962094459, 0.2580494188736775, 0.2130379222435661, 0, 0.6404773765646499, 0.5151777681422338, 0, 0.849179519208299, 0.40777786434510566, 0.03250867310816463, 0.4806021833149339, 0.6205728646164946, 0.4925314551993333, 0.8647770115517502, 0.39070743887579995, 0.42416135424755597, 0.54984394800331, 0.6781483425918189, 0.6498463642722709, 0.8563189193468166, 0.25555800305584464, 0.05700199355385749, 0.08965338534965728, 0.15529537842766272, 0.9149924358819634, 0.44384858784509185, 0.27209987746166564, 0.18655130321037405, 0.9874438834501506, 0.09507860989480466, 0, 0, 0.2440388914742504, 0.5256635084984219, 0.16051723098089754, 0.001558650858930366, 0.7487131249747785, 0.5326827482320085, 0.06556091356378457, 0.9924489229483846, 0.0798524142793332, 0.9536950395766158, 0.2291072757089373, 0.1928968621390703, 0, 0, 0.4373574990027175, 0.530806467064814, 0.20721530788094122, 0.8006704059039377, 0.20253089835154503, 0.47704054715261346, 0.1986035873715144, 0.4589304057288962, 0.6465807585041393, 0.7547689418434551, 0, 0.31060381223641076, 0.414218513671307, 0.5931807317923922, 0.18856659632218276, 0.6196167400377182, 0.08000475281653141, 0.8470246747851051, 0.22057370127305498, 0, 0.8342942680720551, 0.028757429285548808, 0.8156362526375006, 0.995845201683645, 0.24145512335842445, 0.5061782487865663, 0.8026965174319032, 0.43506325401396284, 0.5571950697614316, 0.25066087051375796, 0.8381081762722642, 0.15586199008618862, 0, 0.4937714349711336, 0.9645730942911799, 0.5295090560649596, 0.06968803878960395, 0.44685154003828975, 0.33742346829989367, 0.13481478825449678, 0.803920949795903, 0.630963718190743, 0.24798703491148433, 0.8231362409935734, 0.7240923734212911, 0.2846436847806062, 0.05982454796958059, 0.35028500517867445, 0.614081918820337, 0.861616830962007, 0.534818561063319, 0.8847078321709532, 0.5166459644245666, 0, 0.4233714925279961, 0.4099433816129605, 0.3596634270557205, 0.8610567120629865, 0.8518711302938005, 0.9815643847289087, 0.9399628151862728, 0.2769446749682547, 0.26183229274445396, 0.8088448102791975, 0.33687053509902065, 0.7400021939452547, 0, 0.3104827029954591, 0, 0.21970236591471226, 0.41627055271728786, 0.760887088424125, 0.504908631439443, 0.3309286197749345, 0.02719415662268243, 0.8562693107931387, 0.9160753449428137, 0.9215797164219479, 0.5517490913245979, 0.3243906541755075, 0.7787841293892179, 0.9041181979807672, 0, 0.4065020828144216, 0.3263035620019985, 0.21462217259429095, 0, 0.2095263867639977, 0.6957321786683563, 0.8711085360114326, 0.4567387764709292, 0, 0.9103800028392032, 0.03993385756609047, 0.8743740215362584, 0.8520540004186101, 0.3023664438123982, 0.5362319606972364, 0.6852340947034127, 0.14673994613405017, 0.9556076167243608, 0.2599468487198915, 0.6166229242431326, 0.3647502822229911, 0.06970322845198929, 0.8059945397482337, 0.6935906648605085, 0.14484907161927674, 0.7489671614625661, 0.0761100118803788, 0.6929575390278396, 0.411156902510315, 0.5680362145035645, 0.22103038278350906, 0.33378918072616803, 0.07401872093186024, 0, 0.2384710487765187, 0.7537046273865109, 0.5650633058530684, 0.537903467024971, 0.6730136981282536, 0.258276624419172, 0.0481812499641181, 0.16161642109590713, 0.2063293661678547, 0.613858263924941, 0.7401198936147888, 0.6336758174448643, 0.8225024837353339, 0.5978551426718874, 0.09542417674528791, 0.6120833656788707, 0.323903842960925, 0.13931854582455183, 0.5381619902061565, 0.02333574379227754, 0.4216826597745471, 0.8356196870670161, 0.31827308971600765, 0.039285749836145634, 0.8880276700762737, 0, 0, 0.6875430459082899, 0.8412151232415347, 0.7242854452933934, 0.2775442897910889, 0.6389462017857761, 0.043221056775573086, 0.8988220132682924, 0, 0.1959330022168887, 0.8396355706302502, 0.9773002405212339, 0.224356307715603, 0.2803573387059688, 0.5813042002350415, 0.4172020116207842, 0.8985232844254049, 0.7947533403582208, 0.45353379953204853, 0, 0.325408223919478, 0.11257225763441736, 0.47060979677015324, 0.16172279756269536, 0.16494802886054338, 0.36345724739477214, 0.9207933258117182, 0.8186675832402952, 0.8312671423338792, 0.40003451340073903, 0, 0.3556519587605015, 0.6229618810063414, 0, 0.9783244908721367, 0.25559285308007373, 0.45778206830891155, 0.5878524852553565, 0.06799989537861562, 0.2001853230549019, 0.6793707994030119, 0.7150481117705543, 0.577526152899073, 0.4298314283931458, 0.5405489283762733, 0.9083723028981335, 0.9813915128936416, 0.6738361039036571, 0.5871912023825119, 0.23013287609315702, 0.26115915961928027, 0.6822262476747706, 0.17423279385023271, 0.2673150258440812, 0.46048878108024516, 0.14195996747161166, 0.7603060512054939, 0.7363273112341094, 0.41845020429252844, 0.7769527436525356, 0.45434056921894905, 0.5818152963666642, 0.5146678202075496, 0.792670749711979, 0.15426060206288117, 0.2827108066915468, 0.36435578615059594, 0.7639770507184225, 0.9375989691330652, 0.640466561585606, 0.612054008185558, 0.8327635405917856, 0.33531157041415005, 0.6919819159373694, 0.2814933376586002, 0.4280167561341125, 0.9204562423681187, 0, 0.4617258506284855, 0.1687304854159677, 0.21150153046961362, 0, 0.9473641022454827, 0.6199947695650183, 0.36494798871863776, 0.7687437225004924, 0, 0.31596464149129233, 0.3650930095165815, 0.5515049984692568, 0.6648730895561041, 0.9040205803644148, 0.6517095504440272, 0.8259996994449177, 0.08542523283290193, 0, 0.8352731019851729, 0.7203015441232917, 0.871104185117645, 0.42131068249022896, 0.4961835062629919, 0.44103602918847074, 0.5341996553767577, 0.8955804760089646, 0.03519950963156382, 0.5684316886879497, 0.19634651747610699, 0.8858180625145002, 0.4067999310913569, 0.6462912284097071, 0.8450273425899184, 0, 0.4627496072921794, 0.5453573248220742, 0.2288024693436702, 0.43728563713888946, 0.5225489186245426, 0.02675474718827686, 0.5205272619305802, 0.6907443700242047, 0.04073932102277289, 0.693716360144892, 0.32687302381139904, 0.6277895671325022, 0.7593345890756615, 0.3578991326379847, 0.31060606721434136, 0, 0.733087376565599, 0.061341467569715036, 0.35967434041329605, 0.48940154101859323, 0.23583593301173722, 0.7868846146030043, 0, 0.9543888801166363, 0.9255814796677508, 0.5962028744083365, 0.8076491126833315, 0.31571008569147785, 0.9238533846756112, 0.15398713026371935, 0.1952859534354633, 0, 0.725442879082974, 0.7588757369740254, 0.5684667524603941, 0.9179849730776254, 0.25101526871760105, 0.18105423091013084, 0.8435309046984312, 0.23222336038018143, 0.18700649703925543, 0, 0.4491639803654781, 0.6919975904280216, 0.3190004906272401, 0.8736572536181758, 0.10251230834295466, 0.7058530231012046, 0.8978182112867975, 0.73813298121533, 0.8745006564783215, 0.7845526679418063, 0.39191121691254804, 0.6055716295965105, 0.8356709917180716, 0.00288366886400071, 0.6559699987601796, 0.23331256294315594, 0.9776079303483803, 0.09119367760202723, 0.19556751021159646, 0.8363706359031983, 0.9142543696590871, 0.8318105487214865, 0.5926716090135717, 0.3725814516905266, 0.11340419090818132, 0.9645171525488953, 0.11347184903978369, 0.4468986892355996, 0.5396782277129197, 0.6585159819330665, 0.007796835932915469, 0, 0.20052883098350116, 0, 0, 0.9474749905442867, 0.6069534186098525, 0.3208035794554124, 0.8042891168285783, 0.43736320913444793, 0, 0.25436181360882426, 0.7356693659526581, 0.3490849314850649, 0.36254338777723993, 0.2517640014121405, 0.4710453196055221, 0.5775721161180677, 0.205311102057802, 0.029118079438258948, 0.33317546098187434, 0.5541188602042993, 0, 0.22312773319680268]\n",
21
+ "[[False False False ... False False False]\n",
22
+ " [ True False True ... True False True]\n",
23
+ " [False False False ... False False False]\n",
24
+ " ...\n",
25
+ " [False False False ... False False False]\n",
26
+ " [ True False True ... True False True]\n",
27
+ " [False False False ... False False False]]\n",
28
+ "1857\n"
29
+ ]
30
+ }
31
+ ],
32
+ "source": [
33
+ "import numpy as np\n",
34
+ "from sklearn.neighbors import kneighbors_graph\n",
35
+ "import networkx as nx\n",
36
+ "n = 1000\n",
37
+ "points = np.random.rand(n, 2)\n",
38
+ "# init solutions = 1 with p=0.1\n",
39
+ "solutions = np.random.choice([0, 1], size=n, p=[0.9, 0.1])\n",
40
+ "gain = [0 if i == 0 else np.random.rand() + 1 for i in solutions]\n",
41
+ "loss = [0 if i == 1 else np.random.rand() for i in solutions]\n",
42
+ "connection_matrix = kneighbors_graph(points, n_neighbors=3, mode=\"connectivity\").toarray()\n",
43
+ "print(connection_matrix)\n",
44
+ "solution_matrix = (solutions)[:, None] ^ (solutions)[None, :]\n",
45
+ "gain_loss_matrix = np.logical_and(np.array(gain)[:, None] > np.array(loss)[None, :], np.array(loss)[None, :])\n",
46
+ "print(gain)\n",
47
+ "print(loss)\n",
48
+ "print(gain_loss_matrix)\n",
49
+ "\n",
50
+ "final_matrix = np.logical_and(connection_matrix, np.logical_or(gain_loss_matrix, connection_matrix))\n",
51
+ "\n",
52
+ "G = nx.from_numpy_matrix(final_matrix)\n",
53
+ "print(len(G.edges()))"
54
+ ]
55
+ },
56
+ {
57
+ "cell_type": "code",
58
+ "execution_count": 50,
59
+ "metadata": {},
60
+ "outputs": [
61
+ {
62
+ "name": "stdout",
63
+ "output_type": "stream",
64
+ "text": [
65
+ "[ True False True True True]\n",
66
+ "[[0 2]\n",
67
+ " [2 1]\n",
68
+ " [1 2]\n",
69
+ " [2 1]\n",
70
+ " [3 1]]\n",
71
+ "[[0. 0.65806633 0.43679866 0.78755153]\n",
72
+ " [0.38478979 0.28579072 0.1175966 0.54196134]\n",
73
+ " [0.65806633 0. 0.31616109 0.3822108 ]\n",
74
+ " [0.43679866 0.31616109 0. 0.63191089]\n",
75
+ " [0.78755153 0.3822108 0.63191089 0. ]]\n",
76
+ "[[0. 0.43679866]\n",
77
+ " [0.1175966 0.28579072]\n",
78
+ " [0. 0.31616109]\n",
79
+ " [0. 0.31616109]\n",
80
+ " [0. 0.3822108 ]]\n",
81
+ "[[0. 0.43679866]\n",
82
+ " [0.1175966 0.28579072]\n",
83
+ " [0. 0.31616109]\n",
84
+ " [0. 0.31616109]\n",
85
+ " [0. 0.3822108 ]]\n"
86
+ ]
87
+ }
88
+ ],
89
+ "source": [
90
+ "# init 10 points \n",
91
+ "n = 5\n",
92
+ "import numpy as np\n",
93
+ "from sklearn.metrics import pairwise_distances\n",
94
+ "\n",
95
+ "points = np.random.rand(n, 2)\n",
96
+ "distance_matrix = pairwise_distances(points)\n",
97
+ "for i in range(n):\n",
98
+ " for j in range(n):\n",
99
+ " distance_matrix[i, j] = np.linalg.norm(points[i] - points[j])\n",
100
+ " \n",
101
+ "solution = np.random.choice([False, True], size=n, p=[0.5, 0.5])\n",
102
+ "print(solution)\n",
103
+ "distance2solution = distance_matrix[:, solution]\n",
104
+ "mmin = np.partition(distance2solution, 2, axis=-1)[:,:2]\n",
105
+ "argpartition = np.argpartition(distance2solution, 2, axis=-1)[:,:2]\n",
106
+ "print(argpartition)\n",
107
+ "mmin_arg = distance_matrix[:, solution][np.arange(n)[:, None], argpartition]\n",
108
+ "# print(distance_matrix)\n",
109
+ "print(distance2solution)\n",
110
+ "print(mmin)\n",
111
+ "# print(argpartition)\n",
112
+ "print(mmin_arg)"
113
+ ]
114
+ },
115
+ {
116
+ "cell_type": "code",
117
+ "execution_count": 8,
118
+ "metadata": {},
119
+ "outputs": [
120
+ {
121
+ "name": "stdout",
122
+ "output_type": "stream",
123
+ "text": [
124
+ "[[0. 0.21715016 0.33132471 0.19052463 0.54986648 0.41810508\n",
125
+ " 0.75511092 0.19542409 0.50229276 0.3868646 ]\n",
126
+ " [0.21715016 0. 0.35967329 0.3556269 0.75465908 0.42573686\n",
127
+ " 0.92832302 0.39362976 0.68397764 0.4859949 ]\n",
128
+ " [0.33132471 0.35967329 0. 0.51429654 0.58080672 0.08766521\n",
129
+ " 1.03866136 0.49096679 0.78676377 0.71801827]\n",
130
+ " [0.19052463 0.3556269 0.51429654 0. 0.55490601 0.60192552\n",
131
+ " 0.57544324 0.07961631 0.32839122 0.21350254]\n",
132
+ " [0.54986648 0.75465908 0.58080672 0.55490601 0. 0.63075728\n",
133
+ " 0.7194792 0.4754162 0.54633061 0.72183098]\n",
134
+ " [0.41810508 0.42573686 0.08766521 0.60192552 0.63075728 0.\n",
135
+ " 1.12283786 0.57809215 0.87181293 0.80495882]\n",
136
+ " [0.75511092 0.92832302 1.03866136 0.57544324 0.7194792 1.12283786\n",
137
+ " 0. 0.56159289 0.25405459 0.48903412]\n",
138
+ " [0.19542409 0.39362976 0.49096679 0.07961631 0.4754162 0.57809215\n",
139
+ " 0.56159289 0. 0.3078841 0.27326372]\n",
140
+ " [0.50229276 0.68397764 0.78676377 0.32839122 0.54633061 0.87181293\n",
141
+ " 0.25405459 0.3078841 0. 0.30239869]\n",
142
+ " [0.3868646 0.4859949 0.71801827 0.21350254 0.72183098 0.80495882\n",
143
+ " 0.48903412 0.27326372 0.30239869 0. ]]\n",
144
+ "[False True True True False True True True False False]\n",
145
+ "[[0. 0.35967329 0.3556269 0.42573686 0.92832302 0.39362976]\n",
146
+ " [0.35967329 0. 0.51429654 0.08766521 1.03866136 0.49096679]\n",
147
+ " [0.3556269 0.51429654 0. 0.60192552 0.57544324 0.07961631]\n",
148
+ " [0.42573686 0.08766521 0.60192552 0. 1.12283786 0.57809215]\n",
149
+ " [0.92832302 1.03866136 0.57544324 1.12283786 0. 0.56159289]\n",
150
+ " [0.39362976 0.49096679 0.07961631 0.57809215 0.56159289 0. ]]\n",
151
+ "[0.3556269 0.08766521 0.07961631 0.08766521 0.56159289 0.07961631]\n",
152
+ "[[0. 0.3556269 0.08766521 0.07961631 0. 0.08766521\n",
153
+ " 0.56159289 0.07961631 0. 0. ]\n",
154
+ " [0. 0.3556269 0.08766521 0.07961631 0. 0.08766521\n",
155
+ " 0.56159289 0.07961631 0. 0. ]\n",
156
+ " [0. 0.3556269 0.08766521 0.07961631 0. 0.08766521\n",
157
+ " 0.56159289 0.07961631 0. 0. ]\n",
158
+ " [0. 0.3556269 0.08766521 0.07961631 0. 0.08766521\n",
159
+ " 0.56159289 0.07961631 0. 0. ]\n",
160
+ " [0. 0.3556269 0.08766521 0.07961631 0. 0.08766521\n",
161
+ " 0.56159289 0.07961631 0. 0. ]\n",
162
+ " [0. 0.3556269 0.08766521 0.07961631 0. 0.08766521\n",
163
+ " 0.56159289 0.07961631 0. 0. ]\n",
164
+ " [0. 0.3556269 0.08766521 0.07961631 0. 0.08766521\n",
165
+ " 0.56159289 0.07961631 0. 0. ]\n",
166
+ " [0. 0.3556269 0.08766521 0.07961631 0. 0.08766521\n",
167
+ " 0.56159289 0.07961631 0. 0. ]\n",
168
+ " [0. 0.3556269 0.08766521 0.07961631 0. 0.08766521\n",
169
+ " 0.56159289 0.07961631 0. 0. ]\n",
170
+ " [0. 0.3556269 0.08766521 0.07961631 0. 0.08766521\n",
171
+ " 0.56159289 0.07961631 0. 0. ]]\n"
172
+ ]
173
+ }
174
+ ],
175
+ "source": [
176
+ "# init 10 points \n",
177
+ "n = 10\n",
178
+ "import numpy as np\n",
179
+ "from sklearn.metrics import pairwise_distances\n",
180
+ "\n",
181
+ "points = np.random.rand(n, 2)\n",
182
+ "distance_matrix = pairwise_distances(points)\n",
183
+ "for i in range(n):\n",
184
+ " for j in range(n):\n",
185
+ " distance_matrix[i, j] = np.linalg.norm(points[i] - points[j])\n",
186
+ " \n",
187
+ "solution = np.random.choice([False, True], size=n, p=[0.5, 0.5])\n",
188
+ "m = distance_matrix[:, solution][solution, :]\n",
189
+ "mmin = np.partition(m, 2, axis=-1)[:,1]\n",
190
+ "restore = np.zeros((n, n))\n",
191
+ "restore[:, solution] = mmin\n",
192
+ "\n",
193
+ "print(distance_matrix)\n",
194
+ "print(solution)\n",
195
+ "print(m)\n",
196
+ "print(mmin)\n",
197
+ "print(restore)\n",
198
+ "\n",
199
+ "# 将mmin按照solution恢复原尺寸,false的位置补0列,true的位置补mmin\n"
200
+ ]
201
+ },
202
+ {
203
+ "cell_type": "code",
204
+ "execution_count": 27,
205
+ "metadata": {},
206
+ "outputs": [
207
+ {
208
+ "name": "stdout",
209
+ "output_type": "stream",
210
+ "text": [
211
+ "[1]\n",
212
+ "[1]\n"
213
+ ]
214
+ }
215
+ ],
216
+ "source": [
217
+ "a = [1]\n",
218
+ "print(a)\n",
219
+ "print(list(a))"
220
+ ]
221
+ },
222
+ {
223
+ "cell_type": "code",
224
+ "execution_count": 4,
225
+ "metadata": {},
226
+ "outputs": [
227
+ {
228
+ "name": "stdout",
229
+ "output_type": "stream",
230
+ "text": [
231
+ "[[False True False True]\n",
232
+ " [ True False True False]\n",
233
+ " [False True False True]\n",
234
+ " [ True False True False]]\n",
235
+ "[(0, 1), (0, 3), (1, 2), (2, 3)]\n"
236
+ ]
237
+ }
238
+ ],
239
+ "source": [
240
+ "import numpy as np\n",
241
+ "import networkx as nx\n",
242
+ "solution1 = [False, True, False, True]\n",
243
+ "solution2 = [True, False, True, False]\n",
244
+ "\n",
245
+ "# solution_matrix[i][j] = 1 if solution1[i] and !solution2[j]\n",
246
+ "solution_matrix = np.logical_and(np.array(solution1)[:, None], np.logical_not(np.array(solution2)[None, :]))\n"
247
+ ]
248
+ },
249
+ {
250
+ "cell_type": "code",
251
+ "execution_count": 9,
252
+ "metadata": {},
253
+ "outputs": [
254
+ {
255
+ "name": "stdout",
256
+ "output_type": "stream",
257
+ "text": [
258
+ "[[ True False True False True True False True True False]\n",
259
+ " [ True True False True False False False False False False]\n",
260
+ " [ True False False True False True False True False False]\n",
261
+ " [ True False False True False True True True True False]\n",
262
+ " [False False True False False False False False True False]\n",
263
+ " [False False False False False True True False False True]\n",
264
+ " [ True False True False False True False True True True]\n",
265
+ " [False True True True True False True False False False]\n",
266
+ " [False True True True False True False False False True]\n",
267
+ " [False True True False False True False True True True]]\n"
268
+ ]
269
+ }
270
+ ],
271
+ "source": [
272
+ "# random nxn bool\n",
273
+ "n = 10\n",
274
+ "solution_matrix = np.random.choice([False, True], size=(n, n), p=[0.5, 0.5])\n",
275
+ "print(solution_matrix)\n",
276
+ "\n",
277
+ "# if solution_matrix[i][j] == 1, then solution_matrix[j][i] = 1\n",
278
+ "solution_matrix = np.logical_or(solution_matrix, solution_matrix.T)"
279
+ ]
280
+ },
281
+ {
282
+ "cell_type": "code",
283
+ "execution_count": 1,
284
+ "metadata": {},
285
+ "outputs": [
286
+ {
287
+ "name": "stdout",
288
+ "output_type": "stream",
289
+ "text": [
290
+ "[2, 2, 3, 1, 2]\n"
291
+ ]
292
+ }
293
+ ],
294
+ "source": [
295
+ "a = [\n",
296
+ " [True, False, True, False, True],\n",
297
+ " [False, True, True, False, False],\n",
298
+ " [True, True, True, False, False],\n",
299
+ " [False, False, False, True, True]\n",
300
+ "]\n",
301
+ "\n",
302
+ "result = [sum(sublist) for sublist in zip(*a)]\n",
303
+ "print(result)\n"
304
+ ]
305
+ },
306
+ {
307
+ "cell_type": "code",
308
+ "execution_count": 72,
309
+ "metadata": {},
310
+ "outputs": [
311
+ {
312
+ "name": "stdout",
313
+ "output_type": "stream",
314
+ "text": [
315
+ "[[ 71.41590974 136.395999 107.69667527 113.67004198 121.34052811]\n",
316
+ " [ 8.24500825 16.66169284 150.51499016 100.86207765 189.98448317]\n",
317
+ " [ 47.46595456 0.83386271 8.11927175 14.5288237 82.16321367]\n",
318
+ " [149.58413362 118.14886729 126.25917039 159.2670266 12.87411618]\n",
319
+ " [ 58.30716684 115.2976154 20.11650907 0.91643344 199.49830585]\n",
320
+ " [189.70973312 29.17579981 93.34984535 144.49503616 108.74375928]\n",
321
+ " [ 66.61164501 167.19244399 139.38867947 52.12149803 23.92542262]\n",
322
+ " [124.99918862 171.27254716 176.59560018 123.54288949 61.2720056 ]\n",
323
+ " [ 62.94516036 112.18738057 157.45099897 43.03534539 192.60239645]\n",
324
+ " [ 69.50587057 60.4803078 159.78661763 69.47100966 147.72643729]]\n"
325
+ ]
326
+ }
327
+ ],
328
+ "source": [
329
+ "# rand 4 to 6\n",
330
+ "import numpy as np\n",
331
+ "n = 10\n",
332
+ "m = 5\n",
333
+ "a = np.random.rand(n, m) * 200\n",
334
+ "print(a)"
335
+ ]
336
+ },
337
+ {
338
+ "cell_type": "code",
339
+ "execution_count": 62,
340
+ "metadata": {},
341
+ "outputs": [
342
+ {
343
+ "name": "stdout",
344
+ "output_type": "stream",
345
+ "text": [
346
+ "[20.24185503]\n"
347
+ ]
348
+ }
349
+ ],
350
+ "source": [
351
+ "# open /data2/suhongyuan/flp/gurobi_result/2013_123.pkl\n",
352
+ "import pickle\n",
353
+ "with open(\"/data2/suhongyuan/flp/gurobi_result/2000_200.pkl\", \"rb\") as f:\n",
354
+ " result = pickle.load(f)\n",
355
+ " print(result)"
356
+ ]
357
+ },
358
+ {
359
+ "cell_type": "code",
360
+ "execution_count": 95,
361
+ "metadata": {},
362
+ "outputs": [
363
+ {
364
+ "data": {
365
+ "text/plain": [
366
+ "[<matplotlib.lines.Line2D at 0x7f249bf083d0>]"
367
+ ]
368
+ },
369
+ "execution_count": 95,
370
+ "metadata": {},
371
+ "output_type": "execute_result"
372
+ },
373
+ {
374
+ "data": {
375
+ "image/png": "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",
376
+ "text/plain": [
377
+ "<Figure size 640x480 with 1 Axes>"
378
+ ]
379
+ },
380
+ "metadata": {},
381
+ "output_type": "display_data"
382
+ }
383
+ ],
384
+ "source": [
385
+ "import pickle\n",
386
+ "\n",
387
+ "data_path1 = '/data2/suhongyuan/flp/output/dg-agent-rl-gnn-seed-1_1/best-models/eval_500_44_1.pkl'\n",
388
+ "data_path2 = '/data2/suhongyuan/flp/output/dg-agent-rl-gnn-seed-1_1/best-models/eval_500_44_19.pkl'\n",
389
+ "data_path3 = '/data2/suhongyuan/flp/output/dg-agent-rl-gnn-seed-1_1/best-models/eval_500_44_21.pkl'\n",
390
+ "data1 = pickle.load(open(data_path1, 'rb'))\n",
391
+ "data2 = pickle.load(open(data_path2, 'rb'))\n",
392
+ "data3 = pickle.load(open(data_path3, 'rb'))\n",
393
+ "# best_data[i] = max(data1[:i+1])\n",
394
+ "# plot data123\n",
395
+ "import matplotlib.pyplot as plt\n",
396
+ "import numpy as np\n",
397
+ "\n",
398
+ "plt.plot(data1, label='1')\n",
399
+ "plt.plot(data2, label='2')\n",
400
+ "plt.plot(data3, label='3')"
401
+ ]
402
+ }
403
+ ],
404
+ "metadata": {
405
+ "kernelspec": {
406
+ "display_name": "torch-1.13-py310",
407
+ "language": "python",
408
+ "name": "python3"
409
+ },
410
+ "language_info": {
411
+ "codemirror_mode": {
412
+ "name": "ipython",
413
+ "version": 3
414
+ },
415
+ "file_extension": ".py",
416
+ "mimetype": "text/x-python",
417
+ "name": "python",
418
+ "nbconvert_exporter": "python",
419
+ "pygments_lexer": "ipython3",
420
+ "version": "3.10.12"
421
+ }
422
+ },
423
+ "nbformat": 4,
424
+ "nbformat_minor": 2
425
+ }