Diff of /arm_model/analysis.py [000000] .. [794894]

Switch to unified view

a b/arm_model/analysis.py
1
import itertools
2
import numpy as np
3
import pylab as plt
4
import matplotlib as mpl
5
# from mpl_toolkits.mplot3d import Axes3D
6
import seaborn as sns
7
import pandas as pd
8
from util import to_np_mat, to_np_array, plot_corr_ellipses, draw_ellipse, \
9
    convex_bounded_vertex_enumeration, nullspace
10
from logger import Logger
11
12
13
# ------------------------------------------------------------------------
14
# FeasibleMuscleSetAnalysis
15
# ------------------------------------------------------------------------
16
17
def construct_muscle_space_inequality(NR, fm_par, Fmax):
18
    """Construct the feasible muscle space Z f_m0 <= B .
19
20
    Parameters
21
    ----------
22
23
    NR: moment arm null space matrix
24
25
    fm_par: particular muscle forces
26
27
    Fmax: maximum muscle force
28
29
    """
30
    Z0 = -NR
31
    Z1 = NR
32
    B0 = fm_par
33
    B1 = np.asmatrix(np.diag(Fmax)).reshape(Fmax.shape[0], 1) - fm_par
34
    Z = np.concatenate((Z0, Z1), axis=0)
35
    B = np.concatenate((B0, B1), axis=0)
36
    return Z, B
37
38
39
class FeasibleMuscleSetAnalysis:
40
    """Feasible muscle set analysis.
41
42
    The required command along with the state of the system are recorded. Then
43
    this information is used to compute the feasible muscle null space and
44
    visualize it.
45
46
    """
47
48
    def __init__(self, model, simulation_reporter):
49
        """
50
        """
51
        self.logger = Logger('FeasibleMuscleSetAnalsysis')
52
        self.model = model
53
        self.simulation_reporter = simulation_reporter
54
55
    def visualize_simple_muscle(self, t, ax=None):
56
        """Visualize the feasible force set at a particular time instance for a linear
57
        muscle.
58
59
        Parameters
60
        ----------
61
62
        t: time
63
64
        ax: 1 x 3 axis
65
66
        """
67
        m = self.model.md
68
        q, Z, B, NR, fm_par = self.calculate_simple_muscles(t)
69
        x_max = np.max(to_np_mat(self.model.Fmax))
70
        fm_set = self.generate_solutions(Z, B, NR, fm_par)
71
        dataframe = pd.DataFrame(fm_set, columns=['$m_' + str(i) + '$' for i in
72
                                                  range(1, m + 1)])
73
74
        # box plot
75
        if ax is None or ax.shape[0] < 3:
76
            fig, ax = plt.subplots(1, 3, figsize=(15, 5))
77
78
        # box plot
79
        dataframe.plot.box(ax=ax[0])
80
        ax[0].set_xlabel('muscle id')
81
        ax[0].set_ylabel('force $(N)$')
82
        ax[0].set_title('Muscle-Force Box Plot')
83
        ax[0].set_ylim([0, 1.1 * x_max])
84
85
        # correlation matrix
86
        cmap = mpl.cm.jet
87
        norm = mpl.colors.Normalize(vmin=-1, vmax=1)
88
        corr = dataframe.corr()
89
        m = plot_corr_ellipses(corr, ax=ax[1], norm=norm, cmap=cmap)
90
        cb = plt.colorbar(m, ax=ax[1], orientation='vertical', norm=norm,
91
                          cmap=cmap)
92
        cb.set_label('Correlation Coefficient')
93
        ax[1].margins(0.1)
94
        ax[1].set_xlabel('muscle id')
95
        ax[1].set_ylabel('muscle id')
96
        ax[1].set_title('Correlation Matrix')
97
        ax[1].axis('equal')
98
99
        # draw model
100
        self.model.draw_model(q, False, ax[2], scale=0.7, text=False)
101
102
    def calculate_simple_muscles(self, t):
103
        """Construct Z f_m0 <= B for the case of a linear muscle model for a particular
104
        time instance.
105
106
        Parameters
107
        ----------
108
109
        t: time
110
111
        """
112
        # find nearesrt index corresponding to t
