Spaces:
Build error
Build error
Commit ·
b37334e
1
Parent(s): 2444a7c
first commit
Browse files- .gitignore +1 -0
- app.py +146 -0
- flow_game_solver.py +444 -0
- requirements.txt +56 -0
- 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
|