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

Switch to unified view

a b/arm_model/controller.py
1
import numpy as np
2
import sympy as sp
3
from logger import Logger
4
from scipy.optimize import minimize
5
from util import to_np_mat, to_np_vec, sigmoid, rotate, gaussian
6
from simulation import SimulationReporter
7
from delay import DelayArray
8
9
10
# ------------------------------------------------------------------------
11
# PID
12
# ------------------------------------------------------------------------
13
14
15
class PD:
16
    """Proportional Derivative Controller.
17
18
    a = ad + Kp (xd - x) + Kd (ud - u)
19
20
    """
21
22
    def __init__(self, Kp, Kd):
23
        """
24
        Parameters
25
        ----------
26
27
        Kp: proportional gain
28
29
        Kd: derivative gain
30
31
        """
32
33
        self.Kp = Kp
34
        self.Kd = Kd
35
        self.t = 0
36
37
    def compute(self, x, u, xd, ud, ad):
38
        """
39
        Parameters
40
        ----------
41
42
        x: current position
43
        u: current velocity
44
        xd: desired position
45
        ud: desired velocity
46
        ad: desired acceleration
47
48
        Returns
49
        -------
50
51
        the requred acceleration
52
53
        """
54
55
        return ad + self.Kp * (xd - x) + self.Kd * (ud - u)
56
57
58
class PID:
59
    """
60
    An implementation of Proportional Integral Derivative controller.
61
62
    e = xd - x
63
    u = Kp e + Ki \int_0^t e d\tau + Kd de/dt
64
65
    """
66
67
    def __init__(self, Kp, Ki, Kd):
68
        """
69
        Parameters
70
        ----------
71
72
        Kp: proportional gain
73
        Ki: integral gain
74
        Kd: derivative gain
75
76
        """
77
78
        self.Kp = Kp
79
        self.Ki = Ki
80
        self.Kd = Kd
81
        self.t = 0
82
        self.error_sum = 0
83
84
    def compute(self, t, x, u, xd, ud):
85
        """
86
        Parameters
87
        ----------
88
89
        x: current position
90
        u: current velocity
91
        xd: desired position
92
        ud: desired velocity
93
94
        Returns
95
        -------
96
97
        the requred acceleration
98
99
        Note: TODO if the numerical integration goes backward in time then this
100
        may cause instabilities
101
102
        """
103
104
        dt = np.abs(self.t - t)
105
106
        error = xd - x
107
        error_d = ud - u
108
        self.error_sum = self.error_sum + error * dt
109
        self.t = t
110
111
        return self.Kp * error + self.Kd * error_d + self.Ki * self.error_sum
112
113
114
# ------------------------------------------------------------------------
115
# JointSpaceController
116
# ------------------------------------------------------------------------
117
118
119
class JointSpaceController:
120
    """Simple joint space tracking controller.
121
122
    """
123
124
    def __init__(self, model):
125
        """ Constructor.
126
127
        Parameters
128
        ----------
129
130
        model: reference to the model
131
132
        """
133
        self.logger = Logger('JointSpaceController')
134
        self.model = model
135
        self.pid = PID(10, 2, 2)
136
        self.reporter = SimulationReporter(model)
137
138
    def controller(self, x, t, x0):
139
        """Default simple joint space PD controller.
140
141
        Parameters
142
        ----------
143
144
        x: state
145
        t: time
146
        x0: initial state
147
148
        """
149
        # self.logger.debug('Time: ' + str(t))
150
        n = self.model.nd
151
        q = np.array(x[:n])
152
        u = np.array(x[n:])
153
        qd = np.array(self.target(x, t, x0))
154
        ud = np.zeros((n))
155
        tau = np.array(self.pid.compute(t, q, u, qd, ud))
156
157
        # record
158
        self.reporter.t.append(t)
159
        self.reporter.q.append(q)
160
        self.reporter.qd.append(qd)
161
        self.reporter.tau.append(tau)
162
        # self.fs_reporter.record(t, q, u, tau.reshape((n, 1)))
163
164
        return tau
165
166
    def target(self, x, t, x0):
