|
a |
|
b/arm_model/util.py |
|
|
1 |
#!/usr/bin/env python |
|
|
2 |
# -*- coding: utf-8 -*- |
|
|
3 |
import os |
|
|
4 |
import time |
|
|
5 |
import sympy as sp |
|
|
6 |
import numpy as np |
|
|
7 |
import pandas as pd |
|
|
8 |
from tqdm import tqdm |
|
|
9 |
import matplotlib.pyplot as plt |
|
|
10 |
from mpl_toolkits.mplot3d import Axes3D |
|
|
11 |
from matplotlib.collections import EllipseCollection |
|
|
12 |
from matplotlib import patches |
|
|
13 |
from fractions import Fraction |
|
|
14 |
|
|
|
15 |
# ------------------------------------------------------------------------------ |
|
|
16 |
# logger |
|
|
17 |
# ------------------------------------------------------------------------------ |
|
|
18 |
|
|
|
19 |
# logging.basicConfig(format='%(levelname)s %(asctime)s @%(name)s # %(message)s', |
|
|
20 |
# datefmt='%m/%d/%Y %I:%M:%S %p', |
|
|
21 |
# # filename='example.log', |
|
|
22 |
# level=logging.DEBUG) |
|
|
23 |
|
|
|
24 |
# ------------------------------------------------------------------------------ |
|
|
25 |
# utilities |
|
|
26 |
# ------------------------------------------------------------------------------ |
|
|
27 |
|
|
|
28 |
|
|
|
29 |
def mat_show(mat): |
|
|
30 |
"""Graphical visualization of a 2D matrix. |
|
|
31 |
""" |
|
|
32 |
fig = plt.figure() |
|
|
33 |
ax = fig.add_subplot(111) |
|
|
34 |
cax = ax.matshow(to_np_mat(mat), interpolation='nearest') |
|
|
35 |
fig.colorbar(cax) |
|
|
36 |
|
|
|
37 |
|
|
|
38 |
def is_symmetric(a, tol=1e-8): |
|
|
39 |
"""Check if matrix is symmetric. |
|
|
40 |
|
|
|
41 |
""" |
|
|
42 |
return np.allclose(a, a.T, atol=tol) |
|
|
43 |
|
|
|
44 |
|
|
|
45 |
def mat(array): |
|
|
46 |
"""For a given 2D array return a numpy matrix. |
|
|
47 |
""" |
|
|
48 |
return np.matrix(array) |
|
|
49 |
|
|
|
50 |
|
|
|
51 |
def vec(vector): |
|
|
52 |
"""Construct a column vector of type numpy matrix. |
|
|
53 |
""" |
|
|
54 |
return np.matrix(vector).reshape(-1, 1) |
|
|
55 |
|
|
|
56 |
|
|
|
57 |
def to_np_mat(sympy_mat): |
|
|
58 |
"""Cast sympy Matrix to numpy matrix of float type. |
|
|
59 |
|
|
|
60 |
Parameters |
|
|
61 |
---------- |
|
|
62 |
m: sympy 2D matrix |
|
|
63 |
|
|
|
64 |
Returns |
|
|
65 |
------- |
|
|
66 |
a numpy asmatrix |
|
|
67 |
|
|
|
68 |
""" |
|
|
69 |
return np.asmatrix(sympy_mat.tolist(), dtype=np.float) |
|
|
70 |
|
|
|
71 |
|
|
|
72 |
def to_np_array(sympy_mat): |
|
|
73 |
"""Cast sympy Matrix to numpy matrix of float type. Works for N-D matrices as |
|
|
74 |
compared to to_np_mat(). |
|
|
75 |
|
|
|
76 |
Parameters |
|
|
77 |
---------- |
|
|
78 |
m: sympy 2D matrix |
|
|
79 |
|
|
|
80 |
Returns |
|
|
81 |
------- |
|
|
82 |
a numpy asmatrix |
|
|
83 |
|
|
|
84 |
""" |
|
|
85 |
return np.asarray(sympy_mat.tolist(), dtype=np.float) |
|
|
86 |
|
|
|
87 |
|
|
|
88 |
def to_np_vec(sympy_vec): |
|
|
89 |
"""Transforms a 1D sympy vector (e.g. 5 x 1) to numpy array (e.g. (5,)). |
|
|
90 |
|
|
|
91 |
Parameters |
|
|
92 |
---------- |
|
|
93 |
v: 1D sympy vector |
|
|
94 |
|
|
|
95 |
Returns |
|
|
96 |
------- |
|
|
97 |
a 1D numpy array |
|
|
98 |
|
|
|
99 |
""" |
|
|
100 |
return np.asarray(sp.flatten(sympy_vec), dtype=np.float) |
|
|
101 |
|
|
|
102 |
|
|
|
103 |
def is_pd(A): |
|
|
104 |
"""Check if matrix is positive definite |
|
|
105 |
""" |
|
|
106 |
return np.all(np.linalg.eigvals(A) > 0) |
|
|
107 |
|
|
|
108 |
|
|
|
109 |
def nullspace(A, atol=1e-13, rtol=0): |
|
|
110 |
"""Compute an approximate basis for the nullspace of A. |
|
|
111 |
|
|
|
112 |
The algorithm used by this function is based on the singular value |
|
|
113 |
decomposition of `A`. |
|
|
114 |
|
|
|
115 |
Parameters |
|
|
116 |
---------- |
|
|
117 |
A : ndarray |
|
|
118 |
A should be at most 2-D. A 1-D array with length k will be treated |
|
|
119 |
as a 2-D with shape (1, k) |
|
|
120 |
atol : float |
|
|
121 |
The absolute tolerance for a zero singular value. Singular values |
|
|
122 |
smaller than `atol` are considered to be zero. |
|
|
123 |
rtol : float |
|
|
124 |
The relative tolerance. Singular values less than rtol*smax are |
|
|
125 |
considered to be zero, where smax is the largest singular value. |
|
|
126 |
|
|
|
127 |
If both `atol` and `rtol` are positive, the combined tolerance is the |
|
|
128 |
maximum of the two; that is:: |
|
|
129 |
tol = max(atol, rtol * smax) |
|
|
130 |
Singular values smaller than `tol` are considered to be zero. |
|
|
131 |
|
|
|
132 |
Return value |
|
|
133 |
------------ |
|
|
134 |
ns : ndarray |
|
|
135 |
If `A` is an array with shape (m, k), then `ns` will be an array |
|
|
136 |
with shape (k, n), where n is the estimated dimension of the |
|
|
137 |
nullspace of `A`. The columns of `ns` are a basis for the |
|
|
138 |
nullspace; each element in numpy.dot(A, ns) will be approximately |
|
|
139 |
zero. |
|
|
140 |
""" |
|
|
141 |
|
|
|
142 |
A = np.atleast_2d(A) |
|
|
143 |
u, s, vh = np.linalg.svd(A) |
|
|
144 |
tol = max(atol, rtol * s[0]) |
|
|
145 |
nnz = (s >= tol).sum() |
|
|
146 |
ns = vh[nnz:].conj().T |
|
|
147 |
return ns |
|
|
148 |
|
|
|
149 |
|
|
|
150 |
def lrs_inequality_vertex_enumeration(A, b): |
|
|
151 |
"""Find the vertices given an inequality system A * x <= b. This function |
|
|
152 |
depends on lrs library. |
|
|
153 |
|
|
|
154 |
Parameters |
|
|
155 |
---------- |
|
|
156 |
|
|
|
157 |
A: numpy array [m x n] |
|
|
158 |
|
|
|
159 |
b: numpy array [m] |
|
|
160 |
|
|
|
161 |
Returns |
|
|
162 |
------- |
|
|
163 |
|
|
|
164 |
v: numpy array [k x n] |
|
|
165 |
the vertices of the polytope |
|
|
166 |
|
|
|
167 |
""" |
|
|
168 |
# export H-representation |
|
|
169 |
with open('temp.ine', 'w') as file_handle: |
|
|
170 |
file_handle.write('Feasible_Set\n') |
|
|
171 |
file_handle.write('H-representation\n') |
|
|
172 |
file_handle.write('begin\n') |
|
|
173 |
file_handle.write(str(A.shape[0]) + ' ' + |
|
|
174 |
str(A.shape[1] + 1) + ' rational\n') |
|
|
175 |
for i in range(0, A.shape[0]): |
|
|
176 |
file_handle.write(str(Fraction(b[i]))) |
|
|
177 |
for j in range(0, A.shape[1]): |
|
|
178 |
file_handle.write(' ' + str(Fraction(-A[i, j]))) |
|
|
179 |
|
|
|
180 |
file_handle.write('\n') |
|
|
181 |
|
|
|
182 |
file_handle.write('end\n') |
|
|
183 |
|
|
|
184 |
# call lrs |
|
|
185 |
try: |
|
|
186 |
os.system('lrs temp.ine > temp.ext') |
|
|
187 |
except OSError as e: |
|
|
188 |
raise RuntimeError(e) |
|
|
189 |
|
|
|
190 |
# read the V-representation |
|
|
191 |
vertices = [] |
|
|
192 |
with open('temp.ext', 'r') as file_handle: |
|
|
193 |
begin = False |
|
|
194 |
for line in file_handle: |
|
|
195 |
if begin: |
|
|
196 |
if 'end' in line: |
|
|
197 |
break |
|
|
198 |
|
|
|
199 |
comp = line.split() |
|
|
200 |
try: |
|
|
201 |
v_type = comp.pop(0) |
|
|
202 |
except: |
|
|
203 |
print('No feasible solution') |
|
|
204 |
|
|
|
205 |
if v_type is '1': |
|
|
206 |
v = [float(Fraction(i)) for i in comp] |
|
|
207 |
vertices.append(v) |
|
|
208 |
|
|
|
209 |
else: |
|
|
210 |
if 'begin' in line: |
|
|
211 |
begin = True |
|
|
212 |
|
|
|
213 |
# delete temporary files |
|
|
214 |
try: |
|
|
215 |
os.system('rm temp.ine temp.ext') |
|
|
216 |
except OSError as e: |
|
|
217 |
pass |
|
|
218 |
|
|
|
219 |
return vertices |
|
|
220 |
|
|
|
221 |
|
|
|
222 |
def ccd_inequality_vertex_enumeration(A, b): |
|
|
223 |
"""Find the vertices given an inequality system A * x <= b. This function |
|
|
224 |
depends on pycddlib (cdd). |
|
|
225 |
|
|
|
226 |
Parameters |
|
|
227 |
---------- |
|
|
228 |
|
|
|
229 |
A: numpy array [m x n] |
|
|
230 |
|
|
|
231 |
b: numpy array [m] |
|
|
232 |
|
|
|
233 |
Returns |
|
|
234 |
------- |
|
|
235 |
|
|
|
236 |
v: numpy array [k x n] |
|
|
237 |
the vertices of the polytope |
|
|
238 |
|
|
|
239 |
""" |
|
|
240 |
import cdd |
|
|
241 |
# try floating point, if problem fails try exact arithmetics (slow) |
|
|
242 |
try: |
|
|
243 |
M = cdd.Matrix(np.hstack((b.reshape(-1, 1), -A)), |
|
|
244 |
number_type='float') |
|
|
245 |
M.rep_type = cdd.RepType.INEQUALITY |
|
|
246 |
p = cdd.Polyhedron(M) |
|
|
247 |
except: |
|
|
248 |
print('Warning: switch to exact arithmetics') |
|
|
249 |
M = cdd.Matrix(np.hstack((b.reshape(-1, 1), -A)), |
|
|
250 |
number_type='fraction') |
|
|
251 |
M.rep_type = cdd.RepType.INEQUALITY |
|
|
252 |
p = cdd.Polyhedron(M) |
|
|
253 |
|
|
|
254 |
G = np.array(p.get_generators()) |
|
|
255 |
|
|
|
256 |
if not G.shape[0] == 0: |
|
|
257 |
return G[np.where(G[:, 0] == 1.0)[0], 1:].tolist() |
|
|
258 |
else: |
|
|
259 |
raise ValueError('Infeasible Inequality') |
|
|
260 |
|
|
|
261 |
|
|
|
262 |
def optimization_based_sampling(A, b, optimziation_samples, |
|
|
263 |
closiness_tolerance, max_opt_iterations): |
|
|
264 |
"""Efficient method for sampling the feasible set for a large system of |
|
|
265 |
inequalities (A x <= b). When the dimension of the set (x) is large, |
|
|
266 |
deterministic and pure randomized techniques fail to solve this problem |
|
|
267 |
efficiently. |
|
|
268 |
|
|
|
269 |
This method uses constrained optimization in order to find n solutions that |
|
|
270 |
satisfy the inequality. Each iteration new randomized objective function is |
|
|
271 |
assigned so that the optimization will find a different solution. |
|
|
272 |
|
|
|
273 |
Parameters |
|
|
274 |
---------- |
|
|
275 |
A: numpy array [m x n] |
|
|
276 |
|
|
|
277 |
b: numpy array [m] |
|
|
278 |
|
|
|
279 |
optimziation_samples: integer |
|
|
280 |
number of samples to generate |
|
|
281 |
|
|
|
282 |
closiness_tolerance: float |
|
|
283 |
accept solution if distance is larger than the provided tolerance |
|
|
284 |
|
|
|
285 |
max_opt_iterations: integer |
|
|
286 |
maximum iteration of the optimization algorithm |
|
|
287 |
|
|
|
288 |
Returns |
|
|
289 |
------- |
|
|
290 |
|
|
|
291 |
solutions: list |
|
|
292 |
|
|
|
293 |
""" |
|
|
294 |
from scipy.optimize import minimize |
|
|
295 |
|
|
|
296 |
nullity = A.shape[1] |
|
|
297 |
solutions = [] |
|
|
298 |
j = 0 |
|
|
299 |
while j < optimziation_samples: |
|
|
300 |
# change objective function randomly (Dirichlet distribution ensures |
|
|
301 |
# that w sums to 1) |
|
|
302 |
w = np.random.dirichlet(np.ones(nullity), size=1) |
|
|
303 |
|
|
|
304 |
def objective(x): |
|
|
305 |
return np.sum((w * x) ** 2) |
|
|
306 |
|
|
|
307 |
# solve the optimization_based_sampling |
|
|
308 |
def inequality_constraint(x): |
|
|
309 |
return A.dot(x) - b |
|
|
310 |
|
|
|
311 |
x0 = np.random.uniform(-1, 1, nullity) |
|
|
312 |
bounds = tuple([(None, None) for i in range(0, nullity)]) |
|
|
313 |
constraints = ({'type': 'ineq', 'fun': inequality_constraint}) |
|
|
314 |
options = {'maxiter': max_opt_iterations} |
|
|
315 |
sol = minimize(objective, x0, method='SLSQP', |
|
|
316 |
bounds=bounds, |
|
|
317 |
constraints=constraints, |
|
|
318 |
options=options) |
|
|
319 |
|
|
|
320 |
x = sol.x |
|
|
321 |
# check if solution satisfies the system |
|
|
322 |
if np.all(A.dot(x) <= b): |
|
|
323 |
# if solution is not close to the rest then accept |
|
|
324 |
close = False |
|
|
325 |
for xs in solutions: |
|
|
326 |
if np.linalg.norm(xs - x, 2) < closiness_tolerance: |
|
|
327 |
close = True |
|
|
328 |
break |
|
|
329 |
|
|
|
330 |
if not close: |
|
|
331 |
solutions.append(x) |
|
|
332 |
j = j + 1 |
|
|
333 |
|
|
|
334 |
return solutions |
|
|
335 |
|
|
|
336 |
|
|
|
337 |
def convex_bounded_vertex_enumeration(A, |
|
|
338 |
b, |
|
|
339 |
convex_combination_passes=1, |
|
|
340 |
method='lrs'): |
|
|
341 |
"""Sample a convex, bounded inequality system A * x <= b. The vertices of the |
|
|
342 |
convex polytope are first determined. Then the convexity property is used to |
|
|
343 |
generate additional solutions within the polytope. |
|
|
344 |
|
|
|
345 |
Parameters |
|
|
346 |
---------- |
|
|
347 |
|
|
|
348 |
A: numpy array [m x n] |
|
|
349 |
|
|
|
350 |
b: numpy array [m] |
|
|
351 |
|
|
|
352 |
convex_combination_passes: int (default 1) |
|
|
353 |
recombine vertices to generate additional solutions using the convex |
|
|
354 |
property |
|
|
355 |
|
|
|
356 |
method: str (lrs or cdd or rnd) |
|
|
357 |
|
|
|
358 |
Returns |
|
|
359 |
------- |
|
|
360 |
|
|
|
361 |
v: numpy array [k x n] |
|
|
362 |
solutions within the convex polytope |
|
|
363 |
|
|
|
364 |
""" |
|
|
365 |
# find polytope vertices |
|
|
366 |
if method == 'lrs': |
|
|
367 |
solutions = lrs_inequality_vertex_enumeration(A, b) |
|
|
368 |
elif method == 'cdd': |
|
|
369 |
solutions = ccd_inequality_vertex_enumeration(A, b) |
|
|
370 |
elif method == 'rnd': |
|
|
371 |
solutions = optimization_based_sampling(A, b, |
|
|
372 |
A.shape[0] ** 2, |
|
|
373 |
1e-3, 1000) |
|
|
374 |
else: |
|
|
375 |
raise RuntimeError('Unsupported method: choose "lrs" or "cdd" or "rnd"') |
|
|
376 |
|
|
|
377 |
# since the feasible space is a convex set we can find additional solution |
|
|
378 |
# in the form z = a * x_i + (1-a) x_j |
|
|
379 |
for g in range(0, convex_combination_passes): |
|
|
380 |
n = len(solutions) |
|
|
381 |
for i in range(0, n): |
|
|
382 |
for j in range(0, n): |
|
|
383 |
if i == j: |
|
|
384 |
continue |
|
|
385 |
|
|
|
386 |
a = 0.5 |
|
|
387 |
x1 = np.array(solutions[i]) |
|
|
388 |
x2 = np.array(solutions[j]) |
|
|
389 |
z = a * x1 + (1 - a) * x2 |
|
|
390 |
solutions.append(z.tolist()) |
|
|
391 |
|
|
|
392 |
# remove duplicates from 2D list |
|
|
393 |
solutions = [list(t) for t in set(tuple(element) for element in solutions)] |
|
|
394 |
|
|
|
395 |
return np.array(solutions, np.float) |
|
|
396 |
|
|
|
397 |
|
|
|
398 |
def test_inequality_sampling(d): |
|
|
399 |
A = np.array([[0, 0, -1], |
|
|
400 |
[0, -1, 0], |
|
|
401 |
[1, 0, 0], |
|
|
402 |
[-1, 0, 0], |
|
|
403 |
[0, 1, 0], |
|
|
404 |
[0, 0, 1]]) |
|
|
405 |
b = 0.5 * np.ones(6) |
|
|
406 |
print(A, b) |
|
|
407 |
t1 = time.time() |
|
|
408 |
# solutions = optimization_based_sampling(A, b, 20, 0.3, 1000) |
|
|
409 |
solutions = convex_bounded_vertex_enumeration(A, b, d) |
|
|
410 |
t2 = time.time() |
|
|
411 |
print('Execution time: ' + str(t2 - t1) + 's') |
|
|
412 |
|
|
|
413 |
fig = plt.figure() |
|
|
414 |
ax = fig.add_subplot(111, projection='3d') |
|
|
415 |
ax.scatter(solutions[:, 0], solutions[:, 1], solutions[:, 2]) |
|
|
416 |
ax.set_xlabel('x') |
|
|
417 |
ax.set_ylabel('y') |
|
|
418 |
ax.set_zlabel('z') |
|
|
419 |
plt.show() |
|
|
420 |
fig.tight_layout() |
|
|
421 |
fig.savefig('results/inequality_sampling/inequality_sampling_d' + str(d) + '.png', |
|
|
422 |
format='png', dpi=300) |
|
|
423 |
fig.savefig('results/inequality_sampling/inequality_sampling_d' + str(d) + '.pdf', |
|
|
424 |
format='pdf', dpi=300) |
|
|
425 |
fig.savefig('results/inequality_sampling/inequality_sampling_d' + str(d) + '.eps', |
|
|
426 |
format='eps', dpi=300) |
|
|
427 |
|
|
|
428 |
|
|
|
429 |
# test_inequality_sampling(0) |
|
|
430 |
# test_inequality_sampling(1) |
|
|
431 |
# test_inequality_sampling(2) |
|
|
432 |
|
|
|
433 |
def tensor3_vector_product(T, v): |
|
|
434 |
"""Implements a product of a rank-3 tensor (3D array) with a vector using |
|
|
435 |
tensor product and tensor contraction. |
|
|
436 |
|
|
|
437 |
Parameters |
|
|
438 |
---------- |
|
|
439 |
|
|
|
440 |
T: sp.Array of dimensions n x m x k |
|
|
441 |
|
|
|
442 |
v: sp.Array of dimensions k x 1 |
|
|
443 |
|
|
|
444 |
Returns |
|
|
445 |
------- |
|
|
446 |
|
|
|
447 |
A: sp.Array of dimensions n x m |
|
|
448 |
|
|
|
449 |
Example |
|
|
450 |
------- |
|
|
451 |
|
|
|
452 |
>>>T = sp.Array([[[1, 4, 7, 10], [2, 5, 8, 11], [3, 6, 9, 12]], |
|
|
453 |
[[13, 16, 19, 22], [14, 17, 20, 23], [15, 18, 21, 24]]]) |
|
|
454 |
⎡⎡1 4 7 10⎤ ⎡13 16 19 22⎤⎤ |
|
|
455 |
⎢⎢ ⎥ ⎢ ⎥⎥ |
|
|
456 |
⎢⎢2 5 8 11⎥ ⎢14 17 20 23⎥⎥ |
|
|
457 |
⎢⎢ ⎥ ⎢ ⎥⎥ |
|
|
458 |
⎣⎣3 6 9 12⎦ ⎣15 18 21 24⎦⎦ |
|
|
459 |
>>>v = sp.Array([1, 2, 3, 4]).reshape(4, 1) |
|
|
460 |
⎡1⎤ |
|
|
461 |
⎢ ⎥ |
|
|
462 |
⎢2⎥ |
|
|
463 |
⎢ ⎥ |
|
|
464 |
⎢3⎥ |
|
|
465 |
⎢ ⎥ |
|
|
466 |
⎣4⎦ |
|
|
467 |
>>>tensor3_vector_product(T, v) |
|
|
468 |
⎡⎡70⎤ ⎡190⎤⎤ |
|
|
469 |
⎢⎢ ⎥ ⎢ ⎥⎥ |
|
|
470 |
⎢⎢80⎥ ⎢200⎥⎥ |
|
|
471 |
⎢⎢ ⎥ ⎢ ⎥⎥ |
|
|
472 |
⎣⎣90⎦ ⎣210⎦⎦ |
|
|
473 |
|
|
|
474 |
""" |
|
|
475 |
import sympy as sp |
|
|
476 |
assert(T.rank() == 3) |
|
|
477 |
# reshape v to ensure 1D vector so that contraction do not contain x 1 |
|
|
478 |
# dimension |
|
|
479 |
v.reshape(v.shape[0], ) |
|
|
480 |
p = sp.tensorproduct(T, v) |
|
|
481 |
return sp.tensorcontraction(p, (2, 3)) |
|
|
482 |
|
|
|
483 |
|
|
|
484 |
def test_tensor_product(): |
|
|
485 |
T = sp.Array([[[1, 4, 7, 10], [2, 5, 8, 11], [3, 6, 9, 12]], |
|
|
486 |
[[13, 16, 19, 22], [14, 17, 20, 23], [15, 18, 21, 24]]]) |
|
|
487 |
v = sp.Array([1, 2, 3, 4]).reshape(4, 1) |
|
|
488 |
display(T, v) |
|
|
489 |
display(tensor3_vector_product(T, v)) |
|
|
490 |
|
|
|
491 |
|
|
|
492 |
# test_tensor_product() |
|
|
493 |
|
|
|
494 |
|
|
|
495 |
def draw_ellipse(ax, xc, A, scale=1.0, show_axis=False): |
|
|
496 |
"""Construct an ellipse representation of a 2x2 matrix. |
|
|
497 |
|
|
|
498 |
Parameters |
|
|
499 |
---------- |
|
|
500 |
ax: plot axis |
|
|
501 |
|
|
|
502 |
xc: np.array 2 x 1 |
|
|
503 |
center of the ellipse |
|
|
504 |
mat: np.array 2 x 2 |
|
|
505 |
|
|
|
506 |
scale: float (default=1.0) |
|
|
507 |
scale factor of the principle axes |
|
|
508 |
|
|
|
509 |
""" |
|
|
510 |
eigen_values, eigen_vectors = np.linalg.eig(A) |
|
|
511 |
idx = np.abs(eigen_values).argsort()[::-1] |
|
|
512 |
eigen_values = eigen_values[idx] |
|
|
513 |
eigen_vectors = eigen_vectors[:, idx] |
|
|
514 |
phi = np.rad2deg(np.arctan2(eigen_vectors[1, 0], eigen_vectors[0, 0])) |
|
|
515 |
|
|
|
516 |
ellipse = patches.Ellipse(xy=(xc[0, 0], xc[1, 0]), |
|
|
517 |
width=2 * scale * eigen_values[0], |
|
|
518 |
height=2 * scale * eigen_values[1], |
|
|
519 |
angle=phi, |
|
|
520 |
linewidth=2, fill=False) |
|
|
521 |
ax.add_patch(ellipse) |
|
|
522 |
|
|
|
523 |
# axis |
|
|
524 |
if show_axis: |
|
|
525 |
x_axis = np.array([[xc[0, 0], xc[1, 0]], |
|
|
526 |
[xc[0, 0] + scale * np.abs(eigen_values[0]) * eigen_vectors[0, 0], |
|
|
527 |
xc[1, 0] + scale * np.abs(eigen_values[0]) * eigen_vectors[1, 0]]]) |
|
|
528 |
y_axis = np.array([[xc[0, 0], xc[1, 0]], |
|
|
529 |
[xc[0, 0] + scale * eigen_values[1] * eigen_vectors[0, 1], |
|
|
530 |
xc[1, 0] + scale * eigen_values[1] * eigen_vectors[1, 1]]]) |
|
|
531 |
ax.plot(x_axis[:, 0], x_axis[:, 1], '-r', label='x-axis') |
|
|
532 |
ax.plot(y_axis[:, 0], y_axis[:, 1], '-g', label='y-axis') |
|
|
533 |
|
|
|
534 |
return phi, eigen_values, eigen_vectors |
|
|
535 |
|
|
|
536 |
|
|
|
537 |
def test_ellipse(): |
|
|
538 |
fig, ax = plt.subplots() |
|
|
539 |
xc = vec([0, 0]) |
|
|
540 |
M = mat([[2, 1], [1, 2]]) |
|
|
541 |
# M = mat([[-2.75032375, -11.82938331], [-11.82938331, -53.5627191]]) |
|
|
542 |
print np.linalg.matrix_rank(M) |
|
|
543 |
phi, l, v = draw_ellipse(ax, xc, M, 1, True) |
|
|
544 |
print(phi, l, v) |
|
|
545 |
ax.set_xlabel('x') |
|
|
546 |
ax.set_ylabel('y') |
|
|
547 |
ax.axis('equal') |
|
|
548 |
ax.legend() |
|
|
549 |
fig.show() |
|
|
550 |
|
|
|
551 |
|
|
|
552 |
# test_ellipse() |
|
|
553 |
|
|
|
554 |
|
|
|
555 |
def calculate_feasible_muscle_set(feasible_muscle_set_analysis, base_name, |
|
|
556 |
t_start, t_end, dt, speed): |
|
|
557 |
""" Calculates the feasible muscle space of a simulation. |
|
|
558 |
|
|
|
559 |
Parameters |
|
|
560 |
---------- |
|
|
561 |
|
|
|
562 |
feasible_muscle_set_analysis: FeasibleMuscleSetAnalysis |
|
|
563 |
|
|
|
564 |
base_name: base name of simulation files |
|
|
565 |
|
|
|
566 |
t_start: t start |
|
|
567 |
|
|
|
568 |
t_end: t end |
|
|
569 |
|
|
|
570 |
dt: time interval for reporting |
|
|
571 |
|
|
|
572 |
speed: speed of animation |
|
|
573 |
|
|
|
574 |
""" |
|
|
575 |
print('Calculating feasible muscle set ...') |
|
|
576 |
time = np.linspace(t_start, t_end, t_end / dt + 1, endpoint=True) |
|
|
577 |
for i, t in enumerate(tqdm(time)): |
|
|
578 |
visualize_feasible_muscle_set(feasible_muscle_set_analysis, t, |
|
|
579 |
base_name + str(i).zfill(6), 'png') |
|
|
580 |
|
|
|
581 |
command = 'convert -delay ' + \ |
|
|
582 |
str(speed * dt) + ' -loop 0 ' + base_name + \ |
|
|
583 |
'*.png ' + base_name + 'anim.gif' |
|
|
584 |
|
|
|
585 |
print(command) |
|
|
586 |
try: |
|
|
587 |
os.system(command) |
|
|
588 |
except: |
|
|
589 |
print('unable to execute command') |
|
|
590 |
|
|
|
591 |
|
|
|
592 |
def visualize_feasible_muscle_set(feasible_muscle_set_analysis, t, |
|
|
593 |
fig_name='fig/feasible_muscle_set', format='png'): |
|
|
594 |
""" Visualization of the feasible muscle space. |
|
|
595 |
|
|
|
596 |
Parameters |
|
|
597 |
---------- |
|
|
598 |
|
|
|
599 |
feasible_muscle_set_analysis: FeasibleMuscleSetAnalysis |
|
|
600 |
t: time instance to evaluate the feasible |
|
|
601 |
fig_name: figure name for saving |
|
|
602 |
format: format (e.g. .png, .pdf, .eps) |
|
|
603 |
|
|
|
604 |
""" |
|
|
605 |
fig, ax = plt.subplots(1, 3, figsize=(15, 5)) |
|
|
606 |
feasible_muscle_set_analysis.visualize_simple_muscle(t, ax) |
|
|
607 |
fig.suptitle('t = ' + str(np.around(t, decimals=2)), |
|
|
608 |
y=1.00, fontsize=12, fontweight='bold') |
|
|
609 |
fig.tight_layout() |
|
|
610 |
fig.savefig(fig_name + '.' + format, format=format, dpi=300) |
|
|
611 |
fig.savefig(fig_name + '.pdf', format='pdf', dpi=300) |
|
|
612 |
fig.savefig(fig_name + '.eps', format='eps', dpi=300) |
|
|
613 |
|
|
|
614 |
|
|
|
615 |
def apply_generalized_force(f): |
|
|
616 |
"""Applies a generalized force (f) in a manner that is consistent with Newton's |
|
|
617 |
3rd law. |
|
|
618 |
|
|
|
619 |
Parameters |
|
|
620 |
---------- |
|
|
621 |
f: generalized force |
|
|
622 |
|
|
|
623 |
""" |
|
|
624 |
n = len(f) |
|
|
625 |
tau = [] |
|
|
626 |
for i in range(0, n): |
|
|
627 |
if i == n - 1: |
|
|
628 |
tau.append(f[i]) |
|
|
629 |
else: |
|
|
630 |
tau.append(f[i] - f[i + 1]) |
|
|
631 |
|
|
|
632 |
return tau |
|
|
633 |
|
|
|
634 |
|
|
|
635 |
def custom_exponent(q, A, k, q_lim): |
|
|
636 |
""" Sympy representation of custom exponent function. |
|
|
637 |
|
|
|
638 |
f(q) = A e^(k (q - q_lim)) / (150) ** k |
|
|
639 |
|
|
|
640 |
""" |
|
|
641 |
return A * sp.exp(k * (q - q_lim)) / (148.42) ** k |
|
|
642 |
|
|
|
643 |
|
|
|
644 |
def coordinate_limiting_force(q, q_low, q_up, a, b): |
|
|
645 |
"""A continuous coordinate limiting force for a rotational joint. |
|
|
646 |
|
|
|
647 |
It applies an exponential force when approximating a limit. The convention |
|
|
648 |
is that positive force is generated when approaching the lower limit and |
|
|
649 |
negative when approaching the upper. For a = 1, F ~= 1N at the limits. |
|
|
650 |
|
|
|
651 |
Parameters |
|
|
652 |
---------- |
|
|
653 |
q: generalized coordinate |
|
|
654 |
q_low: lower limit |
|
|
655 |
q_up: upper limit |
|
|
656 |
a: force at limits |
|
|
657 |
b: rate of the slop |
|
|
658 |
|
|
|
659 |
Note: q, q_low, q_up must have the same units (e.g. rad) |
|
|
660 |
|
|
|
661 |
""" |
|
|
662 |
return custom_exponent(q_low + 5, a, b, q) - custom_exponent(q, a, b, q_up - 5) |
|
|
663 |
|
|
|
664 |
|
|
|
665 |
def test_limiting_force(): |
|
|
666 |
""" |
|
|
667 |
""" |
|
|
668 |
q = np.linspace(0, np.pi / 4, 100, endpoint=True) |
|
|
669 |
f = [coordinate_limiting_force(qq, 0, np.pi / 4, 1, 50) for qq in q] |
|
|
670 |
|
|
|
671 |
plt.plot(q, np.array(f)) |
|
|
672 |
plt.show() |
|
|
673 |
|
|
|
674 |
|
|
|
675 |
def gaussian(x, a, m, s): |
|
|
676 |
"""Gaussian function. |
|
|
677 |
|
|
|
678 |
f(x) = a e^(-(x - m)^2 / (2 s ^2)) |
|
|
679 |
|
|
|
680 |
Parameters |
|
|
681 |
---------- |
|
|
682 |
x: x |
|
|
683 |
a: peak |
|
|
684 |
m: mean |
|
|
685 |
s: standard deviation |
|
|
686 |
|
|
|
687 |
For a good approximation of an impulse at t = 0.3 [x, 1, 0.3, 0.01]. |
|
|
688 |
|
|
|
689 |
""" |
|
|
690 |
return a * np.exp(- (x - m) ** 2 / (2 * s ** 2)) |
|
|
691 |
|
|
|
692 |
|
|
|
693 |
def test_gaussian(): |
|
|
694 |
""" |
|
|
695 |
""" |
|
|
696 |
t = np.linspace(0, 2, 200) |
|
|
697 |
y = [gaussian(tt, 0.4, 0.4, 0.01) for tt in t] |
|
|
698 |
plt.plot(t, y) |
|
|
699 |
plt.show() |
|
|
700 |
|
|
|
701 |
|
|
|
702 |
def rotate(origin, point, angle): |
|
|
703 |
"""Rotate a point counterclockwise by a given angle around a given origin. |
|
|
704 |
|
|
|
705 |
The angle should be given in radians. |
|
|
706 |
|
|
|
707 |
""" |
|
|
708 |
R = np.asmatrix([[np.cos(angle), - np.sin(angle)], |
|
|
709 |
[np.sin(angle), np.cos(angle)]]) |
|
|
710 |
|
|
|
711 |
q = origin + R * (point - origin) |
|
|
712 |
|
|
|
713 |
return q |
|
|
714 |
|
|
|
715 |
|
|
|
716 |
def sigmoid(t, t0, A, B): |
|
|
717 |
"""Implementation of smooth sigmoid function. |
|
|
718 |
|
|
|
719 |
Parameters |
|
|
720 |
---------- |
|
|
721 |
t: time to be evalutaed |
|
|
722 |
t0: delay |
|
|
723 |
A: magnitude |
|
|
724 |
B: slope |
|
|
725 |
|
|
|
726 |
Returns |
|
|
727 |
------- |
|
|
728 |
(y, y', y'') |
|
|
729 |
|
|
|
730 |
""" |
|
|
731 |
return (A * (np.tanh(B * (t - t0)) + 1) / 2, |
|
|
732 |
A * B * (- np.tanh(B * (t - t0)) ** 2 + 1) / 2, |
|
|
733 |
- A * B ** 2 * (- np.tanh(B * (t - t0)) ** 2 + 1) * np.tanh(B * (t - t0))) |
|
|
734 |
|
|
|
735 |
|
|
|
736 |
def test_sigmoid(): |
|
|
737 |
""" |
|
|
738 |
""" |
|
|
739 |
t, A, B, t0 = sp.symbols('t A B t0') |
|
|
740 |
y = A / 2 * (sp.tanh(B * (t - t0 - 1)) + 1) |
|
|
741 |
yd = sp.diff(y, t) |
|
|
742 |
ydd = sp.diff(yd, t) |
|
|
743 |
print('\n', y, '\n', yd, '\n', ydd) |
|
|
744 |
|
|
|
745 |
tt = np.linspace(-2, 2, 100) |
|
|
746 |
yy = np.array([sigmoid(x, 0.5, 2, 5) for x in tt]) |
|
|
747 |
plt.plot(tt, yy) |
|
|
748 |
plt.show() |
|
|
749 |
|
|
|
750 |
|
|
|
751 |
def plot_corr_ellipses(data, ax=None, **kwargs): |
|
|
752 |
"""For a given correlation matrix "data", plot the correlation matrix in terms |
|
|
753 |
of ellipses. |
|
|
754 |
|
|
|
755 |
parameters |
|
|
756 |
---------- |
|
|
757 |
data: Pandas dataframe containing the correlation of the data (df.corr()) |
|
|
758 |
ax: axis (e.g. fig, ax = plt.subplots(1, 1)) |
|
|
759 |
kwards: keywords arguments (cmap="Greens") |
|
|
760 |
|
|
|
761 |
https://stackoverflow.com/questions/34556180/ |
|
|
762 |
how-can-i-plot-a-correlation-matrix-as-a-set-of-ellipses-similar-to-the-r-open |
|
|
763 |
|
|
|
764 |
""" |
|
|
765 |
M = np.array(data) |
|
|
766 |
if not M.ndim == 2: |
|
|
767 |
raise ValueError('data must be a 2D array') |
|
|
768 |
if ax is None: |
|
|
769 |
fig, ax = plt.subplots(1, 1, subplot_kw={'aspect': 'equal'}) |
|
|
770 |
ax.set_xlim(-0.5, M.shape[1] - 0.5) |
|
|
771 |
ax.set_ylim(-0.5, M.shape[0] - 0.5) |
|
|
772 |
|
|
|
773 |
# xy locations of each ellipse center |
|
|
774 |
xy = np.indices(M.shape)[::-1].reshape(2, -1).T |
|
|
775 |
|
|
|
776 |
# set the relative sizes of the major/minor axes according to the strength of |
|
|
777 |
# the positive/negative correlation |
|
|
778 |
w = np.ones_like(M).ravel() |
|
|
779 |
h = 1 - np.abs(M).ravel() |
|
|
780 |
a = 45 * np.sign(M).ravel() |
|
|
781 |
|
|
|
782 |
ec = EllipseCollection(widths=w, heights=h, angles=a, units='x', offsets=xy, |
|
|
783 |
transOffset=ax.transData, array=M.ravel(), **kwargs) |
|
|
784 |
ax.add_collection(ec) |
|
|
785 |
|
|
|
786 |
# if data is a DataFrame, use the row/column names as tick labels |
|
|
787 |
if isinstance(data, pd.DataFrame): |
|
|
788 |
ax.set_xticks(np.arange(M.shape[1])) |
|
|
789 |
ax.set_xticklabels(data.columns, rotation=90) |
|
|
790 |
ax.set_yticks(np.arange(M.shape[0])) |
|
|
791 |
ax.set_yticklabels(data.index) |
|
|
792 |
|
|
|
793 |
return ec |
|
|
794 |
|
|
|
795 |
|
|
|
796 |
def get_cmap(n, name='hsv'): |
|
|
797 |
"""Returns a function that maps each index in 0, 1, ..., n-1 to a distinct RGB |
|
|
798 |
color; the keyword argument name must be a standard mpl colormap name. |
|
|
799 |
|
|
|
800 |
""" |
|
|
801 |
return plt.cm.get_cmap(name, n) |
|
|
802 |
|
|
|
803 |
|
|
|
804 |
def assert_if_same(A, B): |
|
|
805 |
"""Assert whether two quantities (value, vector, matrix) are the same.""" |
|
|
806 |
assert np.isclose( |
|
|
807 |
np.array(A).astype(np.float64), |
|
|
808 |
np.array(B).astype(np.float64)).all() == True, 'quantities not equal' |
|
|
809 |
|
|
|
810 |
|
|
|
811 |
def christoffel_symbols(M, q, i, j, k): |
|
|
812 |
""" |
|
|
813 |
M [n x n]: inertia mass matrix |
|
|
814 |
q [n x 1]: generalized coordinates |
|
|
815 |
i, j, k : the indexies to be computed |
|
|
816 |
""" |
|
|
817 |
return sp.Rational(1, 2) * (sp.diff(M[i, j], q[k]) + sp.diff(M[i, k], q[j]) - |
|
|
818 |
sp.diff(M[k, j], q[i])) |
|
|
819 |
|
|
|
820 |
|
|
|
821 |
def coriolis_matrix(M, q, dq): |
|
|
822 |
""" |
|
|
823 |
Coriolis matrix C(q, qdot) [n x n] |
|
|
824 |
Coriolis forces are computed as C(q, qdot) * qdot [n x 1] |
|
|
825 |
""" |
|
|
826 |
n = M.shape[0] |
|
|
827 |
C = sp.zeros(n, n) |
|
|
828 |
for i in range(0, n): |
|
|
829 |
for j in range(0, n): |
|
|
830 |
for k in range(0, n): |
|
|
831 |
C[i, j] = C[i, j] + christoffel_symbols(M, q, i, j, k) * dq[k] |
|
|
832 |
return C |
|
|
833 |
|
|
|
834 |
|
|
|
835 |
def substitute(symbols, constants): |
|
|
836 |
"""For a given symbolic sequence substitute symbols.""" |
|
|
837 |
return np.array([substitute(exp, constants) |
|
|
838 |
if hasattr(exp, '__iter__') |
|
|
839 |
else exp.subs(constants) for exp in symbols]) |