113
        idx = np.abs(np.array(self.simulation_reporter.t) - t).argmin()
114
        t = self.simulation_reporter.t[idx]
115
        q = self.simulation_reporter.q[idx]
116
        u = self.simulation_reporter.u[idx]
117
        tau = self.simulation_reporter.tau[idx]
118
        pose = self.model.model_parameters(q=q, u=u)
119
        n = self.model.nd
120
121
        # calculate required variables
122
        R = to_np_mat(self.model.R.subs(pose))
123
        RBarT = np.asmatrix(np.linalg.pinv(R.T))
124
        # reduce to independent columns to avoid singularities (proposition 3)
125
        NR = nullspace(R.transpose())
126
        fm_par = np.asmatrix(-RBarT * tau.reshape((n, 1)))
127
        Fmax = to_np_mat(self.model.Fmax)
128
129
        Z, B = construct_muscle_space_inequality(NR, fm_par, Fmax)
130
131
        return q, Z, B, NR, fm_par
132
133
    def generate_solutions(self, A, b, NR, fm_par):
134
        """Sample the solution space that satisfy A x <= b.
135
136
        Parameters
137
        ----------
138
139
        A: matrix A
140
141
        b: column vector
142
143
        NR: moment arm nullspace
144
145
        fm_par: particular solution
146
147
        Returns
148
        -------
149
150
        muscle forces: a set of solutions that satisfy the problem
151
152
        """
153
        feasible_set = []
154
        fm0_set = convex_bounded_vertex_enumeration(np.array(A),
155
                                                    np.array(b).flatten(), 0)
156
        n = fm0_set.shape[0]
157
        for i in range(0, n):
158
            fm = fm_par + NR * np.matrix(fm0_set[i, :]).reshape(-1, 1)
159
            feasible_set.append(fm)
160
161
        return np.array(feasible_set).reshape(n, -1)
162
163
164
def test_feasible_set(model):
165
    feasible_set = FeasibleMuscleSetAnalysis(model)
166
    n = model.nd
167
    m = model.md
168
    feasible_set.record(1,
169
                        np.random.random((m, 1)),
170
                        np.random.random((m, m)),
171
                        np.random.random((n, 1)))
172
    fig, ax = plt.subplots(2, 3, figsize=(10, 10))
173
    feasible_set.visualize_simple_muscle(1, ax[0])
174
    feasible_set.visualize_simple_muscle(1, ax[1])
175
    plt.show()
176
177
# ------------------------------------------------------------------------
178
# StiffnessAnalysis
179
# ------------------------------------------------------------------------
180
181
182
class StiffnessAnalysis:
183
    """Stiffness analysis.
184
185
    """
186
187
    def __init__(self, model, task, simulation_reporter,
188
                 feasible_muscle_set_analysis):
189
        """Constructor.
190
191
        Parameters
192
        ----------
193
194
        model: ArmModel
195
196
        task: TaskSpace
197
198
        simulation_reporter: SimulationReporter
199
200
        feasible_muscle_set_analysis: FeasibleMuscleSetAnalysis
201
202
        """
203
        self.logger = Logger('StiffnessAnalysis')
204
        self.model = model
205
        self.task = task
206
        self.simulation_reporter = simulation_reporter
207
        self.feasible_muscle_set_analysis = feasible_muscle_set_analysis
208
        self.dataframe = pd.DataFrame()
209
        self.color = itertools.cycle(('r', 'g', 'b'))
210
        self.marker = itertools.cycle(('o', '+', '*'))
211
        self.linestyle = itertools.cycle(('-', '--', ':'))
212
        self.hatch = itertools.cycle(('//', '\\', 'x'))
213
214
    def visualize_stiffness_properties(self, t, calc_feasible_stiffness,
215
                                       scale_factor, alpha, ax,
216
                                       axis_limits=None):
217
        """Visualize task stiffness ellipse.
218
219
        Parameters
220
        ----------
221
222
        t: float
223
            time
224
225
        calc_feasible_stiffness: bool
226
            whether to calculate the feasible stiffness
227
228
        scale_factor: float
229
            ellipse scale factor
230
231
        alpha: float
232
            alpha value for drawing the model
233
234
        ax: matplotlib
235
236
        axis_limits: list of pairs
237
            axis limits for the 3D plot
238
239
        """