167
        """Default joint space target function.
168
169
        Parameters
170
        ----------
171
172
        x: state
173
        t: time
174
        x0: initial state
175
176
        """
177
        return [np.deg2rad(130), np.deg2rad(60), np.deg2rad(95)]
178
179
# ------------------------------------------------------------------------
180
# TaskSpaceController
181
# ------------------------------------------------------------------------
182
183
184
class TaskSpaceController:
185
    """A task space controller that moves the task goal in a direction.
186
187
    """
188
189
    def __init__(self, model, task, angle=0, evaluate_muscle_forces=False):
190
        """Constructor.
191
192
        Parameters
193
        ----------
194
195
        model: Model
196
            a reference to the model
197
198
        task: TaskSpace
199
            a reference to TaskSpace
200
201
        angle: rad (default=0)
202
            direction to move the task
203
204
        evaluate_muscle_forces: Boolean (default=False)
205
            compute muscle forces that satisfy the task constraint
206
207
        """
208
        self.logger = Logger('TaskSpaceController')
209
        self.model = model
210
        self.task = task
211
        self.angle = angle
212
        self.evaluate_muscle_forces = evaluate_muscle_forces
213
        self.pd = PD(50, 5)
214
        self.reporter = SimulationReporter(model)
215
216
    def controller(self, x, t, x0):
217
        """Controller function.
218
219
        Parameters
220
        ----------
221
222
        x: state
223
        t: time
224
        x0: initial state
225
226
        """
227
        self.logger.debug('Time: ' + str(t))
228
        n = self.model.nd
229
        q = np.array(x[:n])
230
        u = np.array(x[n:])
231
        pose = self.model.model_parameters(q=q, u=u)
232
233
        # task variables
234
        xc = to_np_mat(self.task.x(pose))
235
        uc = to_np_mat(self.task.u(pose, u))
236
        xd, ud, ad = self.target(x, t, x0)
237
        ad = sp.Matrix(self.pd.compute(xc, uc, xd, ud, ad))
238
239
        # forces
240
        tau, ft = self.task.calculate_force(ad, pose)
241
        ft = to_np_vec(ft)
242
        tau = to_np_vec(tau)
243
244
        # solve static optimization
245
        fm = None
246
        if self.evaluate_muscle_forces:
247
            m = self.model.md
248
            R = to_np_mat(self.model.R.subs(pose))
249
            RT = R.transpose()
250
251
            def objective(x):
252
                return np.sum(x**2)
253
254
            def inequality_constraint(x):
255
                return np.array(tau + RT * (x.reshape(-1, 1))).reshape(-1,)
256
257
            x0 = np.zeros(m)
258
            bounds = tuple([(0, self.model.Fmax[i, i]) for i in range(0, m)])
259
            constraints = ({'type': 'ineq', 'fun': inequality_constraint})
260
            sol = minimize(objective, x0,  method='SLSQP',
261
                           bounds=bounds,
262
                           constraints=constraints)
263
            if sol.success is False:
264
                raise Exception('Static optimization failed at: ' + t)
265
266
            fm = sol.x.reshape(-1,)
267
268
        # record
269
        self.reporter.t.append(t)
270
        self.reporter.q.append(q)
271
        self.reporter.u.append(u)
272
        self.reporter.x.append(np.array(xc).reshape(-1,))
273
        self.reporter.xd.append(np.array(xd).reshape(-1,))
274
        self.reporter.tau.append(tau)
275
        self.reporter.ft.append(ft)
276
        self.reporter.fm.append(fm)
277
        # self.fs_reporter.record(t, q, u, tau)
278
279
        return tau
280
281
    def target(self, x, t, x0):
282
        """ A directed sigmoid function target.
283
284
        Parameters
285
        ----------
286
287
        x: state
288
        t: time
289
        x0: initial state
290
291
        Returns
292
        -------
293
294
        (x, u, a)
295
296
        """
297
        pose0 = self.model.model_parameters(q=x0[:self.model.nd])
298
        xt0 = self.task.x(pose0)
299
300
        t0 = 1.0
301
        A = 0.3
302
        B = 4
