darwinharianto commited on
Commit
b37334e
·
1 Parent(s): 2444a7c

first commit

Browse files
Files changed (5) hide show
  1. .gitignore +1 -0
  2. app.py +146 -0
  3. flow_game_solver.py +444 -0
  4. requirements.txt +56 -0
  5. utils.py +261 -0
.gitignore ADDED
@@ -0,0 +1 @@
 
 
1
+ __pycache__
app.py ADDED
@@ -0,0 +1,146 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ from streamlit_drawable_canvas import st_canvas
3
+ from PIL import Image, ImageDraw
4
+ import matplotlib.colors
5
+ import pandas as pd
6
+ import numpy as np
7
+ from flow_game_solver import (
8
+ validate_board_and_count_colors,
9
+ initiate_model_with_param,
10
+ solve_model,
11
+ )
12
+
13
+ # use canvas for user input, read the input, and connect those dots!!
14
+
15
+
16
+ def position_to_index(x, y, width, height, rows, columns):
17
+ return int(x / (width / columns)), int(y / (height / rows))
18
+
19
+
20
+ def convert_json_to_board(
21
+ data: pd.DataFrame, width: int, height: int, rows: int, columns: int
22
+ ):
23
+
24
+ board = np.zeros((columns, rows))
25
+ circleIdList = {}
26
+ for _, row in data.iterrows():
27
+ x, y = row["left"] + row["radius"], row["top"]
28
+ idx, idy = position_to_index(int(x), int(y), width, height, rows, columns)
29
+
30
+ circleId = row["stroke"]
31
+ if circleId == "#ffffff":
32
+ circleIdList[row["stroke"]] = -1
33
+ elif circleId not in circleIdList.keys():
34
+ circleIdList[row["stroke"]] = len(circleIdList) + 1
35
+
36
+ board[idx, idy] = circleIdList[row["stroke"]]
37
+
38
+ return board.T.astype(int).tolist()
39
+
40
+
41
+ def draw_grid(img: Image, columns: int, rows: int):
42
+
43
+ width = img.width
44
+ height = img.height
45
+
46
+ draw = ImageDraw.Draw(img)
47
+ for col in range(columns):
48
+ x = (width / columns) * (col + 1)
49
+ draw.line((x, 0, x, height), fill=128)
50
+
51
+ for row in range(rows):
52
+
53
+ y = (width / rows) * (row + 1)
54
+ draw.line((0, y, width, y), fill=128)
55
+
56
+ return img
57
+
58
+
59
+ st.title("Connect the dots")
60
+
61
+ WIDTH = 300
62
+ HEIGHT = 300
63
+
64
+ col1, col2, col3 = st.columns(3)
65
+ with col1:
66
+ COLUMNS = st.number_input("Columns", min_value=1, max_value=100, value=3)
67
+ with col2:
68
+ ROWS = st.number_input("Rows", min_value=1, max_value=100, value=3)
69
+ with col3:
70
+ fill = st.slider("Fill", min_value=0.0, max_value=1.0, value=1.0)
71
+ waitLimit = st.number_input(
72
+ "Max wait time in seconds", value=1, min_value=1, max_value=100
73
+ )
74
+
75
+ bg_image = Image.fromarray((np.ones((HEIGHT, WIDTH, 3)) * 0).astype(np.uint8))
76
+ # draw = ImageDraw.Draw(bg_image)
77
+ # draw grid
78
+ bg_image = draw_grid(bg_image, COLUMNS, ROWS)
79
+ # draw.line((0, 0, 100, 100), fill=128)
80
+
81
+ stroke_color = st.color_picker("Point Color: ", "#eab")
82
+ # Create a canvas component
83
+ canvas_result = st_canvas(
84
+ # fill_color="rgba(255, 165, 0, 0.3)", # Fixed fill color with some opacity
85
+ fill_color=stroke_color, # Fixed fill color with some opacity
86
+ stroke_width=5,
87
+ stroke_color=stroke_color,
88
+ background_color="#eee",
89
+ background_image=bg_image,
90
+ update_streamlit=True,
91
+ height=HEIGHT,
92
+ width=WIDTH,
93
+ drawing_mode="point",
94
+ point_display_radius=min(WIDTH / COLUMNS, HEIGHT / ROWS) / 3,
95
+ key="canvas",
96
+ )
97
+ st.info("""
98
+ Click on the board to add pair of color source(s).
99
+ Change color for different source(s).
100
+ Use White color for unpassable path.
101
+ """)
102
+
103
+
104
+ clicked = st.button("Show result")
105
+
106
+
107
+ if clicked:
108
+ boardData = pd.json_normalize(canvas_result.json_data["objects"])
109
+
110
+ colors = [
111
+ matplotlib.colors.to_rgb(row["stroke"]) for _, row in boardData.iterrows()
112
+ ]
113
+ colors = list(set(colors))
114
+ colors.insert(0, matplotlib.colors.to_rgb("#222"))
115
+
116
+ board = convert_json_to_board(
117
+ boardData,
118
+ WIDTH,
119
+ HEIGHT,
120
+ ROWS,
121
+ COLUMNS,
122
+ )
123
+
124
+ min_length = 0
125
+ max_length = 1000
126
+
127
+ num_colors, num_wall = validate_board_and_count_colors(board)
128
+
129
+ # initiate model
130
+ model, solution, edge = initiate_model_with_param(
131
+ board,
132
+ num_colors,
133
+ num_wall,
134
+ fill=fill,
135
+ area_matrix=None,
136
+ min_length=min_length,
137
+ max_length=max_length,
138
+ )
139
+
140
+ # solve
141
+ ax = solve_model(model, board, solution, edge, waitLimit, None, colors=colors)
142
+ if ax is not None:
143
+ st.pyplot(ax.get_figure())
144
+ else:
145
+ st.error("Solution does not exist")
146
+ # connect dots using or tools
flow_game_solver.py ADDED
@@ -0,0 +1,444 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from ortools.sat.python import cp_model
2
+ import numpy as np
3
+ import matplotlib.pyplot as plt
4
+ import collections
5
+ import time
6
+ import traceback
7
+ import math
8
+ import sys
9
+ import utils as utils
10
+ import logging
11
+
12
+
13
+ class VarArraySolutionPrinter(cp_model.CpSolverSolutionCallback):
14
+ """Print intermediate solutions."""
15
+
16
+ def __init__(self, board, variables, edge, test, limit, image=None):
17
+ cp_model.CpSolverSolutionCallback.__init__(self)
18
+ self.__variables = variables
19
+ self.__edge = edge
20
+ self.__solution_count = 0
21
+ self.__board = board
22
+ self.__test = test
23
+ self.__before = time.time()
24
+ self.__bg_image = image
25
+ self.__solution_limit = limit
26
+
27
+ def OnSolutionCallback(self):
28
+
29
+ try:
30
+ self.__solution_count += 1
31
+ logging.info("search path time: ", time.time() - self.__before)
32
+ solved = []
33
+ direction = []
34
+ for v in self.__variables:
35
+ # print("".join(str(self.Value(x)) for x in v))
36
+ solved.append([self.Value(x) for x in v])
37
+
38
+ for v in self.__edge:
39
+ color_x_y_x_y = v.split(" ")[1:]
40
+ color, y_prev, x_prev, y_cur, x_cur = color_x_y_x_y
41
+ if (x_prev != x_cur or y_prev != y_cur) and self.Value(self.__edge[v]):
42
+ direction.append([color, x_prev, y_prev, x_cur, y_cur])
43
+ self.__before = time.time()
44
+
45
+ _ = plot_directed_solution_with_dir(
46
+ self.__board, np.array(solved), direction
47
+ )
48
+ logging.info("test value:", self.Value(self.__test))
49
+ logging.info("Draw time: ", time.time() - self.__before)
50
+ if self.__bg_image:
51
+ ax = plt.gca()
52
+ img = plt.imread(self.__bg_image)
53
+ xlim = ax.get_xlim()
54
+ ylim = ax.get_ylim()
55
+ ax.imshow(img, extent=[xlim[0], xlim[1], ylim[0], ylim[1]])
56
+
57
+ ax.figure.savefig(
58
+ f"/Users/darwinharianto/Documents/Git/git_training_lab/Notebook/pipe_proj/img_{self.__solution_count}.png"
59
+ )
60
+ # plt.show()
61
+ self.__before = time.time()
62
+ logging.info("")
63
+
64
+ except Exception as e:
65
+ logging.info(e)
66
+ traceback.print_exc()
67
+ raise e
68
+
69
+ if self.__solution_count >= self.__solution_limit:
70
+ logging.info("Stop search after %i solutions" % self.__solution_limit)
71
+ self.StopSearch()
72
+
73
+ def solution_count(self):
74
+ return self.__solution_count
75
+
76
+
77
+ def fill_board_from_dict(S, data):
78
+ ax = plt.gca()
79
+ colors = plt.cm.tab20.colors
80
+
81
+ s_mat = np.array(S)
82
+ M, N = s_mat.shape[0], s_mat.shape[1]
83
+
84
+ s_tuple = ax.figure.get_size_inches() * ax.figure.dpi
85
+ s = (s_tuple[0] * s_tuple[1]) / (M * N)
86
+
87
+ for key in data:
88
+ color_item = int(key.split(" ")[1])
89
+
90
+ i = int(key.split(" ")[2])
91
+ j = int(key.split(" ")[3])
92
+ if data[key]:
93
+ ax.scatter(
94
+ j,
95
+ i,
96
+ s=s / 4,
97
+ color=colors[color_item] if color_item != 0 else "black",
98
+ marker="x",
99
+ )
100
+
101
+
102
+ def plot_directed_solution_with_dir(board, S, direction, colors=plt.cm.tab20.colors):
103
+
104
+ plt.cla()
105
+ plt.clf()
106
+ ax = plt.gca()
107
+ M, N = np.array(S).shape[0], np.array(S).shape[1]
108
+
109
+ ax.set_ylim(M - 0.5, -0.5)
110
+ ax.set_xlim(-0.5, N - 0.5)
111
+ ax.set_aspect("equal")
112
+ ax.set_facecolor("black")
113
+ ax.set_yticks([i + 0.5 for i in range(M - 1)], minor=True)
114
+ ax.set_xticks([j + 0.5 for j in range(N - 1)], minor=True)
115
+ ax.grid(visible=True, which="minor", color="white")
116
+ ax.set_xticks([])
117
+ ax.set_yticks([])
118
+ ax.tick_params(axis="both", which="both", length=0)
119
+
120
+ linewidth = utils.linewidth_from_data_units(0.45, ax)
121
+ for item in direction:
122
+ prev_x, prev_y = int(item[1]), int(item[2])
123
+ cur_x, cur_y = int(item[3]), int(item[4])
124
+ ax.plot(
125
+ [prev_x, cur_x], [prev_y, cur_y], color=colors[int(item[0])], lw=linewidth
126
+ )
127
+ print(S)
128
+ utils.add_walls_and_source_to_ax(ax, colors, board, linewidth**2)
129
+
130
+ return ax.figure
131
+
132
+
133
+ def validate_board_and_count_colors(board):
134
+ assert isinstance(board, list)
135
+ assert all(isinstance(row, list) for row in board)
136
+ assert len(set(map(len, board))) == 1
137
+ colors = collections.Counter(square for row in board for square in row)
138
+ del colors[0]
139
+ neg_counter = 0
140
+ for color in colors:
141
+ if color < 0:
142
+ neg_counter += 1
143
+ for i in range(neg_counter + 1):
144
+ del colors[-i]
145
+ assert all(count == 2 for count in colors.values())
146
+ num_colors = len(colors)
147
+ assert set(colors.keys()) == set(range(1, num_colors + 1))
148
+
149
+ num_walls = len([x for sublist in board for x in sublist if x < 0])
150
+ return num_colors, num_walls
151
+
152
+
153
+ def initiate_model_with_param(
154
+ board,
155
+ num_colors,
156
+ num_wall=0,
157
+ fill=1,
158
+ area_matrix=None,
159
+ min_length=0,
160
+ max_length=sys.maxsize,
161
+ ):
162
+
163
+ # create model
164
+ model = cp_model.CpModel()
165
+
166
+ # create variable for model
167
+ solution = [
168
+ [square or model.NewIntVar(0, num_colors, "") for (j, square) in enumerate(row)]
169
+ for (i, row) in enumerate(board)
170
+ ]
171
+ edge = {}
172
+ all_turn = {}
173
+ true = model.NewBoolVar("")
174
+ model.AddBoolOr(
175
+ [true]
176
+ ) # without this, there will be line connecting from endpoint to endpoint after few solutions
177
+
178
+ # make filled target variables
179
+ board_size = len(board) * len(board[0])
180
+ cus_board_size = board_size * num_colors
181
+ at_least_filled = math.floor((board_size - num_wall) * fill)
182
+
183
+ # make filled and turn constraint
184
+ not_filled = 0
185
+ turns = 0
186
+ path_length = np.zeros(num_colors).tolist()
187
+
188
+ # test variable
189
+ test = model.NewIntVar(-1000, 10000, "")
190
+
191
+ for color in range(1, num_colors + 1):
192
+ endpoints = []
193
+ arcs = []
194
+ for i, row in enumerate(board):
195
+ for j, square in enumerate(row):
196
+ if square == color:
197
+ endpoints.append((i, j))
198
+ else:
199
+ arcs.append(((i, j), (i, j)))
200
+ if i < len(board) - 1:
201
+ if area_matrix is None:
202
+ if board[i + 1][j] != -1:
203
+ arcs.append(((i, j), (i + 1, j)))
204
+ else:
205
+ if area_matrix[i + 1][j] == color:
206
+ arcs.append(((i, j), (i + 1, j)))
207
+ elif area_matrix[i + 1][j] == 0:
208
+ arcs.append(((i, j), (i + 1, j)))
209
+ if j < len(row) - 1:
210
+ if area_matrix is None:
211
+ if board[i][j + 1] != -1:
212
+ arcs.append(((i, j), (i, j + 1)))
213
+ else:
214
+ if area_matrix[i][j + 1] == color or area_matrix[i][j + 1] == 0:
215
+ arcs.append(((i, j), (i, j + 1)))
216
+ # elif area_matrix[i][j+1] == 0:
217
+ # arcs.append(((i, j), (i, j + 1)))
218
+ (i1, j1), (i2, j2) = endpoints
219
+ k1 = i1 * len(row) + j1
220
+ k2 = i2 * len(row) + j2
221
+ arc_variables = [
222
+ (k2, k1, true)
223
+ ] # without this, there will be line connecting from endpoint to endpoint
224
+
225
+ # make length constraint
226
+ path_length[color - 1] = 0
227
+
228
+ for (i1, j1), (i2, j2) in arcs:
229
+ k1 = i1 * len(row) + j1
230
+ k2 = i2 * len(row) + j2
231
+ edge_name = f"color_i1_j1_i2_j2 {color} {i1} {j1} {i2} {j2}"
232
+ edge[edge_name] = model.NewIntVar(0, 1, edge_name)
233
+ if k1 == k2:
234
+ model.Add(solution[i1][j1] != color).OnlyEnforceIf(edge[edge_name])
235
+ arc_variables.append((k1, k1, edge[edge_name]))
236
+ not_filled += edge[edge_name]
237
+ else:
238
+ model.Add(solution[i1][j1] == color).OnlyEnforceIf(edge[edge_name])
239
+ model.Add(solution[i2][j2] == color).OnlyEnforceIf(edge[edge_name])
240
+ forward = model.NewIntVar(0, 1, "")
241
+ backward = model.NewIntVar(0, 1, "")
242
+ # this clauses will force edge to be the same as forward and backward
243
+
244
+ # Default
245
+ model.AddBoolOr([edge[edge_name], forward.Not()])
246
+ model.AddBoolOr([edge[edge_name], backward.Not()])
247
+ model.AddBoolOr([edge[edge_name].Not(), forward, backward])
248
+ model.AddBoolOr([forward.Not(), backward.Not()])
249
+
250
+ arc_variables.append((k1, k2, forward))
251
+ arc_variables.append((k2, k1, backward))
252
+ path_length[color - 1] += forward + backward
253
+
254
+ for i, row in enumerate(board):
255
+ for j, square in enumerate(row):
256
+ bottom = f"color_i1_j1_i2_j2 {color} {i} {j} {i+1} {j}"
257
+ top = f"color_i1_j1_i2_j2 {color} {i-1} {j} {i} {j}"
258
+ right = f"color_i1_j1_i2_j2 {color} {i} {j} {i} {j+1}"
259
+ left = f"color_i1_j1_i2_j2 {color} {i} {j-1} {i} {j}"
260
+
261
+ turn_bool = model.NewIntVar(0, 1, "color_i1_j1_i2_j2")
262
+ all_turn[f"color_i1_j1_i2_j2 {color} {i} {j} {i} {j}"] = turn_bool
263
+
264
+ left_value = edge[left] if left in edge else true.Not()
265
+ right_value = edge[right] if right in edge else true.Not()
266
+ top_value = edge[top] if top in edge else true.Not()
267
+ bottom_value = edge[bottom] if bottom in edge else true.Not()
268
+
269
+ """
270
+ Minimal Form (with ~) = ~ab~cde + ~abc~de + a~b~cde + a~bc~de + ~a~b~e + ~c~d~e + cd~e + ab~e
271
+ """
272
+ # minimal form
273
+ model.AddBoolOr(
274
+ [
275
+ left_value,
276
+ right_value.Not(),
277
+ bottom_value,
278
+ top_value.Not(),
279
+ turn_bool,
280
+ ]
281
+ ) # A+ ~B + C+ ~D+ ~E
282
+ model.AddBoolOr(
283
+ [
284
+ left_value,
285
+ right_value.Not(),
286
+ bottom_value.Not(),
287
+ top_value,
288
+ turn_bool,
289
+ ]
290
+ ) # A+ ~B + ~C+ D+ ~E
291
+ model.AddBoolOr(
292
+ [
293
+ left_value.Not(),
294
+ right_value,
295
+ bottom_value,
296
+ top_value.Not(),
297
+ turn_bool,
298
+ ]
299
+ ) # ~A+ B + C+ ~D+ ~E
300
+ model.AddBoolOr(
301
+ [
302
+ left_value.Not(),
303
+ right_value,
304
+ bottom_value.Not(),
305
+ top_value,
306
+ turn_bool,
307
+ ]
308
+ ) # ~A+ B + ~C+ D+ ~E
309
+ model.AddBoolOr([left_value, right_value, turn_bool.Not()]) # A+ B + E
310
+ model.AddBoolOr([bottom_value, top_value, turn_bool.Not()]) # C+ D+ E
311
+ model.AddBoolOr(
312
+ [bottom_value.Not(), top_value.Not(), turn_bool.Not()]
313
+ ) # ~C+ ~D+ E
314
+ model.AddBoolOr(
315
+ [left_value.Not(), right_value.Not(), turn_bool.Not()]
316
+ ) # ~A+ ~B+ E
317
+
318
+ turns += turn_bool
319
+
320
+ model.Add(path_length[color - 1] < max_length)
321
+ model.Add(path_length[color - 1] >= min_length)
322
+ model.AddCircuit(arc_variables)
323
+
324
+ # model.Minimize(not_filled)
325
+ model.Minimize(turns + not_filled)
326
+ model.Add((cus_board_size - not_filled) >= at_least_filled)
327
+ model.Add(test == cus_board_size - not_filled)
328
+
329
+ return model, solution, edge
330
+
331
+
332
+ def solve_model(
333
+ model, board, solution, edge, limit=None, image=None, colors=plt.cm.tab20.colors
334
+ ) -> plt.Axes:
335
+
336
+ # solve model
337
+ solver = cp_model.CpSolver()
338
+ solver.parameters.max_time_in_seconds = limit
339
+
340
+ asd = time.time()
341
+ status = solver.Solve(model)
342
+
343
+ logging.info(f"Search sol time: {time.time()-asd}")
344
+ # Output solution.
345
+ if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE:
346
+ solved = []
347
+ direction = []
348
+ for v in solution:
349
+ solved.append([solver.Value(x) for x in v])
350
+ for v in edge:
351
+ color_x_y_x_y = v.split(" ")[1:]
352
+ color, y_prev, x_prev, y_cur, x_cur = color_x_y_x_y
353
+ if (x_prev != x_cur or y_prev != y_cur) and solver.Value(edge[v]):
354
+ direction.append([color, x_prev, y_prev, x_cur, y_cur])
355
+
356
+ _ = plot_directed_solution_with_dir(board, np.array(solved), direction, colors)
357
+ ax = plt.gca()
358
+ if image:
359
+ img = plt.imread(image)
360
+ xlim = ax.get_xlim()
361
+ ylim = ax.get_ylim()
362
+ ax.imshow(img, extent=[xlim[0], xlim[1], ylim[0], ylim[1]])
363
+ ax = plt.gca()
364
+ return ax
365
+
366
+ else:
367
+ logging.info("No solution found")
368
+ return None
369
+
370
+ # #### print multiple solution
371
+ # solution_printer = VarArraySolutionPrinter(board,solution, edge, test, limit, image=image)
372
+ # print(f"finished preparing model {time.time()-dsa}, starting search")
373
+ # status = solver.SearchForAllSolutions(model, solution_printer)
374
+ # print()
375
+ # print('Solutions found : %i' % solution_printer.solution_count())
376
+
377
+
378
+ def main(
379
+ board,
380
+ fill=1,
381
+ min_length=0,
382
+ max_length=sys.maxsize,
383
+ limit=1,
384
+ image=None,
385
+ area_matrix=None,
386
+ save_path=None,
387
+ ):
388
+ # validate board get number of wall and colors
389
+ num_colors, num_wall = validate_board_and_count_colors(board)
390
+
391
+ # initiate model
392
+ model, solution, edge = initiate_model_with_param(
393
+ board,
394
+ num_colors,
395
+ num_wall,
396
+ fill=fill,
397
+ area_matrix=area_matrix,
398
+ min_length=min_length,
399
+ max_length=max_length,
400
+ )
401
+
402
+ # solve
403
+ ax = solve_model(model, board, solution, edge, limit, image, colors=plt.cm.tab20.colors)
404
+
405
+ # plt.show()
406
+ ax.figure.savefig(save_path) if save_path else None
407
+
408
+
409
+ if __name__ == "__main__":
410
+
411
+ board_size = (10, 10)
412
+ num_of_color = 4
413
+
414
+ board = np.zeros(board_size)
415
+ for i in range(num_of_color):
416
+ center_index = (int(board_size[0] / 2) - 1, int(board_size[1] / 2) - 1)
417
+
418
+ for value in range(len(board[center_index[0]]) - 1):
419
+ board[center_index[0]][value] = board[center_index[0]][value + 1]
420
+
421
+ for j in range(num_of_color - 1):
422
+ board[center_index[0]][center_index[1] + i] = i + 1
423
+ board[center_index[0]][center_index[1] + 1 + i] = i + 1
424
+
425
+ board = np.array(
426
+ [
427
+ [-1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
428
+ [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
429
+ # [0,0,0,0,0,0,0,0,0,0,0,0],
430
+ # [0,0,0,0,0,0,0,0,0,0,0,0],
431
+ # [0,0,0,0,0,0,0,0,0,0,0,0],
432
+ # [0,0,0,0,0,0,0,0,0,0,0,0],
433
+ # [0,0,0,0,0,0,0,0,0,0,0,1],
434
+ ]
435
+ )
436
+
437
+ main(
438
+ board.astype(int).tolist(),
439
+ fill=0.8,
440
+ min_length=0,
441
+ max_length=1000,
442
+ limit=1,
443
+ save_path="./res.png",
444
+ )
requirements.txt ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ absl-py==1.3.0
2
+ altair==4.2.0
3
+ attrs==22.1.0
4
+ backports.zoneinfo==0.2.1
5
+ blinker==1.5
6
+ cachetools==5.2.0
7
+ charset-normalizer==2.1.1
8
+ click==8.1.3
9
+ commonmark==0.9.1
10
+ contourpy==1.0.6
11
+ cycler==0.11.0
12
+ decorator==5.1.1
13
+ entrypoints==0.4
14
+ fonttools==4.38.0
15
+ gitdb==4.0.10
16
+ GitPython==3.1.29
17
+ idna==3.4
18
+ importlib-metadata==5.1.0
19
+ importlib-resources==5.10.1
20
+ Jinja2==3.1.2
21
+ jsonschema==4.17.3
22
+ kiwisolver==1.4.4
23
+ MarkupSafe==2.1.1
24
+ matplotlib==3.6.2
25
+ numpy==1.23.5
26
+ ortools==9.5.2237
27
+ packaging==22.0
28
+ pandas==1.5.2
29
+ Pillow==9.3.0
30
+ pkgutil_resolve_name==1.3.10
31
+ protobuf==3.20.3
32
+ pyarrow==10.0.1
33
+ pydeck==0.8.0
34
+ Pygments==2.13.0
35
+ Pympler==1.0.1
36
+ pyparsing==3.0.9
37
+ pyrsistent==0.19.2
38
+ python-dateutil==2.8.2
39
+ pytz==2022.6
40
+ pytz-deprecation-shim==0.1.0.post0
41
+ requests==2.28.1
42
+ rich==12.6.0
43
+ semver==2.13.0
44
+ six==1.16.0
45
+ smmap==5.0.0
46
+ streamlit==1.15.2
47
+ streamlit-drawable-canvas==0.9.2
48
+ toml==0.10.2
49
+ toolz==0.12.0
50
+ tornado==6.2
51
+ typing_extensions==4.4.0
52
+ tzdata==2022.7
53
+ tzlocal==4.2
54
+ urllib3==1.26.13
55
+ validators==0.20.0
56
+ zipp==3.11.0
utils.py ADDED
@@ -0,0 +1,261 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ import time
3
+ import logging
4
+
5
+
6
+ def print_time(before):
7
+ logging.info(time.time() - before)
8
+ return time.time()
9
+
10
+
11
+ def linewidth_from_data_units(linewidth, axis, reference="y"):
12
+ """
13
+ Convert a linewidth in data units to linewidth in points.
14
+
15
+ Parameters
16
+ ----------
17
+ linewidth: float
18
+ Linewidth in data units of the respective reference-axis
19
+ axis: matplotlib axis
20
+ The axis which is used to extract the relevant transformation
21
+ data (data limits and size must not change afterwards)
22
+ reference: string
23
+ The axis that is taken as a reference for the data width.
24
+ Possible values: 'x' and 'y'. Defaults to 'y'.
25
+
26
+ Returns
27
+ -------
28
+ linewidth: float
29
+ Linewidth in points
30
+ """
31
+ fig = axis.get_figure()
32
+ if reference == "x":
33
+ length = fig.bbox_inches.width * axis.get_position().width
34
+ value_range = np.diff(axis.get_xlim())[0]
35
+ elif reference == "y":
36
+ length = fig.bbox_inches.height * axis.get_position().height
37
+ value_range = np.diff(axis.get_ylim())[0]
38
+ # Convert length to points
39
+ length *= 72
40
+ # Scale linewidth to value range
41
+ return linewidth * (length / value_range)
42
+
43
+
44
+ def add_walls_and_source_to_ax(ax, colors, S, scatter_size):
45
+
46
+ s_mat = np.array(S)
47
+ M, N = s_mat.shape[0], s_mat.shape[1]
48
+
49
+ scatter_dict = {"color_item_-1_x_y": []}
50
+ for iter in range(np.sum(np.unique(s_mat) > 0)):
51
+ scatter_dict[f"color_item_{iter+1}_x_y"] = []
52
+
53
+ for i in range(M):
54
+ for j in range(N):
55
+ if S[i][j] != 0:
56
+ scatter_dict[f"color_item_{S[i][j]}_x_y"].append([j, i])
57
+
58
+ for key in scatter_dict:
59
+ color_item = int(key.split("_")[2])
60
+ mat = np.array(scatter_dict[key]).T
61
+ if len(mat) == 0:
62
+ continue
63
+ j = mat[0].tolist()
64
+ i = mat[1].tolist()
65
+ ax.scatter(
66
+ j,
67
+ i,
68
+ s=scatter_size * 2,
69
+ color=colors[color_item] if color_item != 0 else "black",
70
+ marker="x" if color_item < 0 else None,
71
+ )
72
+
73
+
74
+ def plot_area(S):
75
+ import matplotlib
76
+ import matplotlib.pyplot as plt
77
+ from matplotlib import colors
78
+
79
+ ax = plt.gca()
80
+ color = [matplotlib.colors.to_hex(c) for c in plt.cm.tab20.colors]
81
+ s_mat = np.array(S)
82
+ M, N = s_mat.shape[0], s_mat.shape[1]
83
+
84
+ # create discrete colormap
85
+ # print(color[:s_mat.max()] + color[-1])
86
+ cmap = colors.ListedColormap([color[-1]] + color[: s_mat.max() + 1])
87
+ # cmap = colors.ListedColormap(['red', 'blue', 'green', 'yellow', 'black'])
88
+ bounds = [i for i in np.arange(-1, s_mat.max() + 2, 1)]
89
+ norm = colors.BoundaryNorm(bounds, cmap.N)
90
+
91
+ ax.imshow(s_mat + 0.5, cmap=cmap, norm=norm, alpha=0.5) # half is for the threshold
92
+
93
+ # draw gridlines
94
+ ax.set_facecolor("black")
95
+ ax.set_ylim(M - 0.5, -0.5)
96
+ ax.set_xlim(-0.5, N - 0.5)
97
+ return ax.figure
98
+
99
+
100
+ def plot_walls(S):
101
+ import matplotlib.pyplot as plt
102
+
103
+ plt.clf()
104
+ plt.cla()
105
+ ax = plt.gca()
106
+ colors = plt.cm.tab20.colors
107
+ s_mat = np.array(S)
108
+ M, N = s_mat.shape[0], s_mat.shape[1]
109
+
110
+ linewidth = linewidth_from_data_units(0.45, ax)
111
+
112
+ ax.set_ylim(M - 0.5, -0.5)
113
+ ax.set_xlim(-0.5, N - 0.5)
114
+ ax.set_aspect("equal")
115
+ ax.set_facecolor("black")
116
+ ax.set_yticks([i + 0.5 for i in range(M - 1)], minor=True)
117
+ ax.set_xticks([j + 0.5 for j in range(N - 1)], minor=True)
118
+ ax.grid(b=True, which="minor", color="white")
119
+ ax.set_xticks([])
120
+ ax.set_yticks([])
121
+ ax.tick_params(axis="both", which="both", length=0)
122
+
123
+ add_walls_and_source_to_ax(ax, colors, S, linewidth)
124
+ logging.info("finish plot_walls for right side")
125
+
126
+ return ax.figure
127
+
128
+
129
+ def put_num_on_matrix(m, walls, in_num):
130
+
131
+ matrix = np.copy(m)
132
+
133
+ for coords in walls:
134
+ x_min, x_max, y_min, y_max = coords[0], coords[2], coords[1], coords[3]
135
+ for x in range(
136
+ x_min,
137
+ x_max + 1 if (x_min < x_max) else x_max - 1,
138
+ 1 if (x_min < x_max) else -1,
139
+ ):
140
+ x = int(x)
141
+ for y in range(
142
+ y_min,
143
+ y_max + 1 if (y_min < y_max) else y_max - 1,
144
+ 1 if (y_min < y_max) else -1,
145
+ ):
146
+ y = int(y)
147
+ if y < len(matrix) and x < len(matrix[0]) and matrix is not None:
148
+ matrix[y][x] = in_num
149
+
150
+ return matrix
151
+
152
+
153
+ def put_rect_source_on_matrix(matrix, coords):
154
+
155
+ number_of_source = 0
156
+
157
+ x_min, x_max, y_min, y_max = coords[0], coords[2], coords[1], coords[3]
158
+
159
+ for x in range(
160
+ x_min, x_max + 1 if (x_min < x_max) else x_max - 1, 1 if (x_min < x_max) else -1
161
+ ):
162
+ x = int(x)
163
+ for y in range(
164
+ y_min,
165
+ y_max + 1 if (y_min < y_max) else y_max - 1,
166
+ 1 if (y_min < y_max) else -1,
167
+ ):
168
+ y = int(y)
169
+ matrix[y][x] = int(number_of_source / 2) + 1
170
+ number_of_source += 1
171
+ assert number_of_source % 2 == 0 and "Number of source outlet must be mutiply of 2!"
172
+ return matrix
173
+
174
+
175
+ def put_point_source_on_matrix(matrix, coords):
176
+
177
+ number_of_source = len(matrix)
178
+ assert number_of_source % 2 == 0 and "Number of source outlet must be mutiply of 2!"
179
+
180
+ for x, y, col in coords:
181
+ matrix[int(y)][int(x)] = col
182
+
183
+ return matrix
184
+
185
+
186
+ def create_matrix_and_area_matrix(color_dict, canvas_size, pipe_size, source, arr):
187
+
188
+ matrix = np.zeros(
189
+ (np.array(canvas_size) / np.array(pipe_size)).astype("int").tolist()
190
+ ).astype("int")
191
+ area_matrix = None
192
+
193
+ color_set = list(set([item.split("_")[-1] for item in color_dict.keys()]))
194
+ for index in color_set:
195
+ for start_point, end_point in zip(
196
+ color_dict[f"start_{index}"], color_dict[f"end_{index}"]
197
+ ):
198
+ # graph.draw_rectangle(start_point, end_point, fill_color=colors[int(index)], line_color=colors[int(index)])
199
+
200
+ start = np.array(start_point)
201
+ end = np.array(end_point)
202
+
203
+ start[1] = canvas_size[1] - start[1]
204
+ end[1] = canvas_size[1] - end[1]
205
+ start[0], start[1], end[0], end[1] = (
206
+ start[0] / pipe_size[0],
207
+ start[1] / pipe_size[1],
208
+ end[0] / pipe_size[0],
209
+ end[1] / pipe_size[1],
210
+ )
211
+ arr = (
212
+ np.append(arr, [np.append(start, end)], axis=0)
213
+ if arr is not None
214
+ else np.append(start, end).reshape(-1, 4)
215
+ )
216
+
217
+ # this means wall
218
+ if int(index) == -1:
219
+ matrix = put_num_on_matrix(matrix, arr, in_num=-1)
220
+ area_matrix = (
221
+ put_num_on_matrix(area_matrix, arr, in_num=int(index))
222
+ if area_matrix is not None
223
+ else put_num_on_matrix(matrix, arr, in_num=int(index))
224
+ )
225
+ arr = None
226
+
227
+ if source:
228
+ if len(source[0]) == 2:
229
+ start_x = source[0][0] / pipe_size[0]
230
+ end_x = source[1][0] / pipe_size[0]
231
+ start_y = (canvas_size[1] - source[0][1]) / pipe_size[1]
232
+ end_y = (canvas_size[1] - source[1][1]) / pipe_size[1]
233
+ source_sized = np.array([start_x, start_y, end_x, end_y]).astype("int")
234
+ matrix = put_rect_source_on_matrix(matrix, source_sized)
235
+ elif len(source[0]) == 3:
236
+ source_sized = []
237
+ for i in range(len(source)):
238
+ x = source[i][0] / pipe_size[0]
239
+ y = (canvas_size[1] - source[i][1]) / pipe_size[1]
240
+ col = source[i][2]
241
+ source_sized.append([x, y, col])
242
+
243
+ matrix = put_point_source_on_matrix(matrix, source_sized)
244
+
245
+ return matrix, area_matrix
246
+
247
+
248
+ if __name__ == "__main__":
249
+ matrix = [
250
+ [-1, 0, 1, 1],
251
+ [-1, 0, 2, 2],
252
+ [-1, 0, 0, 0],
253
+ ]
254
+ areamatrix = [
255
+ [-1, 0, 1, 1],
256
+ [-1, 0, 1, 1],
257
+ [-1, 0, 2, 2],
258
+ ]
259
+ plot_walls(matrix)
260
+ plot_area(areamatrix)
261
+ pass