10
10
11
11
:todo: perhaps these should be abstract properties, methods of this calss
12
12
"""
13
-
13
+ import copy
14
+ from collections import namedtuple
14
15
import numpy as np
15
16
import roboticstoolbox as rp
16
17
from spatialmath .base .argcheck import \
17
- getvector , verifymatrix
18
-
18
+ getvector , verifymatrix , isscalar
19
+ from scipy import integrate , interpolate
19
20
20
21
class Dynamics :
21
22
23
+ def printdyn (self ):
24
+ """
25
+ Print dynamic parameters
26
+
27
+ Display the kinematic and dynamic parameters to the console in
28
+ reable format
29
+ """
30
+ for j , link in enumerate (self .links ):
31
+ print ("\n Link {:d}::" .format (j ), link )
32
+ print (link .dyn (indent = 2 ))
33
+
34
+
35
+ def fdyn (self , T , q0 , torqfun = None , targs = None , qd0 = None ,
36
+ solver = 'RK45' , sargs = None , dt = None , progress = False ):
37
+ """
38
+ Integrate forward dynamics
39
+
40
+ :param T: integration time
41
+ :type T: float
42
+ :param q0: initial joint coordinates
43
+ :type q0: array_like
44
+ :param qd0: initial joint velocities, assumed zero if not given
45
+ :type qd0: array_like
46
+ :param torqfun: a function that computes torque as a function of time
47
+ and/or state
48
+ :type torqfun: callable
49
+ :param targs: argumments passed to ``torqfun``
50
+ :type targs: dict
51
+ :type solver: name of scipy solver to use, RK45 is the default
52
+ :param solver: str
53
+ :type sargs: arguments passed to the solver
54
+ :param sargs: dict
55
+ :type dt: time step for results
56
+ :param dt: float
57
+ :param progress: show progress bar, default False
58
+ :type progress: bool
59
+
60
+ :return: robot trajectory
61
+ :rtype: namedtuple
62
+
63
+ - ``tg = R.fdyn(T, q)`` integrates the dynamics of the robot with zero
64
+ input torques over the time interval 0 to ``T`` and returns the
65
+ trajectory as a namedtuple with elements:
66
+
67
+ - ``t`` the time vector (M,)
68
+ - ``q`` the joint coordinates (M,n)
69
+ - ``qd`` the joint velocities (M,n)
70
+
71
+ - ``tg = R.fdyn(T, q, torqfun)`` as above but the torque applied to the
72
+ joints is given by the provided function::
73
+
74
+ tau = function(robot, t, q, qd, **args)
75
+
76
+ where the inputs are:
77
+
78
+ - the robot object
79
+ - current time
80
+ - current joint coordinates (n,)
81
+ - current joint velocity (n,)
82
+ - args, optional keyword arguments can be specified, these are
83
+ passed in from the ``targs`` kewyword argument.
84
+
85
+ The function must return a Numpy array (n,) of joint forces/torques.
86
+
87
+ Examples:
88
+
89
+ #. to apply zero joint torque to the robot without Coulomb
90
+ friction::
91
+
92
+ def myfunc(robot, t, q, qd):
93
+ return np.zeros((robot.n,))
94
+
95
+ tg = robot.nofriction().fdyn(5, q0, myfunc)
96
+
97
+ plt.figure()
98
+ plt.plot(tg.t, tg.q)
99
+ plt.show()
100
+
101
+ We could also use a lambda function::
102
+
103
+ tg = robot.nofriction().fdyn(5, q0, lambda r, t, q, qd: np.zeros((r.n,)))
104
+
105
+ #. the robot is controlled by a PD controller. We first define a
106
+ function to compute the control which has additional parameters for
107
+ the setpoint and control gains (qstar, P, D)::
108
+
109
+ def myfunc(robot, t, q, qd, qstar, P, D):
110
+ return (qstar - q) * P + qd * D # P, D are (6,)
111
+
112
+ targs = {'qstar': VALUE, 'P': VALUE, 'D': VALUE}
113
+ tg = robot.fdyn(10, q0, myfunc, targs=targs) )
114
+
115
+ Many integrators have variable step length which is problematic if we
116
+ want to animate the result. If ``dt`` is specified then the solver
117
+ results are interpolated in time steps of ``dt``.
118
+
119
+ :notes:
120
+
121
+ - This function performs poorly with non-linear joint friction, such as
122
+ Coulomb friction. The R.nofriction() method can be used to set this
123
+ friction to zero.
124
+ - If the function is not specified then zero force/torque is
125
+ applied to the manipulator joints.
126
+ - Interpolation is performed using `ScipY integrate.ode <https://docs.scipy.org/doc/scipy/reference/generated/scipy.integrate.ode.html>`
127
+ - The SciPy RK45 integrator is used by default
128
+ - Interpolation is performed using `SciPy interp1 <https://docs.scipy.org/doc/scipy/reference/generated/scipy.interpolate.interp1d.html>`
129
+
130
+ :seealso: :func:`DHRobot.accel`, :func:`DHRobot.nofriction`, :func:`DHRobot.rne`.
131
+ """
132
+
133
+ n = self .n
134
+
135
+ if not isscalar (T ):
136
+ raise ValueError ('T must be a scalar' )
137
+ q0 = getvector (q0 , n )
138
+ if qd0 is None :
139
+ qd0 = np .zeros ((n ,))
140
+ else :
141
+ qd0 = getvector (qd0 , n )
142
+ if torqfun is not None :
143
+ if not callable (torqfun ):
144
+ raise ValueError ('torque function must be callable' )
145
+ if sargs is None :
146
+ sargs = {}
147
+ if targs is None :
148
+ targs = {}
149
+
150
+ # concatenate q and qd into the initial state vector
151
+ x0 = np .r_ [q0 , qd0 ]
152
+
153
+ scipy_integrator = integrate .__dict__ [solver ] # get user specified integrator
154
+
155
+ integrator = scipy_integrator (
156
+ lambda t , y : self ._fdyn (t , y , torqfun , targs ),
157
+ t0 = 0.0 , y0 = x0 , t_bound = T , ** sargs
158
+ )
1241
159
+
160
+ # initialize list of time and states
161
+ tlist = [0 ]
162
+ xlist = [np .r_ [q0 , qd0 ]]
163
+
164
+ if progress :
165
+ _printProgressBar (0 , prefix = 'Progress:' , suffix = 'complete' , length = 60 )
166
+
167
+ while integrator .status == 'running' :
168
+
169
+ # step the integrator, calls _fdyn multiple times
170
+ integrator .step ()
171
+
172
+ if integrator .status == 'failed' :
173
+ raise RuntimeError ('integration completed with failed status ' )
174
+
175
+ # stash the results
176
+ tlist .append (integrator .t )
177
+ xlist .append (integrator .y )
178
+
179
+ # update the progress bar
180
+ if progress :
181
+ _printProgressBar (integrator .t / T , prefix = 'Progress:' , suffix = 'complete' , length = 60 )
182
+
183
+ # cleanup the progress bar
184
+ if progress :
185
+ print ('\r ' + ' ' * 90 + '\r ' )
186
+
187
+ tarray = np .array (tlist )
188
+ xarray = np .array (xlist )
189
+
190
+ if dt is not None :
191
+ # interpolate data to equal time steps of dt
192
+ interp = interpolate .interp1d (tarray , xarray , axis = 0 )
193
+
194
+ tnew = np .arange (0 , T , dt )
195
+ xnew = interp (tnew )
196
+ return namedtuple ('fdyn' , 't q qd' )(tnew , xnew [:, :n ], xnew [:, n :])
197
+ else :
198
+ return namedtuple ('fdyn' , 't q qd' )(tarray , xarray [:, :n ], xarray [:, n :])
199
+
200
+ def _fdyn (self , t , x , torqfun , targs ):
201
+ """
202
+ Private function called by fdyn
203
+
204
+ :param t: current time
205
+ :type t: float
206
+ :param x: current state [q, qd]
207
+ :type x: numpy array (2n,)
208
+ :param torqfun: a function that computes torque as a function of time
209
+ and/or state
210
+ :type torqfun: callable
211
+ :param targs: argumments passed to ``torqfun``
212
+ :type targs: dict
213
+
214
+ :return: derivative of current state [qd, qdd]
215
+ :rtype: numpy array (2n,)
216
+
217
+ Called by ``fdyn`` to evaluate the robot velocity and acceleration for
218
+ forward dynamics.
219
+ """
220
+ n = self .n
221
+
222
+ q = x [0 :n ]
223
+ qd = x [n :]
224
+
225
+ # evaluate the torque function if one is given
226
+ if torqfun is None :
227
+ tau = np .zeros ((n ,))
228
+ else :
229
+ tau = torqfun (self , t , q , qd , ** targs )
230
+ if len (tau ) != n or not all (np .isreal (tau )):
231
+ raise RuntimeError ('torque function must return vector with N real elements' )
232
+
233
+ qdd = self .accel (q , qd , tau )
234
+
235
+ return np .r_ [qd , qdd ]
236
+
237
+
22
238
def accel (self , q , qd , torque , ):
23
239
"""
24
240
Compute acceleration due to applied torque
@@ -113,18 +329,14 @@ def nofriction(self, coulomb=True, viscous=False):
113
329
114
330
"""
115
331
116
- L = []
332
+ # shallow copy the robot object
333
+ nf = copy .copy (self )
334
+ nf .name = 'NF/' + self .name
117
335
118
- for i in range ( self . n ):
119
- L . append ( self . links [ i ] .nofriction (coulomb , viscous ))
336
+ # add the modified links (copies)
337
+ nf . _links = [ link .nofriction (coulomb , viscous ) for link in self . links ]
120
338
121
- return rp .SerialLink (
122
- L ,
123
- name = 'NF' + self .name ,
124
- manufacturer = self .manuf ,
125
- base = self .base ,
126
- tool = self .tool ,
127
- gravity = self .gravity )
339
+ return nf
128
340
129
341
def pay (self , W , q = None , J = None , frame = 1 ):
130
342
"""
@@ -866,3 +1078,10 @@ def perterb(self, p=0.1):
866
1078
r2 .links [i ].I = r2 .links [i ].I * s # noqa
867
1079
868
1080
return r2
1081
+
1082
+ def _printProgressBar (fraction , prefix = '' , suffix = '' , decimals = 1 , length = 50 , fill = '█' , printEnd = "\r " ):
1083
+
1084
+ percent = ("{0:." + str (decimals ) + "f}" ).format (fraction * 100 )
1085
+ filledLength = int (length * fraction )
1086
+ bar = fill * filledLength + '-' * (length - filledLength )
1087
+ print (f'\r { prefix } |{ bar } | { percent } % { suffix } ' , end = printEnd )
0 commit comments