forked from sbochkar/distributed_coverage_planner
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathpairwise_reopt.py
267 lines (201 loc) · 7.22 KB
/
pairwise_reopt.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
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
from shapely.geometry import Polygon
from shapely.geometry import LineString
from numpy import linspace
from itertools import product
from chi import compute_chi
from polygon_split import polygon_split
RADIUS = 0.1
LINEAR_PENALTY = 1 # Weights for the cost function
ANGULAR_PENALTY = 10 # Weights for the cost function
DEBUG_LEVEL = 0
def poly_shapely_to_canonical(polygon=[]):
"""
A simple helper function to convert a shapely object representing a polygon
intop a cononical form polygon.
Args:
polygon: A shapely object representing a polygon
Returns:
A polygon in canonical form.
"""
if not polygon:
return []
canonicalPolygon = []
if polygon.exterior.is_ccw:
polyExterior = list(polygon.exterior.coords)
else:
polyExterior = list(polygon.exterior.coords)[::-1]
holes = []
for hole in polygon.interiors:
if hole.is_ccw:
holes.append(list(polygon.exterior.coords)[::-1])
else:
holes.append(list(polygon.exterior.coords))
canonicalPolygon.append(polyExterior)
canonicalPolygon.append(holes)
return canonicalPolygon
def compute_pairwise_optimal(polygonA=[],
polygonB=[],
robotAInitPos=[],
robotBInitPos=[],
nrOfSamples=100,
radius = 0.1,
linPenalty = 1.0,
angPenalty = 10*1.0/360):
"""
Takes two adjacent polygons and attempts to modify the shared edge such that
the metric chi is reduced.
TODO:
Need to investigate assignment of cells to robots.
Args:
polygonA: First polygon in canonical form.
polygonB: Second polygoni n canonical form.
robotAInitPos: Location of robot A.
robotBInitPos: Location of robot B.
nrOfSamples: Samppling density to be used in the search for optimal cut.
Returns:
Returns the cut that minimizes the maximum chi metrix. Or [] if no such
cut exists or original cut is the best.
"""
# The actual algorithm:
# 1) Combine the two polygons
# 2) Find one cut that works better
# 3) Return that cut or no cut if nothing better was found
if not polygonA or not polygonB:
if DEBUG_LEVEL & 0x04:
print("Pairwise reoptimization is requested on an empty polygon.")
return []
if not robotAInitPos or not robotBInitPos:
if DEBUG_LEVEL & 0x04:
print("Pairwise reoptimization is requested on an empty init pos.")
return []
if not Polygon(*polygonA).is_valid or not Polygon(*polygonB).is_valid:
if DEBUG_LEVEL & 0x04:
print("Pariwise reoptimization is requested on invalid polygons.")
return []
if not Polygon(*polygonA).is_valid or not Polygon(*polygonB).is_valid:
if DEBUG_LEVEL & 0x04:
print("Pariwise reoptimization is requested on invalid polygons.")
return []
if not Polygon(*polygonA).is_simple or not Polygon(*polygonB).is_simple:
if DEBUG_LEVEL & 0x04:
print("Pariwise reoptimization is requested on nonsimple polygons.")
return []
if not Polygon(*polygonA).touches(Polygon(*polygonB)):
if DEBUG_LEVEL & 0x04:
print("Pariwise reoptimization is requested on nontouching polys.")
return []
# Check that the polygons intersect only at the boundary and one edge
intersection = Polygon(*polygonA).intersection(Polygon(*polygonB))
if type(intersection) is not LineString:
if DEBUG_LEVEL & 0x04:
print("Pariwise reoptimization is requested but they don't touch\
at an edge.")
return []
# Combine the two polygons
polygonUnion = Polygon(*polygonA).union(Polygon(*polygonB))
# Also create a copy of polygonUnion in canonical form
polygonUnionCanon = poly_shapely_to_canonical(polygonUnion)
if not polygonUnion.is_valid or not polygonUnion.is_simple:
if DEBUG_LEVEL & 0x04:
print("Pariwise reoptimization is requested but the union resulted\
in bad polygon.")
return []
if type(polygonUnion) is not Polygon:
if DEBUG_LEVEL & 0x04:
print("Pariwise reoptimization is requested but union resulted in\
non polygon.")
return []
# Perform intialization stage for the optimization
# Initializae the search space as well original cost
polyExterior = polygonUnion.exterior
searchDistances = list(linspace(0, polyExterior.length, nrOfSamples))
searchSpace = []
for distance in searchDistances:
solutionCandidate = polyExterior.interpolate(distance)
searchSpace.append((solutionCandidate.x, solutionCandidate.y))
# Record the costs at this point
chiL = compute_chi(polygon = polygonA,
initPos = robotAInitPos,
radius = radius,
linPenalty = linPenalty,
angPenalty = angPenalty)
chiR = compute_chi(polygon = polygonB,
initPos = robotBInitPos,
radius = radius,
linPenalty = linPenalty,
angPenalty = angPenalty)
initMaxChi = max(chiL, chiR)
minMaxChiFinal = 10e10
minCandidate = []
# This search is over any two pairs of samples points on the exterior
# It is a very costly search.
for cutEdge in product(searchSpace, repeat=2):
if DEBUG_LEVEL & 0x8:
print("polygonUnionCanon: %s"%polygonUnionCanon)
print("Cut candidate: %s"%(cutEdge, ))
result = polygon_split(polygonUnionCanon, cutEdge)
if DEBUG_LEVEL & 0x8:
if result:
print("%s Split Line: %s"%('GOOD', cutEdge,))
else:
print("%s Split Line: %s"%("BAD ", cutEdge))
if result:
# Resolve cell-robot assignments here.
# This is to avoid the issue of cell assignments that
# don't make any sense after polygon cut.
chiA0 = compute_chi(polygon = result[0],
initPos = robotAInitPos,
radius = radius,
linPenalty = linPenalty,
angPenalty = angPenalty)
chiA1 = compute_chi(polygon = result[1],
initPos = robotAInitPos,
radius = radius,
linPenalty = linPenalty,
angPenalty = angPenalty)
chiB0 = compute_chi(polygon = result[0],
initPos = robotBInitPos,
radius = radius,
linPenalty = linPenalty,
angPenalty = angPenalty)
chiB1 = compute_chi(polygon = result[1],
initPos = robotBInitPos,
radius = radius,
linPenalty = linPenalty,
angPenalty = angPenalty)
maxChiCases = [max(chiA0, chiB1),
max(chiA1, chiB0)]
minMaxChi = min(maxChiCases)
if minMaxChi <= minMaxChiFinal:
minCandidate = cutEdge
minMaxChiFinal = minMaxChi
if DEBUG_LEVEL & 0x8:
print("Computed min max chi as: %4.2f"%minMaxChiFinal)
print("Cut: %s"%(minCandidate, ))
if initMaxChi < minMaxChiFinal:
if DEBUG_LEVEL & 0x8:
print("No cut results in minimum altitude")
return []
newPolygons = polygon_split(polygonUnionCanon, minCandidate)
return newPolygons
if __name__ == '__main__':
# If package is launched from cmd line, run sanity checks
global DEBUG_LEVEL
DEBUG_LEVEL = 0 #0x8+0x4
print("\nSanity tests for pairwise reoptimization.\n")
P1 = [[(0, 0), (1, 0), (1, 1), (0, 1)], []]
P2 = [[(1, 0), (2, 0), (2, 1), (1, 1)], []]
initA = (0, 0)
initB = (1, 0)
result = "PASS" if not compute_pairwise_optimal(P1, P2, initA, initB) else "FAIL"
print("[%s] Simple two polygon test."%result)
P1 = [[(0, 0), (1, 0), (1, 1), (0, 1)], []]
P2 = [[(1, 0), (2, 0), (2, 1), (1, 1)], []]
initA = (0, 0)
initB = (0, 0)
print compute_pairwise_optimal(P1, P2, initA, initB)
P1 = [[(0, 0), (1, 0), (1, 1), (0, 1)], []]
P2 = [[(1, 0), (2, 0), (2, 1), (1, 1)], []]
initA = (0, 0)
initB = (0, 1)
print compute_pairwise_optimal(P1, P2, initA, initB)