-
Notifications
You must be signed in to change notification settings - Fork 0
/
solver.py
227 lines (185 loc) · 5.78 KB
/
solver.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
"""solver.py
Jinzhe Zhang
A99000241
Provides Solver class as a wrapper/container/handle for the entire problem.
"""
import random
import matplotlib
import numpy as np
import matplotlib.pyplot as plt
from tower import Tower
class Solver:
"""docstring for Solver
Attributes:
coverage (np.ndarray): The current coverage. 0 means uncovered, other
values means covered by the tower of that rank
height (int): The height of the problem size
width (int): The width of the problem size
num_tower (int): Number of online towers
tower_list (list): A list of online towers
"""
def __init__(self, height, width):
"""Initialize a solver
Args:
height (int): Height of the rectangle
width (int): Width of the rectangle
"""
self.height = int(height)
self.width = int(width)
self.clear()
def clear(self):
self.coverage = np.zeros((self.height, self.width), dtype=np.uint)
self.num_tower = 0
self.tower_list = []
def create_tower(self, x1, x2, y1, y2):
"""Create a tower instance for the solver
Args:
x1 (int): x1 coordinate
x2 (int): x2 coordinate
y1 (int): y1 coordinate
y2 (int): y2 coordinate
Returns:
Tower: A Tower instance
"""
# Argument checks are done by Tower.__init__
return Tower(self, x1, x2, y1, y2)
def dump_towers(self):
"""Render a string the rank and coordiantes of the towers"""
return "\n".join(tower.dump() for tower in self.tower_list)
def generate_random_tower(self):
"""Generate a random tower. Sizes are uniformly distributed, coordinates
are uniformly distributed.
Returns:
Tower: The generated tower
"""
# Get a random width
width = random.randint(1, self.width)
# Get a random x1
x1 = random.randint(0, self.width - width)
# Get x2
x2 = x1 + width
# Get a random height
height = random.randint(1, self.height)
# Get a random y1
y1 = random.randint(0, self.height - height)
# Get y2
y2 = y1 + height
return self.create_tower(x1, x2, y1, y2)
def generate_random_valid_tower_untrimmed(self):
"""Generate a random tower whose area is not totally covered yet.
Returns:
Tower: The generated tower
Raises:
RuntimeError: The entire space has been covered
"""
if np.all(self.coverage):
raise RuntimeError ("The entire space has been covered")
tower = self.generate_random_tower()
while np.all(self.coverage[tower.mask]):
tower = self.generate_random_tower()
return tower
def generate_random_valid_tower_trimmed(self):
"""Generate a random tower whose area is not totally covered yet, and
then trim its coverage.
Returns:
Tower: The generated tower
"""
tower = self.generate_random_valid_tower_untrimmed()
tower.trim()
return tower
def add_tower(self, tower):
"""Add a tower to the solution. Assign a rank to it and update solver's
states.
Args:
tower (Tower): The tower to be added
Raises:
RuntimeError: Varies reasons blocking the tower to be added
TypeError: Wrong argument types
"""
if not isinstance(tower, Tower):
raise TypeError ("tower %s is not a Tower object" % tower)
if not tower.is_for(self):
raise RuntimeError ("The tower %s was not created for the solver %s"
% (tower, self))
if np.any(self.coverage[tower.mask]):
raise RuntimeError ("New tower overlaps with the current coverage")
self.num_tower += 1
tower.rank = self.num_tower
self.tower_list.append(tower)
self.coverage[tower.mask] = tower.rank
def plot_coverage(self, im=None):
"""Plot the current coverage in a binary form.
Black means uncovered.
Dark red means covered.
Args:
im (AxiImage, optional): If set, update the data instead of creating
a new plot.
Returns:
AxiImage: The figure used to plot
"""
data = self.coverage > 0
if im:
im.set_data(data)
else:
im = plt.imshow(data, 'hot', vmin=0, vmax=4)
return im
def plot_coverage_history(self, im=None):
"""Plot the current coverage with different color on each tower.
Args:
im (AxiImage, optional): If set, update the data instead of creating
a new plot.
Returns:
AxiImage: The figure used to plot
"""
data = self.coverage.copy()
data[data != 0] += 8
vmax = max(8, np.max(data))
data[data == vmax] += 8
vmax += 8
if im:
im.set_data(data)
im.norm.vmax = vmax
else:
im = plt.imshow(data, 'hot', vmin=0, vmax=vmax)
return im
def plot_coverage_overlay(self, tower_high=None, tower_low=None, im=None):
"""Plot the current coverage with two extra towers overlayed.
Args:
tower_high (Tower, optional): Tower that will be strongly highlighted.
tower_low (Tower, optional): Tower that will be lightly highlighted.
im (AxiImage, optional): If set, update the data instead of creating
a new plot.
Returns:
AxiImage: The figure used to plot
"""
data = np.zeros_like(self.coverage)
data[self.coverage != 0] += 2
if tower_high:
data[tower_high.mask] += 7
if tower_low:
data[tower_low.mask] += 1
if im:
im.set_data(data)
else:
im = plt.imshow(data, 'hot', vmin=0, vmax=8)
return im
def solve_once(self):
"""Solve the problem once, return the number of towers.
Returns:
int: Number of tower used to cover the entire space.
"""
self.clear()
while not np.all(self.coverage):
self.add_tower(self.generate_random_valid_tower_trimmed())
return self.num_tower
def solve(self, times=1):
"""Solve the problem for multiple times, return a list of results
Args:
times (int, optional): Number of times to solve the problem
Returns:
np.array: A list of results (number of towers)
"""
result = np.empty(times, dtype=np.int)
for index in xrange(times):
result[index] = self.solve_once()
return result