c4dynamics 2.0.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- c4dynamics/__init__.py +240 -0
- c4dynamics/datasets/__init__.py +95 -0
- c4dynamics/datasets/_manager.py +596 -0
- c4dynamics/datasets/_registry.py +80 -0
- c4dynamics/detectors/__init__.py +37 -0
- c4dynamics/detectors/yolo3_opencv.py +686 -0
- c4dynamics/detectors/yolo3_tf.py +124 -0
- c4dynamics/eqm/__init__.py +324 -0
- c4dynamics/eqm/derivs.py +212 -0
- c4dynamics/eqm/integrate.py +359 -0
- c4dynamics/filters/__init__.py +1373 -0
- c4dynamics/filters/a.py +48 -0
- c4dynamics/filters/ekf.py +320 -0
- c4dynamics/filters/kalman.py +725 -0
- c4dynamics/filters/kalman_v0.py +1071 -0
- c4dynamics/filters/kalman_v1.py +821 -0
- c4dynamics/filters/lowpass.py +123 -0
- c4dynamics/filters/luenberger.py +97 -0
- c4dynamics/rotmat/__init__.py +141 -0
- c4dynamics/rotmat/animate.py +465 -0
- c4dynamics/rotmat/rotmat.py +351 -0
- c4dynamics/sensors/__init__.py +72 -0
- c4dynamics/sensors/lineofsight.py +78 -0
- c4dynamics/sensors/radar.py +740 -0
- c4dynamics/sensors/seeker.py +1030 -0
- c4dynamics/states/__init__.py +327 -0
- c4dynamics/states/lib/__init__.py +320 -0
- c4dynamics/states/lib/datapoint.py +660 -0
- c4dynamics/states/lib/pixelpoint.py +776 -0
- c4dynamics/states/lib/rigidbody.py +677 -0
- c4dynamics/states/state.py +1486 -0
- c4dynamics/utils/__init__.py +44 -0
- c4dynamics/utils/_struct.py +6 -0
- c4dynamics/utils/const.py +130 -0
- c4dynamics/utils/cprint.py +80 -0
- c4dynamics/utils/gen_gif.py +142 -0
- c4dynamics/utils/idx2keys.py +4 -0
- c4dynamics/utils/images_loader.py +63 -0
- c4dynamics/utils/math.py +136 -0
- c4dynamics/utils/plottools.py +140 -0
- c4dynamics/utils/plottracks.py +304 -0
- c4dynamics/utils/printpts.py +36 -0
- c4dynamics/utils/slides_gen.py +64 -0
- c4dynamics/utils/tictoc.py +167 -0
- c4dynamics/utils/video_gen.py +300 -0
- c4dynamics/utils/vidgen.py +182 -0
- c4dynamics-2.0.3.dist-info/METADATA +242 -0
- c4dynamics-2.0.3.dist-info/RECORD +50 -0
- c4dynamics-2.0.3.dist-info/WHEEL +5 -0
- c4dynamics-2.0.3.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,677 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import sys
|
|
3
|
+
sys.path.append('.')
|
|
4
|
+
import c4dynamics as c4d
|
|
5
|
+
|
|
6
|
+
from c4dynamics.states.lib.datapoint import datapoint
|
|
7
|
+
|
|
8
|
+
class rigidbody(datapoint): #
|
|
9
|
+
'''
|
|
10
|
+
A rigid-body object
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
The :class:`rigidbody` extends the :class:`datapoint <c4dynamics.states.lib.datapoint.datapoint>`
|
|
14
|
+
class to form an elementary rigidbody object in space, i.e.
|
|
15
|
+
an object with length and attitude.
|
|
16
|
+
|
|
17
|
+
It introduces attributes related to rotational dynamics,
|
|
18
|
+
such as angular position, angular velocity, and moment of inertia.
|
|
19
|
+
As such its state vector consists of the following variables:
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
.. math::
|
|
23
|
+
|
|
24
|
+
X = [x, y, z, v_x, v_y, v_z, \\varphi, \\theta, \\psi, p, q, r)]^T
|
|
25
|
+
|
|
26
|
+
- Position coordinates, velocity coordinates.
|
|
27
|
+
- Angles, angular rates.
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
**Arguments**
|
|
32
|
+
|
|
33
|
+
x : float or int, optional
|
|
34
|
+
The x-position of the datapoint. Default value :math:`x = 0`.
|
|
35
|
+
y : float or int, optional
|
|
36
|
+
The y-position of the datapoint. Default value :math:`y = 0`.
|
|
37
|
+
z : float or int, optional
|
|
38
|
+
The z-position of the datapoint. Default value :math:`z = 0`.
|
|
39
|
+
vx : float or int, optional
|
|
40
|
+
Component of velocity along the x-axis. Default value :math:`v_x = 0`.
|
|
41
|
+
vy : float or int, optional
|
|
42
|
+
Component of velocity along the y-axis. Default value :math:`v_y = 0`.
|
|
43
|
+
vz : float or int, optional
|
|
44
|
+
Component of velocity along the z-axis. Default value :math:`v_z = 0`.
|
|
45
|
+
phi : float or int, optional
|
|
46
|
+
Euler angle representing rotation around the x-axis (rad). Default value :math:`\\varphi = 0`.
|
|
47
|
+
theta : float or int, optional
|
|
48
|
+
Euler angle representing rotation around the y-axis (rad). Default value :math:`\\theta = 0`.
|
|
49
|
+
psi : float or int, optional
|
|
50
|
+
Euler angle representing rotation around the z-axis (rad). Default value :math:`\\psi = 0`.
|
|
51
|
+
p : float or int, optional
|
|
52
|
+
Angular rate around the x-axis (roll). (rad/sec). Default value :math:`p = 0`.
|
|
53
|
+
q : float or int, optional
|
|
54
|
+
Angular rate around the y-axis (pitch). (rad/sec). Default value :math:`q = 0`.
|
|
55
|
+
r : float or int, optional
|
|
56
|
+
Angular rate around the z-axis (yaw). (rad/sec). Default value :math:`r = 0`.
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
The input arguments determine the initial values of the instance.
|
|
60
|
+
The vector of initial conditions can be retrieved by calling
|
|
61
|
+
:attr:`rigidbody.X0 <c4dynamics.states.state.state.X0>`:
|
|
62
|
+
|
|
63
|
+
.. code::
|
|
64
|
+
|
|
65
|
+
>>> from c4dynamics import rigidbody
|
|
66
|
+
|
|
67
|
+
.. code::
|
|
68
|
+
|
|
69
|
+
>>> rb = rigidbody(z = 1000, theta = 10 * c4d.d2r, q = 0.5 * c4d.d2r)
|
|
70
|
+
>>> rb.X0 # doctest: +NUMPY_FORMAT
|
|
71
|
+
[0 0 1000 0 0 0 0 0.174 0 0 0.0087 0]
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
When the initial values are not known at the stage of constructing
|
|
75
|
+
the state object, it's possible to pass zeros and override them later
|
|
76
|
+
by direct assignment of the state variable with a `0` suffix.
|
|
77
|
+
See more at :attr:`state.X0 <c4dynamics.states.state.state.X0>`.
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
Parameters
|
|
81
|
+
==========
|
|
82
|
+
|
|
83
|
+
mass : float
|
|
84
|
+
The mass of the datapoint
|
|
85
|
+
I : [float, float, float]
|
|
86
|
+
An array of moments of inertia
|
|
87
|
+
|
|
88
|
+
See Also
|
|
89
|
+
========
|
|
90
|
+
.lib
|
|
91
|
+
.rotmat
|
|
92
|
+
.state
|
|
93
|
+
.eqm
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
Example
|
|
97
|
+
=======
|
|
98
|
+
|
|
99
|
+
A simplified model of an aircraft autopilot is given by:
|
|
100
|
+
|
|
101
|
+
.. math::
|
|
102
|
+
|
|
103
|
+
\\dot{z}(t) = 5 \\cdot \\theta(t)
|
|
104
|
+
|
|
105
|
+
\\dot{\\theta}(t) = -0.5 \\cdot \\theta(t) - 0.1 \\cdot z(t)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
Where:
|
|
109
|
+
|
|
110
|
+
- :math:`z` is the deviation of the aircraft from the required altitude
|
|
111
|
+
- :math:`\\theta` is the pitch angle
|
|
112
|
+
|
|
113
|
+
The aircraft is represented by a `rigidbody` object.
|
|
114
|
+
`scipy's odeint` is employed to solve the
|
|
115
|
+
dynamics equations and update the state vector `X`.
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
import required packages:
|
|
119
|
+
|
|
120
|
+
.. code::
|
|
121
|
+
|
|
122
|
+
>>> import c4dynamics as c4d
|
|
123
|
+
>>> from matplotlib import pyplot as plt
|
|
124
|
+
>>> from scipy.integrate import odeint
|
|
125
|
+
>>> import numpy as np
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
Settings and initial conditions:
|
|
129
|
+
|
|
130
|
+
.. code::
|
|
131
|
+
|
|
132
|
+
>>> dt, tf = 0.01, 15
|
|
133
|
+
>>> tspan = np.arange(0, tf, dt)
|
|
134
|
+
>>> A = np.zeros((12, 12))
|
|
135
|
+
>>> A[2, 7] = 5
|
|
136
|
+
>>> A[7, 2] = -0.1
|
|
137
|
+
>>> A[7, 7] = -0.5
|
|
138
|
+
>>> f16 = c4d.rigidbody(z = 1, theta = 0)
|
|
139
|
+
>>> for t in tspan:
|
|
140
|
+
... f16.X = odeint(lambda y, t: A @ y, f16.X, [t, t + dt])[-1]
|
|
141
|
+
... f16.store(t)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
.. code::
|
|
145
|
+
|
|
146
|
+
>>> _, ax = plt.subplots(2, 1)
|
|
147
|
+
>>> f16.plot('z', ax = ax[0]) # doctest: +IGNORE_OUTPUT
|
|
148
|
+
>>> ax[0].set(xlabel = '') # doctest: +IGNORE_OUTPUT
|
|
149
|
+
>>> f16.plot('theta', ax = ax[1], scale = c4d.r2d)
|
|
150
|
+
>>> plt.show()
|
|
151
|
+
|
|
152
|
+
.. figure:: /_examples/rigidbody/intro_f16_autopilot.png
|
|
153
|
+
|
|
154
|
+
The :meth:`animate` method allows the user to play the attitude
|
|
155
|
+
histories given a 3D model (requires installation of `open3D`).
|
|
156
|
+
|
|
157
|
+
The model in the example can be fetched using the c4dynamics datasets module
|
|
158
|
+
(see :mod:`c4dynamics.datasets`):
|
|
159
|
+
|
|
160
|
+
.. code::
|
|
161
|
+
|
|
162
|
+
>>> modelpath = c4d.datasets.d3_model('f16')
|
|
163
|
+
Fetched successfully
|
|
164
|
+
|
|
165
|
+
.. code::
|
|
166
|
+
|
|
167
|
+
>>> f16colors = np.vstack(([255, 215, 0], [255, 215, 0], [184, 134, 11], [0, 32, 38], [218, 165, 32], [218, 165, 32], [54, 69, 79], [205, 149, 12], [205, 149, 12])) / 255
|
|
168
|
+
>>> f16.animate(modelpath, angle0 = [90 * c4d.d2r, 0, 180 * c4d.d2r], modelcolor = f16colors)
|
|
169
|
+
|
|
170
|
+
.. figure:: /_examples/rigidbody/rb_intro_ap.gif
|
|
171
|
+
|
|
172
|
+
'''
|
|
173
|
+
phi: float
|
|
174
|
+
theta: float
|
|
175
|
+
psi: float
|
|
176
|
+
p: float
|
|
177
|
+
q: float
|
|
178
|
+
r: float
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
_ixx = 0
|
|
182
|
+
_iyy = 0
|
|
183
|
+
_izz = 0
|
|
184
|
+
|
|
185
|
+
def __init__(self, x = 0, y = 0, z = 0, vx = 0, vy = 0, vz = 0
|
|
186
|
+
, phi = 0, theta = 0, psi = 0, p = 0, q = 0, r = 0
|
|
187
|
+
):
|
|
188
|
+
|
|
189
|
+
rbargs = {}
|
|
190
|
+
|
|
191
|
+
rbargs.setdefault('x', x)
|
|
192
|
+
rbargs.setdefault('y', y)
|
|
193
|
+
rbargs.setdefault('z', z)
|
|
194
|
+
rbargs.setdefault('vx', vx)
|
|
195
|
+
rbargs.setdefault('vy', vy)
|
|
196
|
+
rbargs.setdefault('vz', vz)
|
|
197
|
+
|
|
198
|
+
rbargs.setdefault('phi', phi)
|
|
199
|
+
rbargs.setdefault('theta', theta)
|
|
200
|
+
rbargs.setdefault('psi', psi)
|
|
201
|
+
rbargs.setdefault('p', p)
|
|
202
|
+
rbargs.setdefault('q', q)
|
|
203
|
+
rbargs.setdefault('r', r)
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
c4d.state.__init__(self, **rbargs)
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
@property
|
|
210
|
+
def I(self):
|
|
211
|
+
'''
|
|
212
|
+
Gets and sets the array of moments of inertia.
|
|
213
|
+
|
|
214
|
+
.. math::
|
|
215
|
+
|
|
216
|
+
I = [I_{xx}, I_{yy}, I_{zz}]
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
Default: :math:`I = [0, 0, 0]`
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
Parameters
|
|
223
|
+
----------
|
|
224
|
+
I : numpy.array or list
|
|
225
|
+
An array of three moments of inertia about each
|
|
226
|
+
one of the axes :math:`([I_{xx}, I_{yy}, I_{zz}])`.
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
Returns
|
|
230
|
+
-------
|
|
231
|
+
out : numpy.array
|
|
232
|
+
An array of the three moments of inertia :math:`[I_{xx}, I_{yy}, I_{zz}]`.
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
Example
|
|
236
|
+
-------
|
|
237
|
+
|
|
238
|
+
The moment of inertia
|
|
239
|
+
determines how much torque is required for a
|
|
240
|
+
desired angular acceleration about a rotational axis.
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
In this example, two physical pendulums with the same
|
|
244
|
+
initial conditions
|
|
245
|
+
show the effect of different moments of inertia
|
|
246
|
+
on the time period of an oscillation:
|
|
247
|
+
|
|
248
|
+
.. math::
|
|
249
|
+
|
|
250
|
+
T = 2 \\cdot \\pi \\cdot \\sqrt{{I \\over m \\cdot g \\cdot l}}
|
|
251
|
+
|
|
252
|
+
where here :math:`m` is the mass :math:`m = 1`,
|
|
253
|
+
:math:`l` is the length from the center of mass :math:`l = 1`,
|
|
254
|
+
:math:`g` is the gravity acceleration,
|
|
255
|
+
and :math:`I` is the moment of inertia about :math:`y`, :math:`I_{yy1} = 0.5, I_{yy2} = 0.05`
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
Import required packages:
|
|
259
|
+
|
|
260
|
+
.. code::
|
|
261
|
+
|
|
262
|
+
>>> import c4dynamics as c4d
|
|
263
|
+
>>> from matplotlib import pyplot as plt
|
|
264
|
+
>>> from scipy.integrate import odeint
|
|
265
|
+
>>> import numpy as np
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
Settings and initial condtions:
|
|
269
|
+
|
|
270
|
+
.. code::
|
|
271
|
+
|
|
272
|
+
>>> b = 0.5
|
|
273
|
+
>>> dt = 0.01
|
|
274
|
+
>>> g = c4d.g_ms2
|
|
275
|
+
>>> theta0 = 80 * c4d.d2r
|
|
276
|
+
>>> rb05 = c4d.rigidbody(theta = theta0)
|
|
277
|
+
>>> rb05.I = [0, .5, 0]
|
|
278
|
+
>>> rb005 = c4d.rigidbody(theta = theta0)
|
|
279
|
+
>>> rb005.I = [0, .05, 0]
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
Physical pendulum dynamics:
|
|
284
|
+
|
|
285
|
+
.. code::
|
|
286
|
+
|
|
287
|
+
>>> def pendulum(yin, t, Iyy):
|
|
288
|
+
... theta, q = yin[7], yin[10]
|
|
289
|
+
... yout = np.zeros(12)
|
|
290
|
+
... yout[7] = q
|
|
291
|
+
... yout[10] = -g * c4d.sin(theta) / Iyy - b * q
|
|
292
|
+
... return yout
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
Main loop
|
|
297
|
+
|
|
298
|
+
.. code::
|
|
299
|
+
|
|
300
|
+
>>> for ti in np.arange(0, 5, dt):
|
|
301
|
+
... # Iyy = 0.5
|
|
302
|
+
... rb05.X = odeint(pendulum, rb05.X, [ti, ti + dt], (rb05.I[1],))[1]
|
|
303
|
+
... rb05.store(ti)
|
|
304
|
+
... # Iyy = 0.05
|
|
305
|
+
... rb005.X = odeint(pendulum, rb005.X, [ti, ti + dt], (rb005.I[1],))[1]
|
|
306
|
+
... rb005.store(ti)
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
Plot results:
|
|
310
|
+
|
|
311
|
+
.. code::
|
|
312
|
+
|
|
313
|
+
>>> rb05.plot('theta')
|
|
314
|
+
>>> rb005.plot('theta', ax = plt.gca(), color = 'c')
|
|
315
|
+
>>> plt.show()
|
|
316
|
+
|
|
317
|
+
.. figure:: /_examples/rigidbody/Iyy_pendulum.png
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
'''
|
|
321
|
+
return np.array([self._ixx, self._iyy, self._izz])
|
|
322
|
+
|
|
323
|
+
@I.setter
|
|
324
|
+
def I(self, I):
|
|
325
|
+
self._ixx = I[0]
|
|
326
|
+
self._iyy = I[1]
|
|
327
|
+
self._izz = I[2]
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
@property
|
|
331
|
+
def angles(self):
|
|
332
|
+
'''
|
|
333
|
+
Returns an array of Euler angles.
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
.. math::
|
|
337
|
+
|
|
338
|
+
angles = [\\varphi, \\theta, \\psi]
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
Returns
|
|
343
|
+
-------
|
|
344
|
+
out : numpy.array
|
|
345
|
+
An array of three Euler angles, about each one of the axes
|
|
346
|
+
:math:`([\\varphi, \\theta, \\psi])`
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
Examples
|
|
350
|
+
--------
|
|
351
|
+
|
|
352
|
+
.. code::
|
|
353
|
+
|
|
354
|
+
>>> rb = c4d.rigidbody(phi = 135)
|
|
355
|
+
>>> rb.angles # doctest: +NUMPY_FORMAT
|
|
356
|
+
[135 0 0]
|
|
357
|
+
|
|
358
|
+
'''
|
|
359
|
+
|
|
360
|
+
return np.array([self.phi, self.theta, self.psi])
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
@property
|
|
364
|
+
def ang_rates(self):
|
|
365
|
+
'''
|
|
366
|
+
Returns an array of angular rates.
|
|
367
|
+
|
|
368
|
+
.. math::
|
|
369
|
+
|
|
370
|
+
angular rates = [p, q, r]
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
Returns
|
|
375
|
+
-------
|
|
376
|
+
out : numpy.array
|
|
377
|
+
An array of three angular rates of the body axes
|
|
378
|
+
:math:`([p, q, r])`
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
Examples
|
|
382
|
+
--------
|
|
383
|
+
|
|
384
|
+
.. code::
|
|
385
|
+
|
|
386
|
+
>>> q0 = 30
|
|
387
|
+
>>> rb = c4d.rigidbody(q = q0)
|
|
388
|
+
>>> rb.ang_rates # doctest: +NUMPY_FORMAT
|
|
389
|
+
[0 30 0]
|
|
390
|
+
|
|
391
|
+
'''
|
|
392
|
+
return np.array([self.p, self.q, self.r])
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
@property
|
|
396
|
+
def BR(self):
|
|
397
|
+
'''
|
|
398
|
+
Returns a Body-from-Reference Direction Cosine Matrix (DCM).
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
Based on the current Euler angles, `BR` returns the DCM in a 3-2-1 order,
|
|
402
|
+
i.e. first rotation about the z axis (yaw, :math:`\\psi`), then a rotation about the
|
|
403
|
+
y axis (pitch, :math:`\\theta`), and finally a rotation about the x axis (roll, :math:`\\varphi`).
|
|
404
|
+
|
|
405
|
+
The `DCM321` matrix is calculated by the
|
|
406
|
+
:mod:`rotmat <c4dynamics.rotmat>` module and is given by:
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
.. math::
|
|
410
|
+
|
|
411
|
+
R = \\begin{bmatrix}
|
|
412
|
+
c\\theta \\cdot c\\psi
|
|
413
|
+
& c\\theta \\cdot s\\psi
|
|
414
|
+
& -s\\theta \\\\
|
|
415
|
+
s\\varphi \\cdot s\\theta \\cdot c\\psi - c\\varphi \\cdot s\\psi
|
|
416
|
+
& s\\varphi \\cdot s\\theta \\cdot s\\psi + c\\varphi \\cdot c\\psi
|
|
417
|
+
& s\\varphi \\cdot c\\theta \\\\
|
|
418
|
+
c\\varphi \\cdot s\\theta \\cdot c\\psi + s\\varphi \\cdot s\\psi
|
|
419
|
+
& c\\varphi \\cdot s\\theta \\cdot s\\psi - s\\varphi \\cdot c\\psi
|
|
420
|
+
& c\\varphi \\cdot c\\theta
|
|
421
|
+
\\end{bmatrix}
|
|
422
|
+
|
|
423
|
+
where
|
|
424
|
+
|
|
425
|
+
- :math:`c\\varphi \\equiv cos(\\varphi)`
|
|
426
|
+
- :math:`s\\varphi \\equiv sin(\\varphi)`
|
|
427
|
+
- :math:`c\\theta \\equiv cos(\\theta)`
|
|
428
|
+
- :math:`s\\theta \\equiv sin(\\theta)`
|
|
429
|
+
- :math:`c\\psi \\equiv cos(\\psi)`
|
|
430
|
+
- :math:`s\\psi \\equiv sin(\\psi)`
|
|
431
|
+
|
|
432
|
+
|
|
433
|
+
|
|
434
|
+
For the background material regarding the rotational matrix operations,
|
|
435
|
+
see :mod:`rotmat <c4dynamics.rotmat>`.
|
|
436
|
+
|
|
437
|
+
Returns
|
|
438
|
+
-------
|
|
439
|
+
|
|
440
|
+
out : numpy.ndarray
|
|
441
|
+
A 3x3 DCM matrix uses to rotate a vector
|
|
442
|
+
to the body frame
|
|
443
|
+
from a reference frame of coordinates.
|
|
444
|
+
|
|
445
|
+
|
|
446
|
+
Example
|
|
447
|
+
-------
|
|
448
|
+
|
|
449
|
+
.. code::
|
|
450
|
+
|
|
451
|
+
>>> v_inertial = [1, 0, 0]
|
|
452
|
+
>>> rb = c4d.rigidbody(psi = 45 * c4d.d2r)
|
|
453
|
+
>>> v_body = rb.BR @ v_inertial
|
|
454
|
+
>>> v_body # doctest: +NUMPY_FORMAT
|
|
455
|
+
[0.707 -0.707 0.0]
|
|
456
|
+
|
|
457
|
+
'''
|
|
458
|
+
|
|
459
|
+
return c4d.rotmat.dcm321(phi = self.phi, theta = self.theta, psi = self.psi)
|
|
460
|
+
|
|
461
|
+
|
|
462
|
+
@property
|
|
463
|
+
def RB(self):
|
|
464
|
+
'''
|
|
465
|
+
Returns a Reference-from-Body Direction Cosine Matrix (DCM).
|
|
466
|
+
|
|
467
|
+
|
|
468
|
+
Based on the current Euler angles, `RB` returns the
|
|
469
|
+
transpose matrix of :attr:`BR <c4dynamics.states.lib.rigidbody.rigidbody.BR>`,
|
|
470
|
+
where :attr:`BR <c4dynamics.states.lib.rigidbody.rigidbody.BR>`
|
|
471
|
+
is the Body from Reference
|
|
472
|
+
DCM in 3-2-1 order.
|
|
473
|
+
|
|
474
|
+
The transpose matrix of the DCM generated by
|
|
475
|
+
three Euler angles :math:`\\varphi` (rotation about `x`),
|
|
476
|
+
:math:`\\theta` (about `y`), and :math:`\\psi` (about `z`) in 3-2-1 order,
|
|
477
|
+
is given by:
|
|
478
|
+
|
|
479
|
+
.. math::
|
|
480
|
+
|
|
481
|
+
R = \\begin{bmatrix}
|
|
482
|
+
c\\theta \\cdot c\\psi
|
|
483
|
+
& s\\varphi \\cdot s\\theta \\cdot c\\psi - c\\varphi \\cdot s\\psi
|
|
484
|
+
& c\\varphi \\cdot s\\theta \\cdot c\\psi + s\\varphi \\cdot s\\psi \\\\
|
|
485
|
+
c\\theta \\cdot s\\psi
|
|
486
|
+
& s\\varphi \\cdot s\\theta \\cdot s\\psi + c\\varphi \\cdot c\\psi
|
|
487
|
+
& c\\varphi \\cdot s\\theta \\cdot s\\psi - s\\varphi \\cdot c\\psi \\\\
|
|
488
|
+
-s\\theta
|
|
489
|
+
& s\\varphi \\cdot c\\theta
|
|
490
|
+
& c\\varphi \\cdot c\\theta
|
|
491
|
+
\\end{bmatrix}
|
|
492
|
+
|
|
493
|
+
where
|
|
494
|
+
|
|
495
|
+
- :math:`c\\varphi \\equiv cos(\\varphi)`
|
|
496
|
+
- :math:`s\\varphi \\equiv sin(\\varphi)`
|
|
497
|
+
- :math:`c\\theta \\equiv cos(\\theta)`
|
|
498
|
+
- :math:`s\\theta \\equiv sin(\\theta)`
|
|
499
|
+
- :math:`c\\psi \\equiv cos(\\psi)`
|
|
500
|
+
- :math:`s\\psi \\equiv sin(\\psi)`
|
|
501
|
+
|
|
502
|
+
|
|
503
|
+
For the background material regarding the rotational matrix operations,
|
|
504
|
+
see :mod:`rotmat <c4dynamics.rotmat>`.
|
|
505
|
+
|
|
506
|
+
|
|
507
|
+
|
|
508
|
+
Returns
|
|
509
|
+
-------
|
|
510
|
+
|
|
511
|
+
out : numpy.ndarray
|
|
512
|
+
A 3x3 DCM matrix uses to rotate a vector from a body frame
|
|
513
|
+
to a reference frame of coordinates.
|
|
514
|
+
|
|
515
|
+
|
|
516
|
+
Example
|
|
517
|
+
-------
|
|
518
|
+
|
|
519
|
+
.. code::
|
|
520
|
+
|
|
521
|
+
>>> v_body = [np.sqrt(3), 0, 1]
|
|
522
|
+
>>> rb = c4d.rigidbody(theta = 30 * c4d.d2r)
|
|
523
|
+
>>> v_inertial = rb.RB @ v_body
|
|
524
|
+
>>> v_inertial # doctest: +NUMPY_FORMAT
|
|
525
|
+
[2.0 0.0 0.0]
|
|
526
|
+
|
|
527
|
+
'''
|
|
528
|
+
return np.transpose(self.BR)
|
|
529
|
+
|
|
530
|
+
|
|
531
|
+
def inteqm(self, forces, moments, dt): # type: ignore
|
|
532
|
+
'''
|
|
533
|
+
Advances the state vector, :attr:`rigidbody.X <c4dynamics.states.state.state.X>`,
|
|
534
|
+
with respect to the input
|
|
535
|
+
forces and moments on a single step of time, `dt`.
|
|
536
|
+
|
|
537
|
+
Integrates equations of six degrees motion using the Runge-Kutta method.
|
|
538
|
+
|
|
539
|
+
This method numerically integrates the equations of motion for a dynamic system
|
|
540
|
+
using the fourth-order Runge-Kutta method as given by
|
|
541
|
+
:func:`eqm.int6 <c4dynamics.eqm.integrate.int6>`.
|
|
542
|
+
|
|
543
|
+
The derivatives of the equations are of six dimensional motion as
|
|
544
|
+
given by
|
|
545
|
+
:py:func:`eqm.eqm6 <c4dynamics.eqm.derivs.eqm6>`.
|
|
546
|
+
|
|
547
|
+
|
|
548
|
+
Parameters
|
|
549
|
+
----------
|
|
550
|
+
forces : numpy.array or list
|
|
551
|
+
An external forces vector acting on the body, `forces = [Fx, Fy, Fz]`
|
|
552
|
+
moments : numpy.array or list
|
|
553
|
+
An external moments vector acting on the body, `moments = [Mx, My, Mz]`
|
|
554
|
+
dt : float or int
|
|
555
|
+
Interval time step for integration.
|
|
556
|
+
|
|
557
|
+
|
|
558
|
+
Returns
|
|
559
|
+
-------
|
|
560
|
+
out : numpy.float64
|
|
561
|
+
An acceleration array at the final time step.
|
|
562
|
+
|
|
563
|
+
|
|
564
|
+
Warning
|
|
565
|
+
-------
|
|
566
|
+
This method is not recommanded when the vectors
|
|
567
|
+
of forces or moments depend on the state variables.
|
|
568
|
+
Since the vectors of forces and moments are provided once at the
|
|
569
|
+
entrance to the integration, they remain constant
|
|
570
|
+
for the entire steps.
|
|
571
|
+
Therefore, when the forces or moments depend on the state variables
|
|
572
|
+
the results of this method are not accurate and may lead to instability.
|
|
573
|
+
|
|
574
|
+
|
|
575
|
+
Examples
|
|
576
|
+
--------
|
|
577
|
+
A torque is applied by spacecraft thrusters to stabilize
|
|
578
|
+
the roll in a constant rate.
|
|
579
|
+
|
|
580
|
+
Import required packages:
|
|
581
|
+
|
|
582
|
+
.. code::
|
|
583
|
+
|
|
584
|
+
>>> import c4dynamics as c4d
|
|
585
|
+
>>> from matplotlib import pyplot as plt
|
|
586
|
+
>>> import numpy as np
|
|
587
|
+
|
|
588
|
+
|
|
589
|
+
Settings and initial conditions:
|
|
590
|
+
|
|
591
|
+
.. code::
|
|
592
|
+
|
|
593
|
+
>>> dt = 0.001
|
|
594
|
+
>>> torque = [0.1, 0, 0]
|
|
595
|
+
>>> rb = c4d.rigidbody()
|
|
596
|
+
>>> rb.I = [0.5, 0, 0] # Moment of inertia about x
|
|
597
|
+
|
|
598
|
+
|
|
599
|
+
Main loop:
|
|
600
|
+
|
|
601
|
+
.. code::
|
|
602
|
+
|
|
603
|
+
>>> for ti in np.arange(0, 5, dt):
|
|
604
|
+
... rb.inteqm(np.zeros(3), torque, dt) # doctest: +IGNORE_OUTPUT
|
|
605
|
+
... if rb.p >= 10 * c4d.d2r:
|
|
606
|
+
... torque = [0, 0, 0]
|
|
607
|
+
... rb.store(ti)
|
|
608
|
+
|
|
609
|
+
|
|
610
|
+
Plot results:
|
|
611
|
+
|
|
612
|
+
.. code::
|
|
613
|
+
|
|
614
|
+
>>> rb.plot('p')
|
|
615
|
+
>>> plt.show()
|
|
616
|
+
|
|
617
|
+
.. figure:: /_examples/rigidbody/inteqm_rollstable.png
|
|
618
|
+
|
|
619
|
+
|
|
620
|
+
'''
|
|
621
|
+
# from c4dynamics.eqm import eqm6
|
|
622
|
+
self.X, acc = c4d.eqm.int6(self, forces, moments, dt, derivs_out = True)
|
|
623
|
+
return acc
|
|
624
|
+
|
|
625
|
+
|
|
626
|
+
def animate(self, modelpath, angle0 = [0, 0, 0]
|
|
627
|
+
, modelcolor = None, dt = 1e-3
|
|
628
|
+
, savedir = None, cbackground = [1, 1, 1]):
|
|
629
|
+
|
|
630
|
+
c4d.rotmat.animate(self, modelpath, angle0, modelcolor, dt, savedir, cbackground)
|
|
631
|
+
|
|
632
|
+
|
|
633
|
+
|
|
634
|
+
rigidbody.animate.__doc__ = c4d.rotmat.animate.__doc__
|
|
635
|
+
|
|
636
|
+
|
|
637
|
+
|
|
638
|
+
if __name__ == "__main__":
|
|
639
|
+
|
|
640
|
+
import doctest, contextlib, os
|
|
641
|
+
from c4dynamics import IgnoreOutputChecker, cprint
|
|
642
|
+
|
|
643
|
+
# Register the custom OutputChecker
|
|
644
|
+
doctest.OutputChecker = IgnoreOutputChecker
|
|
645
|
+
|
|
646
|
+
tofile = False
|
|
647
|
+
optionflags = doctest.FAIL_FAST
|
|
648
|
+
|
|
649
|
+
|
|
650
|
+
# Filter out tests for the animate method by overriding the testmod.
|
|
651
|
+
def custom_testmod(module=None, **kwargs):
|
|
652
|
+
tests = doctest.DocTestFinder().find(module)
|
|
653
|
+
|
|
654
|
+
tests = [test for test in tests if test.name != "__main__.rigidbody.animate"] # Exclude animate
|
|
655
|
+
|
|
656
|
+
runner = doctest.DocTestRunner(**kwargs)
|
|
657
|
+
for test in tests:
|
|
658
|
+
runner.run(test)
|
|
659
|
+
return runner.summarize()
|
|
660
|
+
|
|
661
|
+
|
|
662
|
+
|
|
663
|
+
if tofile:
|
|
664
|
+
with open(os.path.join('tests', '_out', 'output.txt'), 'w') as f:
|
|
665
|
+
with contextlib.redirect_stdout(f), contextlib.redirect_stderr(f):
|
|
666
|
+
result = custom_testmod(sys.modules[__name__], optionflags=optionflags)
|
|
667
|
+
else:
|
|
668
|
+
result = custom_testmod(sys.modules[__name__], optionflags=optionflags)
|
|
669
|
+
|
|
670
|
+
if result.failed == 0:
|
|
671
|
+
cprint(os.path.basename(__file__) + ": all tests passed!", 'g')
|
|
672
|
+
else:
|
|
673
|
+
print(f"{result.failed}")
|
|
674
|
+
|
|
675
|
+
|
|
676
|
+
|
|
677
|
+
|