-
Notifications
You must be signed in to change notification settings - Fork 497
/
Copy pathDynamics.py
1643 lines (1273 loc) · 54.5 KB
/
Dynamics.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
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
"""
Rigid-body dynamics functionality of the Toolbox.
Requires access to:
* ``links`` list of ``Link`` objects, atttribute
* ``rne()`` the inverse dynamics method
so must be subclassed by ``DHRobot`` class.
:todo: perhaps these should be abstract properties, methods of this calss
"""
from collections import namedtuple
import numpy as np
from spatialmath.base import getvector, verifymatrix, isscalar, getmatrix, t2r, rot2jac
from scipy import integrate, interpolate
from spatialmath.base import symbolic as sym
from roboticstoolbox import rtb_get_param
from ansitable import ANSITable, Column
import warnings
class DynamicsMixin:
# --------------------------------------------------------------------- #
@property
def gravity(self):
"""
Get/set default gravitational acceleration (Robot superclass)
- ``robot.name`` is the default gravitational acceleration
:return: robot name
:rtype: ndarray(3,)
- ``robot.name = ...`` checks and sets default gravitational
acceleration
.. note:: If the z-axis is upward, out of the Earth, this should be
a positive number.
"""
return self._gravity
@gravity.setter
def gravity(self, gravity_new):
self._gravity = getvector(gravity_new, 3)
self.dynchanged()
# --------------------------------------------------------------------- #
def dynamics(self):
"""
Pretty print the dynamic parameters (Robot superclass)
The dynamic parameters (inertial and friction) are printed in a table,
with one row per link.
Example:
.. runblock:: pycon
>>> import roboticstoolbox as rtb
>>> robot = rtb.models.DH.Puma560()
>>> robot.links[2].dyntable()
>>> robot.dyntable()
"""
unicode = rtb_get_param("unicode")
table = ANSITable(
Column("j", colalign=">", headalign="^"),
Column("m", colalign="<", headalign="^"),
Column("r", colalign="<", headalign="^"),
Column("I", colalign="<", headalign="^"),
Column("Jm", colalign="<", headalign="^"),
Column("B", colalign="<", headalign="^"),
Column("Tc", colalign="<", headalign="^"),
Column("G", colalign="<", headalign="^"),
border="thin" if unicode else "ascii",
)
for j, link in enumerate(self):
table.row(link.name, *link._dyn2list())
table.print()
def dynamics_list(self):
"""
Print dynamic parameters (Robot superclass)
Display the kinematic and dynamic parameters to the console in
reable format
"""
for j, link in enumerate(self.links):
print("\nLink {:d}::".format(j), link)
print(link.dyn(indent=2))
# --------------------------------------------------------------------- #
def friction(self, qd):
r"""
Manipulator joint friction (Robot superclass)
:param qd: The joint velocities of the robot
:type qd: ndarray(n)
:return: The joint friction forces/torques for the robot
:rtype: ndarray(n,)
``robot.friction(qd)`` is a vector of joint friction
forces/torques for the robot moving with joint velocities ``qd``.
The friction model includes:
- Viscous friction which is a linear function of velocity.
- Coulomb friction which is proportional to sign(qd).
.. math::
\tau_j = G^2 B \dot{q}_j + |G_j| \left\{ \begin{array}{ll}
\tau_{C,j}^+ & \mbox{if $\dot{q}_j > 0$} \\
\tau_{C,j}^- & \mbox{if $\dot{q}_j < 0$} \end{array} \right.
.. note::
- The friction value should be added to the motor output torque to
determine the nett torque. It has a negative value when qd > 0.
- The returned friction value is referred to the output of the
gearbox.
- The friction parameters in the Link object are referred to the
motor.
- Motor viscous friction is scaled up by :math:`G^2`.
- Motor Coulomb friction is scaled up by math:`G`.
- The appropriate Coulomb friction value to use in the
non-symmetric case depends on the sign of the joint velocity,
not the motor velocity.
- Coulomb friction is zero for zero joint velocity, stiction is
not modeled.
- The absolute value of the gear ratio is used. Negative gear
ratios are tricky: the Puma560 robot has negative gear ratio for
joints 1 and 3.
- The absolute value of the gear ratio is used. Negative gear
ratios are tricky: the Puma560 has negative gear ratio for
joints 1 and 3.
:seealso: :func:`Robot.nofriction`, :func:`Link.friction`
"""
qd = getvector(qd, self.n)
tau = np.zeros(self.n)
for i in range(self.n):
tau[i] = self.links[i].friction(qd[i])
return tau
# --------------------------------------------------------------------- #
def nofriction(self, coulomb=True, viscous=False):
"""
Remove manipulator joint friction (Robot superclass)
:param coulomb: set the Coulomb friction to 0
:type coulomb: bool
:param viscous: set the viscous friction to 0
:type viscous: bool
:return: A copy of the robot with dynamic parameters perturbed
:rtype: Robot subclass
``nofriction()`` copies the robot and returns
a robot with the same link parameters except the Coulomb and/or viscous
friction parameter are set to zero.
:seealso: :func:`Robot.friction`, :func:`Link.nofriction`
"""
# shallow copy the robot object
self.delete_rne() # remove the inherited C pointers
nf = self.copy()
nf.name = "NF/" + self.name
# add the modified links (copies)
nf._links = [link.nofriction(coulomb, viscous) for link in self.links]
return nf
def fdyn(
self,
T,
q0,
torque=None,
torque_args={},
qd0=None,
solver="RK45",
solver_args={},
dt=None,
progress=False,
):
"""
Integrate forward dynamics
:param T: integration time
:type T: float
:param q0: initial joint coordinates
:type q0: array_like
:param qd0: initial joint velocities, assumed zero if not given
:type qd0: array_like
:param torque: a function that computes torque as a function of time
and/or state
:type torque: callable
:param torque_args: positional arguments passed to ``torque``
:type torque_args: dict
:type solver: name of scipy solver to use, RK45 is the default
:param solver: str
:type solver_args: arguments passed to the solver
:param solver_args: dict
:type dt: time step for results
:param dt: float
:param progress: show progress bar, default False
:type progress: bool
:return: robot trajectory
:rtype: namedtuple
- ``tg = R.fdyn(T, q)`` integrates the dynamics of the robot with zero
input torques over the time interval 0 to ``T`` and returns the
trajectory as a namedtuple with elements:
- ``t`` the time vector (M,)
- ``q`` the joint coordinates (M,n)
- ``qd`` the joint velocities (M,n)
- ``tg = R.fdyn(T, q, torqfun)`` as above but the torque applied to the
joints is given by the provided function::
tau = function(robot, t, q, qd, **args)
where the inputs are:
- the robot object
- current time
- current joint coordinates (n,)
- current joint velocity (n,)
- args, optional keyword arguments can be specified, these are
passed in from the ``targs`` kewyword argument.
The function must return a Numpy array (n,) of joint forces/torques.
Examples:
#. to apply zero joint torque to the robot without Coulomb
friction::
def myfunc(robot, t, q, qd):
return np.zeros((robot.n,))
tg = robot.nofriction().fdyn(5, q0, myfunc)
plt.figure()
plt.plot(tg.t, tg.q)
plt.show()
We could also use a lambda function::
tg = robot.nofriction().fdyn(
5, q0, lambda r, t, q, qd: np.zeros((r.n,)))
#. the robot is controlled by a PD controller. We first define a
function to compute the control which has additional parameters for
the setpoint and control gains (qstar, P, D)::
def myfunc(robot, t, q, qd, qstar, P, D):
return (qstar - q) * P + qd * D # P, D are (6,)
tg = robot.fdyn(10, q0, myfunc, torque_args=(qstar, P, D)) )
Many integrators have variable step length which is problematic if we
want to animate the result. If ``dt`` is specified then the solver
results are interpolated in time steps of ``dt``.
.. note::
- This function performs poorly with non-linear joint friction,
such as Coulomb friction. The R.nofriction() method can be used
to set this friction to zero.
- If the function is not specified then zero force/torque is
applied to the manipulator joints.
- Interpolation is performed using `ScipY integrate.ode
<https://docs.scipy.org/doc/scipy/reference/generated/scipy.integrate.ode.html>`
- The SciPy RK45 integrator is used by default
- Interpolation is performed using `SciPy interp1
<https://docs.scipy.org/doc/scipy/reference/generated/scipy.interpolate.interp1d.html>`
:seealso: :func:`DHRobot.accel`, :func:`DHRobot.nofriction`,
:func:`DHRobot.rne`.
"""
n = self.n
if not isscalar(T):
raise ValueError("T must be a scalar")
q0 = getvector(q0, n)
if qd0 is None:
qd0 = np.zeros((n,))
else:
qd0 = getvector(qd0, n)
if torque is not None:
if not callable(torque):
raise ValueError("torque function must be callable")
# concatenate q and qd into the initial state vector
x0 = np.r_[q0, qd0]
# get user specified integrator
scipy_integrator = integrate.__dict__[solver]
integrator = scipy_integrator(
lambda t, y: self._fdyn(t, y, torque, torque_args),
t0=0.0,
y0=x0,
t_bound=T,
**solver_args,
)
# initialize list of time and states
tlist = [0]
xlist = [np.r_[q0, qd0]]
if progress:
_printProgressBar(0, prefix="Progress:", suffix="complete", length=60)
while integrator.status == "running":
# step the integrator, calls _fdyn multiple times
integrator.step()
if integrator.status == "failed":
raise RuntimeError("integration completed with failed status ")
# stash the results
tlist.append(integrator.t)
xlist.append(integrator.y)
# update the progress bar
if progress:
_printProgressBar(
integrator.t / T, prefix="Progress:", suffix="complete", length=60
)
# cleanup the progress bar
if progress:
print("\r" + " " * 90 + "\r")
tarray = np.array(tlist)
xarray = np.array(xlist)
if dt is not None:
# interpolate data to equal time steps of dt
interp = interpolate.interp1d(tarray, xarray, axis=0)
tnew = np.arange(0, T, dt)
xnew = interp(tnew)
return namedtuple("fdyn", "t q qd")(tnew, xnew[:, :n], xnew[:, n:])
else:
return namedtuple("fdyn", "t q qd")(tarray, xarray[:, :n], xarray[:, n:])
def _fdyn(self, t, x, torqfun, targs):
"""
Private function called by fdyn
:param t: current time
:type t: float
:param x: current state [q, qd]
:type x: numpy array (2n,)
:param torqfun: a function that computes torque as a function of time
and/or state
:type torqfun: callable
:param targs: argumments passed to ``torqfun``
:type targs: dict
:return: derivative of current state [qd, qdd]
:rtype: numpy array (2n,)
Called by ``fdyn`` to evaluate the robot velocity and acceleration for
forward dynamics.
"""
n = self.n
q = x[0:n]
qd = x[n:]
# evaluate the torque function if one is given
if torqfun is None:
tau = np.zeros((n,))
else:
tau = torqfun(self, t, q, qd, **targs)
if len(tau) != n or not all(np.isreal(tau)):
raise RuntimeError(
"torque function must return vector with N real elements"
)
qdd = self.accel(q, qd, tau)
return np.r_[qd, qdd]
def accel(self, q, qd, torque, gravity=None):
r"""
Compute acceleration due to applied torque
:param q: Joint coordinates
:type q: ndarray(n)
:param qd: Joint velocity
:type qd: ndarray(n)
:param torque: Joint torques of the robot
:type torque: ndarray(n)
:param gravity: Gravitational acceleration (Optional, if not supplied will
use the ``gravity`` attribute of self).
:type gravity: ndarray(3)
:return: Joint accelerations of the robot
:rtype: ndarray(n)
``qdd = accel(q, qd, torque)`` calculates a vector (n) of joint
accelerations that result from applying the actuator force/torque (n)
to the manipulator in state `q` (n) and `qd` (n), and ``n`` is
the number of robot joints.
.. math::
\ddot{q} = \mathbf{M}^{-1} \left(\tau - \mathbf{C}(q)\dot{q} - \mathbf{g}(q)\right)
Example:
.. runblock:: pycon
>>> import roboticstoolbox as rtb
>>> puma = rtb.models.DH.Puma560()
>>> puma.accel(puma.qz, 0.5 * np.ones(6), np.zeros(6))
**Trajectory operation**
If `q`, `qd`, torque are matrices (m,n) then ``qdd`` is a matrix (m,n)
where each row is the acceleration corresponding to the equivalent rows
of q, qd, torque.
.. note::
- Useful for simulation of manipulator dynamics, in
conjunction with a numerical integration function.
- Uses the method 1 of Walker and Orin to compute the forward
dynamics.
- Featherstone's method is more efficient for robots with large
numbers of joints.
- Joint friction is considered.
:references:
- Efficient dynamic computer simulation of robotic mechanisms,
M. W. Walker and D. E. Orin,
ASME Journa of Dynamic Systems, Measurement and Control, vol.
104, no. 3, pp. 205-211, 1982.
""" # noqa
q = getmatrix(q, (None, self.n))
qd = getmatrix(qd, (None, self.n))
torque = getmatrix(torque, (None, self.n))
qdd = np.zeros((q.shape[0], self.n))
for k, (qk, qdk, tauk) in enumerate(zip(q, qd, torque)):
# Compute current manipulator inertia torques resulting from unit
# acceleration of each joint with no gravity.
qI = (np.c_[qk] @ np.ones((1, self.n))).T
qdI = np.zeros((self.n, self.n))
qddI = np.eye(self.n)
M = self.rne(qI, qdI, qddI, gravity=[0, 0, 0])
# Compute gravity and coriolis torque torques resulting from zero
# acceleration at given velocity & with gravity acting.
tau = self.rne(qk, qdk, np.zeros((1, self.n)), gravity=gravity)
# solve is faster than inv() which is faster than pinv()
qdd[k, :] = np.linalg.solve(M, tauk - tau)
if q.shape[0] == 1:
return qdd[0, :]
else:
return qdd
def pay(self, W, q=None, J=None, frame=1):
"""
tau = pay(W, J) Returns the generalised joint force/torques due to a
payload wrench W applied to the end-effector. Where the manipulator
Jacobian is J (6xn), and n is the number of robot joints.
tau = pay(W, q, frame) as above but the Jacobian is calculated at pose
q in the frame given by frame which is 0 for base frame, 1 for
end-effector frame.
Uses the formula tau = J'W, where W is a wrench vector applied at the
end effector, W = [Fx Fy Fz Mx My Mz]'.
Trajectory operation:
In the case q is nxm or J is 6xnxm then tau is nxm where each row
is the generalised force/torque at the pose given by corresponding
row of q.
:param W: A wrench vector applied at the end effector,
W = [Fx Fy Fz Mx My Mz]
:type W: ndarray(6)
:param q: Joint coordinates
:type q: ndarray(n)
:param J: The manipulator Jacobian (Optional, if not supplied will
use the q value).
:type J: ndarray(6,n)
:param frame: The frame in which to torques are expressed in when J
is not supplied. 0 means base frame of the robot, 1 means end-
effector frame
:type frame: int
:return: Joint forces/torques due to w
:rtype: ndarray(n)
.. note::
- Wrench vector and Jacobian must be from the same reference
frame.
- Tool transforms are taken into consideration when frame=1.
- Must have a constant wrench - no trajectory support for this
yet.
"""
try:
W = getvector(W, 6)
trajn = 0
except ValueError:
trajn = W.shape[0]
verifymatrix(W, (trajn, 6))
if trajn:
# A trajectory
if J is not None:
# Jacobian supplied
verifymatrix(J, (trajn, 6, self.n))
else:
# Use q instead
verifymatrix(q, (trajn, self.n))
J = np.zeros((trajn, 6, self.n))
for i in range(trajn):
if frame:
J[i, :, :] = self.jacobe(q[i, :])
else:
J[i, :, :] = self.jacob0(q[i, :])
else:
# Single configuration
if J is not None:
# Jacobian supplied
verifymatrix(J, (6, self.n))
else:
# Use q instead
if q is None:
q = np.copy(self.q)
else:
q = getvector(q, self.n)
if frame:
J = self.jacobe(q)
else:
J = self.jacob0(q)
if trajn == 0:
tau = -J.T @ W
else:
tau = np.zeros((trajn, self.n))
for i in range(trajn):
tau[i, :] = -J[i, :, :].T @ W[i, :]
return tau
def payload(self, m, p=np.zeros(3)):
"""
payload(m, p) adds payload mass adds a payload with point mass m at
position p in the end-effector coordinate frame.
payload(m) adds payload mass adds a payload with point mass m at
in the end-effector coordinate frame.
payload(0) removes added payload.
:param m: mass (kg)
:type m: float
:param p: position in end-effector frame
:type p: ndarray(3,1)
"""
p = getvector(p, 3, out="col")
lastlink = self.links[self.n - 1]
lastlink.m = m
lastlink.r = p
def jointdynamics(self, q, qd=None):
"""
Transfer function of joint actuator
:param q: Joint coordinates
:type q: ndarray(n)
:param qd: Joint velocity
:type qd: ndarray(n)
:return: transfer function denominators
:rtype: list of 2-tuples
- ``tf = jointdynamics(qd, q)`` calculates a vector of n
continuous-time transfer functions that represent the transfer
function 1/(Js+B) for each joint based on the dynamic parameters
of the robot and the configuration q (n). n is the number of robot
joints. The result is a list of tuples (J, B) for each joint.
- ``tf = jointdynamics(q, qd)`` as above but include the linearized
effects of Coulomb friction when operating at joint velocity QD
(1xN).
"""
tf = []
for j, link in enumerate(self.links):
# compute inertia for this joint
zero = np.zeros((self.n))
qdd = np.zeros((self.n))
qdd[j] = 1
M = self.rne(q, zero, qdd, gravity=[0, 0, 0])
J = link.Jm + M[j] / abs(link.G) ** 2
# compute friction
B = link.B
if qd is not None:
# add linearized Coulomb friction at the operating point
if qd > 0:
B += link.Tc[0] / qd[j]
elif qd < 0:
B += link.Tc[1] / qd[j]
tf.append(((1,), (J, B)))
return tf
def cinertia(self, q):
"""
Deprecated, use ``inertia_x``
"""
warnings.warn("cinertia is deprecated, use inertia_x", DeprecationWarning)
def inertia(self, q):
"""
Manipulator inertia matrix
:param q: Joint coordinates
:type q: ndarray(n) or ndarray(m,n)
:return: The inertia matrix
:rtype: ndarray(n,n) or ndarray(m,n,n)
``inertia(q)`` is the symmetric joint inertia matrix (n,n) which
relates joint torque to joint acceleration for the robot at joint
configuration q.
Example:
.. runblock:: pycon
>>> import roboticstoolbox as rtb
>>> puma = rtb.models.DH.Puma560()
>>> puma.inertia(puma.qz)
**Trajectory operation**
If ``q`` is a matrix (m,n), each row is interpretted as a joint state
vector, and the result is a 3d-matrix (nxnxk) where each plane
corresponds to the inertia for the corresponding row of q.
.. note::
- The diagonal elements ``M[j,j]`` are the inertia seen by joint
actuator ``j``.
- The off-diagonal elements ``M[j,k]`` are coupling inertias that
relate acceleration on joint ``j`` to force/torque on
joint ``k``.
- The diagonal terms include the motor inertia reflected through
the gear ratio.
:seealso: :func:`cinertia`
"""
q = getmatrix(q, (None, self.n))
In = np.zeros((q.shape[0], self.n, self.n))
for k, qk in enumerate(q):
In[k, :, :] = self.rne(
(np.c_[qk] @ np.ones((1, self.n))).T,
np.zeros((self.n, self.n)),
np.eye(self.n),
gravity=[0, 0, 0],
)
if q.shape[0] == 1:
return In[0, :, :]
else:
return In
def coriolis(self, q, qd):
r"""
Coriolis and centripetal term
:param q: Joint coordinates
:type q: ndarray(n) or ndarray(m,n)
:param qd: Joint velocity
:type qd: ndarray(n) or ndarray(m,n)
:return: Velocity matrix
:rtype: ndarray(n,n) or ndarray(m,n,n)
``coriolis(q, qd)`` calculates the Coriolis/centripetal matrix (n,n)
for the robot in configuration ``q`` and velocity ``qd``, where ``n``
is the number of joints.
The product :math:`\mathbf{C} \dot{q}` is the vector of joint
force/torque due to velocity coupling. The diagonal elements are due to
centripetal effects and the off-diagonal elements are due to Coriolis
effects. This matrix is also known as the velocity coupling matrix,
since it describes the disturbance forces on any joint due to
velocity of all other joints.
Example:
.. runblock:: pycon
>>> import roboticstoolbox as rtb
>>> puma = rtb.models.DH.Puma560()
>>> puma.coriolis(puma.qz, 0.5 * np.ones((6,)))
**Trajectory operation**
If ``q`` and `qd` are matrices (m,n), each row is interpretted as a
joint configuration, and the result (n,n,m) is a 3d-matrix where
each plane corresponds to a row of ``q`` and ``qd``.
.. note::
- Joint viscous friction is also a joint force proportional to
velocity but it is eliminated in the computation of this value.
- Computationally slow, involves :math:`n^2/2` invocations of RNE.
"""
q = getmatrix(q, (None, self.n))
qd = getmatrix(qd, (None, self.n))
if q.shape[0] != qd.shape[0]:
raise ValueError("q and qd must have the same number of rows")
# ensure that friction doesn't enter the mix, it's also a velocity
# dependent force/torque
r1 = self.nofriction(True, True)
C = np.zeros((q.shape[0], self.n, self.n))
Csq = np.zeros((q.shape[0], self.n, self.n))
# Find the torques that depend on a single finite joint speed,
# these are due to the squared (centripetal) terms
# set QD = [1 0 0 ...] then resulting torque is due to qd_1^2
for k, qk in enumerate(q):
for i in range(self.n):
QD = np.zeros(self.n)
QD[i] = 1
tau = r1.rne(qk, QD, np.zeros(self.n), gravity=[0, 0, 0])
Csq[k, :, i] = Csq[k, :, i] + tau
# Find the torques that depend on a pair of finite joint speeds,
# these are due to the product (Coriolis) terms
# set QD = [1 1 0 ...] then resulting torque is due to
# qd_1 qd_2 + qd_1^2 + qd_2^2
for k, (qk, qdk) in enumerate(zip(q, qd)):
for i in range(self.n):
for j in range(i + 1, self.n):
# Find a product term qd_i * qd_j
QD = np.zeros(self.n)
QD[i] = 1
QD[j] = 1
tau = r1.rne(qk, QD, np.zeros(self.n), gravity=[0, 0, 0])
C[k, :, j] = (
C[k, :, j] + (tau - Csq[k, :, j] - Csq[k, :, i]) * qdk[i] / 2
)
C[k, :, i] = (
C[k, :, i] + (tau - Csq[k, :, j] - Csq[k, :, i]) * qdk[j] / 2
)
C[k, :, :] = C[k, :, :] + Csq[k, :, :] @ np.diag(qdk)
if q.shape[0] == 1:
return C[0, :, :]
else:
return C
def gravload(self, q=None, gravity=None):
"""
Compute gravity load
:param q: Joint coordinates
:type q: ndarray(n)
:param gravity: Gravitational acceleration (Optional, if not supplied will
use the stored gravity values).
:type gravity: ndarray(3)
:return: The generalised joint force/torques due to gravity
:rtype: ndarray(n)
``robot.gravload(q)`` calculates the joint gravity loading (n) for
the robot in the joint configuration ``q`` and using the default
gravitational acceleration specified in the DHRobot object.
``robot.gravload(q, gravity=g)`` as above except the gravitational
acceleration is explicitly specified as ``g``.
Example:
.. runblock:: pycon
>>> import roboticstoolbox as rtb
>>> puma = rtb.models.DH.Puma560()
>>> puma.gravload(puma.qz)
**Trajectory operation**
If q is a matrix (nxm) each column is interpreted as a joint
configuration vector, and the result is a matrix (nxm) each column
being the corresponding joint torques.
"""
q = getmatrix(q, (None, self.n))
if gravity is None:
gravity = self.gravity
else:
gravity = getvector(gravity, 3)
taug = np.zeros((q.shape[0], self.n))
z = np.zeros(self.n)
for k, qk in enumerate(q):
taug[k, :] = self.rne(qk, z, z, gravity=gravity)
if q.shape[0] == 1:
return taug[0, :]
else:
return taug
def inertia_x(self, q=None, pinv=False, representation="rpy/xyz", Ji=None):
r"""
Operational space inertia matrix
:param q: Joint coordinates
:type q: array_like(n) or ndarray(m,n)
:param pinv: use pseudo inverse rather than inverse
:type pinv: bool
:param analytical: the type of analytical Jacobian to use, default is
'rpy/xyz'
:type analytical: str
:return: The inertia matrix
:rtype: ndarray(6,6) or ndarray(m,6,6)
``robot.inertia_x(q)`` is the operational space (Cartesian) inertia
matrix which relates Cartesian force/torque to Cartesian
acceleration at the joint configuration q.
.. math::
\mathbf{M}_x = \mathbf{J}(q)^{-T} \mathbf{M}(q) \mathbf{J}(q)^{-1}
The transformation to operational space requires an analytical, rather
than geometric, Jacobian. ``analytical`` can be one of:
============= ========================================
Value Rotational representation
============= ========================================
``'rpy/xyz'`` RPY angular rates in XYZ order (default)
``'rpy/zyx'`` RPY angular rates in XYZ order
``'eul'`` Euler angular rates in ZYZ order
``'exp'`` exponential coordinate rates
============= ========================================
Example:
.. runblock:: pycon
>>> import roboticstoolbox as rtb
>>> puma = rtb.models.DH.Puma560()
>>> puma.inertia_x(puma.qz)
**Trajectory operation**
If ``q`` is a matrix (m,n), each row is interpretted as a joint state
vector, and the result is a 3d-matrix (m,n,n) where each plane
corresponds to the Cartesian inertia for the corresponding
row of ``q``.
.. note::
- If the robot is not 6 DOF the ``pinv`` option is set True.
- ``pinv()`` is around 5x slower than ``inv()``
.. warning:: Assumes that the operational space has 6 DOF.
:seealso: :func:`inertia`
"""
q = getmatrix(q, (None, self.n))
if q.shape[1] != 6:
pinv = True
if q.shape[0] == 1:
# single q case
if Ji is None:
Ja = self.jacob0_analytical(q[0, :], representation)
if pinv:
Ji = np.linalg.pinv(Ja)
else:
Ji = np.linalg.inv(Ja)
M = self.inertia(q[0, :])
return Ji.T @ M @ Ji
else:
# trajectory case
Mt = np.zeros((q.shape[0], 6, 6))
for k, qk in enumerate(q):
Ja = self.jacob0_analytical(qk, representation)
if pinv:
Ji = np.linalg.pinv(Ja)
else:
Ji = np.linalg.inv(Ja)
M = self.inertia(qk)
Mt[k, :, :] = Ji.T @ M @ Ji
return Mt
def coriolis_x(
self,
q,
qd,
pinv=False,
representation="rpy/xyz",
J=None,
Ji=None,
Jd=None,
C=None,
Mx=None,
):
r"""
Operational space Coriolis and centripetal term
:param q: Joint coordinates
:type q: ndarray(n) or ndarray(m,n)
:param qd: Joint velocity
:type qd: ndarray(n) or ndarray(m,n)
:param pinv: use pseudo inverse rather than inverse
:type pinv: bool
:param analytical: the type of analytical Jacobian to use, default is
'rpy/xyz'
:type analytical: str
:return: Operational space velocity matrix
:rtype: ndarray(6,6) or ndarray(m,6,6)
``coriolis_x(q, qd)`` is the Coriolis/centripetal matrix (m,m)
in operational space for the robot in configuration ``q`` and velocity
``qd``, where ``n`` is the number of joints.
.. math::
\mathbf{C}_x = \mathbf{J}(q)^{-T} \left(
\mathbf{C}(q) - \mathbf{M}_x(q) \mathbf{J})(q)
\right) \mathbf{J}(q)^{-1}
The product :math:`\mathbf{C} \dot{x}` is the operational space wrench
due to joint velocity coupling. This matrix is also known as the
velocity coupling matrix, since it describes the disturbance forces on
any joint due to velocity of all other joints.
The transformation to operational space requires an analytical, rather
than geometric, Jacobian. ``analytical`` can be one of:
============= ========================================
Value Rotational representation
============= ========================================
``'rpy/xyz'`` RPY angular rates in XYZ order (default)
``'rpy/zyx'`` RPY angular rates in XYZ order
``'eul'`` Euler angular rates in ZYZ order
``'exp'`` exponential coordinate rates
============= ========================================
Example:
.. runblock:: pycon
>>> import roboticstoolbox as rtb
>>> puma = rtb.models.DH.Puma560()