240
        at, q, xc, Km, Kj, Kt = self.calculate_stiffness_properties(t,
241
                                                                    calc_feasible_stiffness)
242
243
        # ellipse
244
        self.model.draw_model(q, False, ax[0], 1, True, alpha, False)
245
        phi = []
246
        eigen_values = []
247
        area = []
248
        eccentricity = []
249
        for K in Kt:
250
            phi_temp, eigen_values_temp, v = draw_ellipse(ax[0], xc, K,
251
                                                          scale_factor,
252
                                                          True)
253
            axes_length = np.abs(eigen_values_temp).flatten()
254
            idx = axes_length.argsort()[::-1]
255
            eccentricity_temp = np.sqrt(1 - axes_length[idx[1]]**2
256
                                        / axes_length[idx[0]]**2)
257
            area_temp = scale_factor ** 2 * np.pi * axes_length[0] * axes_length[1]
258
259
            if phi_temp < 0:
260
                phi_temp = phi_temp + 180
261
262
            phi.append(phi_temp)
263
            eigen_values.append(eigen_values_temp)
264
            area.append(area_temp)
265
            eccentricity.append(eccentricity_temp)
266
267
        ax[0].set_xlabel('x $(m)$')
268
        ax[0].set_ylabel('y $(m)$')
269
        ax[0].set_title('Task Stiffness Ellipses')
270
271
        # ellipse properties
272
        if axis_limits is not None:
273
            ax[1].set_xlim(axis_limits[0][0], axis_limits[0][1])
274
            # ax[1].set_ylim(axis_limits[1][0], axis_limits[1][1])
275
            ax[1].set_ylim(axis_limits[2][0], axis_limits[2][1])
276
            for i in range(0, len(area)):  # remove outliers
277
                if area[i] < axis_limits[0][0] or area[i] > axis_limits[0][1] or \
278
                   eccentricity[i] < axis_limits[1][0] or eccentricity[i] > axis_limits[1][1] or \
279
                   phi[i] < axis_limits[2][0] or phi[i] > axis_limits[2][1]:
280
                    area[i] = np.NaN
281
                    eccentricity[i] = np.NaN
282
                    phi[i] = np.NaN
283
284
285
        # color_cycle = ax[1]._get_lines.prop_cycler
286
        # color = next(color_cycle)['color']
287
        color = self.color.next()
288
        marker = self.marker.next()
289
        linestyle = self.linestyle.next()
290
        data = pd.DataFrame()
291
        data['area'] = area
292
        data['phi'] = phi
293
        sns.distplot(data['area'].dropna(), kde=True, rug=False, hist=False, vertical=False,
294
                     color=color, kde_kws={'linestyle': linestyle}, ax=ax[1])
295
        sns.distplot(data['phi'].dropna(), kde=True, rug=False, hist=False, vertical=True,
296
                     color=color, kde_kws={'linestyle': linestyle}, ax=ax[1])
297
        ax[1].scatter(area, phi, label=str(t) + 's', color=color, marker=marker)
298
        ax[1].set_xlabel('area $(m^2)$')
299
        # ax[1].set_ylabel('$\epsilon$')
300
        ax[1].set_ylabel('$\phi (deg)$')
301
        ax[1].set_title('Task Stiffness Properties')
302
        ax[1].legend()
303
304
        # joint stiffness
305
        Kj_temp = [np.abs(kj.diagonal()).reshape(-1, 1) for kj in Kj]
306
        Kj_temp = np.array(Kj_temp).reshape(len(Kj_temp), -1)
307
        n = self.model.nd
308
        current_df = pd.DataFrame(Kj_temp, columns=['$J_' + str(i) + '$'
309
                                                    for i in range(1, n + 1)])
310
        current_df['Time'] = t
311
        if not self.dataframe.empty:
312
            self.dataframe = self.dataframe.append(current_df)
313
        else:
314
            self.dataframe = current_df
315
316
        ax[2].clear()