303
        o = np.asmatrix([[0], [0]])
304
        xd, ud, ad = sigmoid(t, t0, A, B)
305
306
        xd = rotate(o, np.asmatrix([[xd], [0]]), self.angle)
307
        ud = rotate(o, np.asmatrix([[ud], [0]]), self.angle)
308
        ad = rotate(o, np.asmatrix([[ad], [0]]), self.angle)
309
310
        return (np.asmatrix(xt0 + xd),
311
                np.asmatrix(ud),
312
                np.asmatrix(ad))
313
314
315
# ------------------------------------------------------------------------
316
# MuscleSpaceControllerJS
317
# ------------------------------------------------------------------------
318
319
320
class MuscleSpaceControllerJS:
321
    """This controller uses the muscle space EoM to driving a model from a
322
    reference pose to a desired pose in joint space.
323
324
    """
325
326
    def __init__(self, model, musclespace):
327
        """Constructor.
328
329
        Parameters
330
        ----------
331
332
        model: a reference to the model
333
        musclespace: a reference to MuscleSpace
334
335
        """
336
        self.logger = Logger('MsucleSpaceControllerJS')
337
        self.model = model
338
        self.musclespace = musclespace
339
        self.pid = PID(10, 0, 10)
340
        self.reporter = SimulationReporter(model)
341
342
    def controller(self, x, t, x0):
343
        """Default simple joint space PD controller.
344
345
        Parameters
346
        ----------
347
348
        x: state
349
        t: time
350
        x0: initial state
351
352
        """
353
        self.logger.debug('Time: ' + str(t))
354
        n = self.model.nd
355
        m = self.model.md
356
        q = np.array(x[:n])
357
        u = np.array(x[n:])
358
        qd = np.array(self.target(x, t, x0))
359
        ud = np.zeros((n))
360
        qddot = np.array(self.pid.compute(t, q, u, qd, ud))
361
362
        # evalutate desired lmdd_d
363
        pose = self.model.model_parameters(q=q, u=u)
364
        RDotQDot = self.model.RDotQDot.subs(pose)
365
        RQDDot = self.model.R.subs(pose) * sp.Matrix(qddot)
366
        lmdd_d = sp.Matrix(RDotQDot + RQDDot)
367
        lmd = to_np_vec(self.model.lmd.subs(pose))
368
        lmd_d = to_np_vec(self.model.R.subs(pose) * sp.Matrix(u))
369
370
        tau = self.musclespace.calculate_force(lmdd_d, pose)[0]
371
        tau = to_np_vec(tau)
372
373
        # store record
374
        self.reporter.t.append(t)
375
        self.reporter.q.append(q)
376
        self.reporter.u.append(u)
377
        self.reporter.qd.append(qd)
378
        self.reporter.lmd.append(lmd)
379
        self.reporter.lmd_d.append(lmd_d)
380
        self.reporter.tau.append(tau)
381
382
        return tau
383
384
    def target(self, x, t, x0):
385
        """Default joint space target function.
386
387
        Parameters
388
        ----------
389
390
        x: state
391
        t: time
392
        x0: initial state
393
394
        """
395
        return [np.deg2rad(130), np.deg2rad(60), np.deg2rad(95)]
396
397
398
# ------------------------------------------------------------------------
399
# PosturalMuscleSpaceController
400
# ------------------------------------------------------------------------
401
402
403
class PosturalMuscleSpaceController:
404
    """This is a posture controller in muscle space. The system is disturbed and a
405
    muscle length controller is responsible for restoring it to its initial
406
    pose.
407
408
    """
409
410
    def __init__(self, model, musclespace, kp, kd, delay, a, t0, sigma, gamma):
411
        """ Constructor.
412
413
        Parameters
414
        ----------
415
416
        model: a reference to ToyModel
417
418
        muscle: a reference to TaskSpace
419
420
        kp: proportional gain
421
422
        kd: derivative gain
423
424
        delay: the delay of the reflex loops
425
426
        a: Gaussian aptitude (disturbance)
427
428
        t0: time of application (disturbance)
429
430
        sigma: outspread (disturbance)
431
432
        gamma: direction of disturbance
433
434
        """
