-
Notifications
You must be signed in to change notification settings - Fork 8
/
PWLPlan.py
363 lines (315 loc) · 12.7 KB
/
PWLPlan.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
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
from gurobipy import *
from math import *
import numpy as np
import time
M = 1e3
# a large M causes numerical issues and make the model infeasible to Gurobi
T_MIN_SEP = 1e-2
# see comments in GreaterThanZero
IntFeasTol = 1e-1 * T_MIN_SEP / M
def setM(v):
global M, IntFeasTol
M = v
IntFeasTol = 1e-1 * T_MIN_SEP / M
EPS = 1e-2
def _sub(x1, x2):
return [x1[i] - x2[i] for i in range(len(x1))]
def _add(x1, x2):
return [x1[i] + x2[i] for i in range(len(x1))]
def L1Norm(model, x):
xvar = model.addVars(len(x), lb=-GRB.INFINITY)
abs_x = model.addVars(len(x))
model.update()
xvar = [xvar[i] for i in range(len(xvar))]
abs_x = [abs_x[i] for i in range(len(abs_x))]
for i in range(len(x)):
model.addConstr(xvar[i] == x[i])
model.addConstr(abs_x[i] == abs_(xvar[i]))
return sum(abs_x)
class Conjunction(object):
# conjunction node
def __init__(self, deps = []):
super(Conjunction, self).__init__()
self.deps = deps
self.constraints = []
class Disjunction(object):
# disjunction node
def __init__(self, deps = []):
super(Disjunction, self).__init__()
self.deps = deps
self.constraints = []
def noIntersection(a, b, c, d):
# z = 1 iff. [a, b] and [c, d] has no intersection
# b < c or d < a
return Disjunction([c-b-EPS, a-d-EPS])
def hasIntersection(a, b, c, d):
# z = 1 iff. [a, b] and [c, d] has no intersection
# b >= c and d >= a
return Conjunction([b-c, d-a])
def always(i, a, b, zphis, PWL):
t_i = PWL[i][1]
t_i_1 = PWL[i+1][1]
conjunctions = []
for j in range(len(PWL)-1):
t_j = PWL[j][1]
t_j_1 = PWL[j+1][1]
conjunctions.append(Disjunction([noIntersection(t_j, t_j_1, t_i + a, t_i_1 + b), zphis[j]]))
return Conjunction(conjunctions)
def eventually(i, a, b, zphis, PWL):
t_i = PWL[i][1]
t_i_1 = PWL[i+1][1]
z_intervalWidth = b-a-(t_i_1-t_i)-EPS
disjunctions = []
for j in range(len(PWL)-1):
t_j = PWL[j][1]
t_j_1 = PWL[j+1][1]
disjunctions.append(Conjunction([hasIntersection(t_j, t_j_1, t_i_1 + a, t_i + b), zphis[j]]))
return Conjunction([z_intervalWidth, Disjunction(disjunctions)])
def bounded_eventually(i, a, b, zphis, PWL, tmax):
t_i = PWL[i][1]
t_i_1 = PWL[i+1][1]
z_intervalWidth = b-a-(t_i_1-t_i)-EPS
disjunctions = []
for j in range(len(PWL)-1):
t_j = PWL[j][1]
t_j_1 = PWL[j+1][1]
disjunctions.append(Conjunction([hasIntersection(t_j, t_j_1, t_i_1 + a, t_i + b), zphis[j]]))
return Disjunction([Conjunction([z_intervalWidth, Disjunction(disjunctions)]), t_i+b-tmax-EPS])
def until(i, a, b, zphi1s, zphi2s, PWL):
t_i = PWL[i][1]
t_i_1 = PWL[i+1][1]
z_intervalWidth = b-a-(t_i_1-t_i)-EPS
disjunctions = []
for j in range(len(PWL)-1):
t_j = PWL[j][1]
t_j_1 = PWL[j+1][1]
conjunctions = [hasIntersection(t_j, t_j_1, t_i_1 + a, t_i + b), zphi2s[j]]
for l in range(j+1):
t_l = PWL[l][1]
t_l_1 = PWL[l+1][1]
conjunctions.append(Disjunction([noIntersection(t_l, t_l_1, t_i, t_i_1 + b), zphi1s[l]]))
disjunctions.append(Conjunction(conjunctions))
return Conjunction([z_intervalWidth, Disjunction(disjunctions)])
def release(i, a, b, zphi1s, zphi2s, PWL):
t_i = PWL[i][1]
t_i_1 = PWL[i+1][1]
conjunctions = []
for j in range(len(PWL)-1):
t_j = PWL[j][1]
t_j_1 = PWL[j+1][1]
disjunctions = [noIntersection(t_j, t_j_1, t_i_1 + a, t_i + b), zphi2s[j]]
for l in range(j):
t_l = PWL[l][1]
t_l_1 = PWL[l+1][1]
disjunctions.append(Conjunction([hasIntersection(t_l, t_l_1, t_i_1, t_i_1 + b), zphi1s[l]]))
conjunctions.append(Disjunction(disjunctions))
return Conjunction(conjunctions)
def mu(i, PWL, bloat_factor, A, b):
bloat_factor = np.max([0, bloat_factor])
# this segment is fully contained in Ax<=b (shrinked)
b = b.reshape(-1)
num_edges = len(b)
conjunctions = []
for e in range(num_edges):
a = A[e,:]
for j in [i, i+1]:
x = PWL[j][0]
conjunctions.append(b[e] - np.linalg.norm(a) * bloat_factor - sum([a[k]*x[k] for k in range(len(x))]) - EPS)
return Conjunction(conjunctions)
def negmu(i, PWL, bloat_factor, A, b):
# this segment is outside Ax<=b (bloated)
b = b.reshape(-1)
num_edges = len(b)
disjunctions = []
for e in range(num_edges):
a = A[e,:]
conjunctions = []
for j in [i, i+1]:
x = PWL[j][0]
conjunctions.append(sum([a[k]*x[k] for k in range(len(x))]) - (b[e] + np.linalg.norm(a) * bloat_factor) - EPS)
disjunctions.append(Conjunction(conjunctions))
return Disjunction(disjunctions)
def add_space_constraints(model, xlist, limits, bloat=0.):
xlim, ylim = limits
for x in xlist:
model.addConstr(x[0] >= (xlim[0] + bloat))
model.addConstr(x[1] >= (ylim[0] + bloat))
model.addConstr(x[0] <= (xlim[1] - bloat))
model.addConstr(x[1] <= (ylim[1] - bloat))
return None
def add_time_constraints(model, PWL, tmax=None):
if tmax is not None:
model.addConstr(PWL[-1][1] <= tmax - T_MIN_SEP)
for i in range(len(PWL)-1):
x1, t1 = PWL[i]
x2, t2 = PWL[i+1]
model.addConstr(t2 - t1 >= T_MIN_SEP)
def add_velocity_constraints(model, PWL, vmax=3):
for i in range(len(PWL)-1):
x1, t1 = PWL[i]
x2, t2 = PWL[i+1]
# squared_dist = sum([(x1[j]-x2[j])*(x1[j]-x2[j]) for j in range(len(x1))])
# model.addConstr(squared_dist <= (vmax**2) * (t2 - t1) * (t2 - t1))
L1_dist = L1Norm(model, _sub(x1,x2))
model.addConstr(L1_dist <= vmax * (t2 - t1))
def disjoint_segments(model, seg1, seg2, bloat):
assert(len(seg1) == 2)
assert(len(seg2) == 2)
# assuming that bloat is the error bound in two norm for one agent
return 0.5 * L1Norm(model, _sub(_add(seg1[0], seg1[1]), _add(seg2[0], seg2[1]))) - 0.5 * (L1Norm(model, _sub(seg1[0], seg1[1])) + L1Norm(model, _sub(seg2[0], seg2[1]))) - 2*bloat*np.sqrt(len(seg1[0])) - EPS
def add_mutual_clearance_constraints(model, PWLs, bloat):
for i in range(len(PWLs)):
for j in range(i+1, len(PWLs)):
PWL1 = PWLs[i]
PWL2 = PWLs[j]
for k in range(len(PWL1)-1):
for l in range(len(PWL2)-1):
x11, t11 = PWL1[k]
x12, t12 = PWL1[k+1]
x21, t21 = PWL2[l]
x22, t22 = PWL2[l+1]
z_noIntersection = noIntersection(t11, t12, t21, t22)
z_disjoint_segments = disjoint_segments(model, [x11, x12], [x21, x22], bloat)
z = Disjunction([z_noIntersection, z_disjoint_segments])
add_CDTree_Constraints(model, z)
class Node(object):
"""docstring for Node"""
def __init__(self, op, deps = [], zs = [], info = []):
super(Node, self).__init__()
self.op = op
self.deps = deps
self.zs = zs
self.info = info
def clearSpecTree(spec):
for dep in spec.deps:
clearSpecTree(dep)
spec.zs = []
def handleSpecTree(spec, PWL, bloat_factor, size):
for dep in spec.deps:
handleSpecTree(dep, PWL, bloat_factor, size)
if len(spec.zs) == len(PWL)-1:
return
elif len(spec.zs) > 0:
raise ValueError('incomplete zs')
if spec.op == 'mu':
spec.zs = [mu(i, PWL, 0.1, spec.info['A'], spec.info['b']) for i in range(len(PWL)-1)]
elif spec.op == 'negmu':
spec.zs = [negmu(i, PWL, bloat_factor + size, spec.info['A'], spec.info['b']) for i in range(len(PWL)-1)]
elif spec.op == 'and':
spec.zs = [Conjunction([dep.zs[i] for dep in spec.deps]) for i in range(len(PWL)-1)]
elif spec.op == 'or':
spec.zs = [Disjunction([dep.zs[i] for dep in spec.deps]) for i in range(len(PWL)-1)]
elif spec.op == 'U':
spec.zs = [until(i, spec.info['int'][0], spec.info['int'][1], spec.deps[0].zs, spec.deps[1].zs, PWL) for i in range(len(PWL)-1)]
elif spec.op == 'F':
spec.zs = [eventually(i, spec.info['int'][0], spec.info['int'][1], spec.deps[0].zs, PWL) for i in range(len(PWL)-1)]
elif spec.op == 'BF':
spec.zs = [bounded_eventually(i, spec.info['int'][0], spec.info['int'][1], spec.deps[0].zs, PWL, spec.info['tmax']) for i in range(len(PWL)-1)]
elif spec.op == 'A':
spec.zs = [always(i, spec.info['int'][0], spec.info['int'][1], spec.deps[0].zs, PWL) for i in range(len(PWL)-1)]
else:
raise ValueError('wrong op code')
def gen_CDTree_constraints(model, root):
if not hasattr(root, 'deps'):
return [root,]
else:
if len(root.constraints)>0:
# TODO: more check here
return root.constraints
dep_constraints = []
for dep in root.deps:
dep_constraints.append(gen_CDTree_constraints(model, dep))
zs = []
for dep_con in dep_constraints:
if isinstance(root, Disjunction):
z = model.addVar(vtype=GRB.BINARY)
zs.append(z)
dep_con = [con + M * (1 - z) for con in dep_con]
root.constraints += dep_con
if len(zs)>0:
root.constraints.append(sum(zs)-1)
model.update()
return root.constraints
def add_CDTree_Constraints(model, root):
constrs = gen_CDTree_constraints(model, root)
for con in constrs:
model.addConstr(con >= 0)
def plan(x0s, specs, bloat, limits=None, num_segs=None, tasks=None, vmax=3., MIPGap=1e-4, max_segs=None, tmax=None, hard_goals=None, size=0.11*4/2):
if num_segs is None:
min_segs = 1
assert max_segs is not None
else:
min_segs = num_segs
max_segs = num_segs
for num_segs in range(min_segs, max_segs+1):
for spec in specs:
clearSpecTree(spec)
if tasks:
for task in tasks:
for t in task:
clearSpecTree(t)
print('----------------------------')
print('num_segs', num_segs)
PWLs = []
m = Model("xref")
# m.setParam(GRB.Param.OutputFlag, 0)
m.setParam(GRB.Param.IntFeasTol, IntFeasTol)
m.setParam(GRB.Param.MIPGap, MIPGap)
# m.setParam(GRB.Param.NonConvex, 2)
# m.getEnv().set(GRB_IntParam_OutputFlag, 0)
for idx_a in range(len(x0s)):
x0 = x0s[idx_a]
x0 = np.array(x0).reshape(-1).tolist()
spec = specs[idx_a]
dims = len(x0)
PWL = []
for i in range(num_segs+1):
PWL.append([m.addVars(dims, lb=-GRB.INFINITY), m.addVar()])
PWLs.append(PWL)
m.update()
# the initial constriant
m.addConstrs(PWL[0][0][i] == x0[i] for i in range(dims))
m.addConstr(PWL[0][1] == 0)
if hard_goals is not None:
goal = hard_goals[idx_a]
m.addConstrs(PWL[-1][0][i] == goal[i] for i in range(dims))
if limits is not None:
add_space_constraints(m, [P[0] for P in PWL], limits)
add_velocity_constraints(m, PWL, vmax=vmax)
add_time_constraints(m, PWL, tmax)
handleSpecTree(spec, PWL, bloat, size)
add_CDTree_Constraints(m, spec.zs[0])
if tasks is not None:
for idx_agent in range(len(tasks)):
for idx_task in range(len(tasks[idx_agent])):
handleSpecTree(tasks[idx_agent][idx_task], PWLs[idx_agent], bloat, size)
conjunctions = []
for idx_task in range(len(tasks[0])):
disjunctions = [tasks[idx_agent][idx_task].zs[0] for idx_agent in range(len(tasks))]
conjunctions.append(Disjunction(disjunctions))
z = Conjunction(conjunctions)
add_CDTree_Constraints(m, z)
add_mutual_clearance_constraints(m, PWLs, bloat)
# obj = sum([L1Norm(m, _sub(PWL[i][0], PWL[i+1][0])) for PWL in PWLs for i in range(len(PWL)-1)])
obj = sum([PWL[-1][1] for PWL in PWLs])
m.setObjective(obj, GRB.MINIMIZE)
m.write("test.lp")
print('NumBinVars: %d'%m.getAttr('NumBinVars'))
# m.computeIIS()
# import ipdb;ipdb.set_treace()
try:
start = time.time()
m.optimize()
end = time.time()
print('sovling it takes %.3f s'%(end - start))
PWLs_output = []
for PWL in PWLs:
PWL_output = []
for P in PWL:
PWL_output.append([[P[0][i].X for i in range(len(P[0]))], P[1].X])
PWLs_output.append(PWL_output)
m.dispose()
return PWLs_output
except Exception as e:
m.dispose()
return [None,]