317
        boxplot = sns.boxplot(x='Time', y='Stiffness', hue='Joint',
318
                    data=self.dataframe.set_index('Time', append=True)
319
                    .stack()
320
                    .to_frame()
321
                    .reset_index()
322
                    .rename(columns={'level_2': 'Joint', 0: 'Stiffness'})
323
                    .drop('level_0', axis='columns'), ax=ax[2])
324
        for b in boxplot.artists:
325
            b.set_hatch(self.hatch.next())
326
327
        boxplot.legend()
328
        ax[2].set_xlabel('time $(s)$')
329
        ax[2].set_ylabel('joint stiffness $(Nm / rad)$')
330
        ax[2].set_title('Joint Stiffness')
331
        ax[2].set_ylim([0, 50])
332
333
334
    def calculate_stiffness_properties(self, t, calc_feasible_stiffness=False):
335
        """Calculates the stiffness properties of the model at a particular time
336
        instance.
337
338
        Parameters
339
        ----------
340
341
        t: float
342
            time of interest
343
344
        calc_feasible_stiffness: bool
345
            whether to calculate the feasible stiffness
346
347
        Returns
348
        -------
349
350
        t: float
351
            actual time (closest to recorded values, not interpolated)
352
353
        q: mat n x 1 (mat = numpy.matrix)
354
            generalized coordinates
355
356
        xc: mat d x 1
357
            position of the task
358
359
        Km: mat m x m
360
            muscle space stiffness
361
362
        Kj: mat n x n
363
            joint space stiffness
364
365
        Kt: mat d x d
366
            task space stiffness
367
368
        """
369
        # find nearesrt index corresponding to t
370
        idx = np.abs(np.array(self.simulation_reporter.t) - t).argmin()
371
        t = self.simulation_reporter.t[idx]
372
        q = self.simulation_reporter.q[idx]
373
        u = self.simulation_reporter.u[idx]
374
        fm = self.simulation_reporter.fm[idx]
375
        ft = self.simulation_reporter.ft[idx]
376
        pose = self.model.model_parameters(q=q, u=u)
377
378
        # calculate required variables
379
        R = to_np_mat(self.model.R.subs(pose))
380
        RT = R.transpose()
381
        RTDq = to_np_array(self.model.RTDq.subs(pose))
382
        Jt = to_np_mat(self.task.Jt.subs(pose))
383
        JtPInv = np.linalg.pinv(Jt)
384
        JtTPInv = JtPInv.transpose()
385
        JtTDq = to_np_array(self.task.JtTDq.subs(pose))
386
        xc = to_np_mat(self.task.x(pose))
387
388
        # calculate feasible muscle forces
389
        if calc_feasible_stiffness:
390
            q, Z, B, NR, fm_par = self.feasible_muscle_set_analysis\
391
                                      .calculate_simple_muscles(t)
392
            fm_set = self.feasible_muscle_set_analysis.generate_solutions(Z, B,
393
                                                                          NR,
394
                                                                          fm_par)
395
396
        # calculate stiffness properties
397
        Km = []
398
        Kj = []
399
        Kt = []
400
        for i in range(0, fm_set.shape[0]):  # , fm_set.shape[0] / 500):
401
            if calc_feasible_stiffness:
402
                fm = fm_set[i, :]
403
404
            # calculate muscle stiffness from sort range stiffness (ignores
405
            # tendon stiffness)
406
            gamma = 23.5
407
            Km.append(np.asmatrix(np.diag([gamma * fm[i] / self.model.lm0[i]
408
                                           for i in
409
                                           range(0, self.model.lm0.shape[0])]),
410
                                  np.float))
411
412
            # switches for taking into account (=1) the tensor products
413
            dynamic = 1.0
414
            static = 1.0
415
416
            # transpose is required in the tensor product because n(dq) x n(q) x
417
            # d(t) and we need n(q) x n(dq)
418
419
            # calculate joint stiffness
420
            RTDqfm = np.matmul(RTDq, fm)
421
            Kj.append(-dynamic * RTDqfm.T - static * RT * Km[-1] * R)
422
423
            # calculate task stiffness
424
            JtTDqft = np.matmul(JtTDq, ft)
425
            Kt.append(JtTPInv * (Kj[-1] - dynamic * JtTDqft.T) * JtPInv)
426
427
        return t, q, xc, Km, Kj, Kt