435
        self.logger = Logger('PosturalMsucleSpaceController')
436
        self.model = model
437
        self.musclespace = musclespace
438
        self.pd = PD(kp, kd)  # (10, 10) -> full controller, (0, 10) -> reflex
439
        self.delay = delay
440
        self.a = a
441
        self.t0 = t0
442
        self.sigma = sigma
443
        self.gamma = gamma
444
        self.reporter = SimulationReporter(model)
445
        self.__initialize_delay_components()
446
        self.__calculate_task()
447
448
    def __calculate_task(self):
449
        """Calculate model's end effector Jacobian transpose.
450
451
        """
452
        xt = sp.Matrix(self.model.ee)
453
        Jt = xt.jacobian(self.model.Q())
454
        self.JtT = Jt.transpose()
455
456
    def __add_in_distrurbance(self, t, tau, pose):
457
        """Adds distrubance to end effector.
458
459
        """
460
        angle = self.gamma
461
        o = np.asmatrix([[0], [0]])
462
        fd = np.asmatrix([[gaussian(t, self.a, self.t0, self.sigma)], [0]])
463
        fd = rotate(o, fd, angle)
464
        return tau + self.JtT.subs(pose) * fd
465
466
    def __initialize_delay_components(self):
467
        """Initializes the delay components with the default muscle length (state(0))
468
        and zero length velocities.
469
470
        """
471
        n = self.model.nd
472
        m = self.model.md
473
474
        state0 = self.model.state0
475
        q = state0[:n]
476
        u = state0[n:]
477
        pose = self.model.model_parameters(q=q, u=u)
478
        self.lm0 = self.model.lm.subs(pose)
479
        self.lm0 = to_np_vec(self.lm0)
480
481
        delay = np.full(m, self.delay)
482
483
        self.lm_del = DelayArray(m, delay, self.lm0)
484
        self.lmd_del = DelayArray(m, delay, np.zeros(m))
485
486
    def controller(self, x, t, x0):
487
        """ Controller.
488
489
        Parameters
490
        ----------
491
492
        x: state
493
        t: time
494
        x0: initial state
495
496
        """
497
        self.logger.debug('Time: ' + str(t))
498
        m = self.model.md
499
        n = self.model.nd
500
        q = np.array(x[:n])
501
        u = np.array(x[n:])
502
503
        # compute current muscle length and derivative
504
        pose = self.model.model_parameters(q=q, u=u)
505
        lm = self.model.lm.subs(pose)
506
        lm = to_np_vec(lm)
507
        lmd = self.model.lmd.subs(pose)
508
        lmd = to_np_vec(lmd)
509
510
        # compute target
511
        lm_des = self.target(x, t, x0)
512
        lmd_des = np.zeros(m)
513
        lmdd_des = np.zeros(m)
514
515
        # update delayed values and get current (must update first)
516
        self.lm_del.add(t, lm)
517
        self.lmd_del.add(t, lmd)
518
519
        lm_del = np.array(self.lm_del.get_delayed())
520
        lmd_del = np.array(self.lmd_del.get_delayed())
521
522
        lmdd_des = sp.Matrix(self.pd.compute(
523
            lm_del, lmd_del, lm_des, lmd_des, lmdd_des))
524
525
        tau, fm = self.musclespace.calculate_force(lmdd_des, pose)
526
        tau = self.__add_in_distrurbance(t, tau, pose)
527
        tau = to_np_vec(tau)
528
529
        # record
530
        self.reporter.t.append(t)
531
        self.reporter.q.append(q)
532
        self.reporter.u.append(u)
533
        self.reporter.lm.append(lm)
534
        self.reporter.lm_d.append(lm_des)
535
        self.reporter.fm.append(fm)
536
        self.reporter.tau.append(tau)
537
538
        return tau
539
540
    def target(self, x, t, x0):
541
        """Default muscle space target function.
542
543
        Parameters
544
        ----------
545
546
        x: state
547
        t: time
548
        x0: initial state
549
550
        """
551
        return self.lm0