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,1486 @@
|
|
|
1
|
+
import os, sys
|
|
2
|
+
import numpy as np
|
|
3
|
+
sys.path.append('.')
|
|
4
|
+
import c4dynamics as c4d
|
|
5
|
+
from numpy.typing import NDArray
|
|
6
|
+
from typing import Any
|
|
7
|
+
import warnings
|
|
8
|
+
|
|
9
|
+
class state:
|
|
10
|
+
'''
|
|
11
|
+
Custom state object.
|
|
12
|
+
|
|
13
|
+
A state object represents a state vector and other attributes
|
|
14
|
+
that form an entity of a physical (dynamic) system.
|
|
15
|
+
|
|
16
|
+
A custom state object means any set of state variables is possible,
|
|
17
|
+
while pre-defined states from the :mod:`state library<c4dynamics.states.lib>`
|
|
18
|
+
are ready to use out of the box.
|
|
19
|
+
|
|
20
|
+
Keyword Arguments
|
|
21
|
+
=================
|
|
22
|
+
|
|
23
|
+
**kwargs : float or int
|
|
24
|
+
Keyword arguments representing the variables and their initial conditions.
|
|
25
|
+
Each key is a variable name and each value is its initial condition.
|
|
26
|
+
For example: :code:`s = c4d.state(x = 0, theta = 3.14)`
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
See Also
|
|
30
|
+
========
|
|
31
|
+
.states
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
Examples
|
|
35
|
+
========
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
``Pendulum``
|
|
39
|
+
|
|
40
|
+
.. code::
|
|
41
|
+
|
|
42
|
+
>>> s = c4d.state(theta = 10 * c4d.d2r, omega = 0) # doctest: +IGNORE_OUTPUT
|
|
43
|
+
[ θ ω ]
|
|
44
|
+
|
|
45
|
+
``Strapdown navigation system``
|
|
46
|
+
|
|
47
|
+
.. code::
|
|
48
|
+
|
|
49
|
+
>>> s = c4d.state(x = 0, y = 0, z = 0, vx = 0, vy = 0, vz = 0, q0 = 0, q1 = 0, q2 = 0, q3 = 0, bax = 0, bay = 0, baz = 0) # doctest: +IGNORE_OUTPUT
|
|
50
|
+
[ x y z vx vy vz q0 q1 q2 q3 bax bay baz ]
|
|
51
|
+
|
|
52
|
+
``Objects tracker``
|
|
53
|
+
|
|
54
|
+
.. code::
|
|
55
|
+
|
|
56
|
+
>>> s = c4d.state(x = 960, y = 540, w = 20, h = 10) # doctest: +IGNORE_OUTPUT
|
|
57
|
+
[ x y w h ]
|
|
58
|
+
|
|
59
|
+
``Aircraft``
|
|
60
|
+
|
|
61
|
+
.. code::
|
|
62
|
+
|
|
63
|
+
>>> s = c4d.state(x = 0, y = 0, z = 0, vx = 0, vy = 0, vz = 0, phi = 0, theta = 0, psi = 0, p = 0, q = 0, r = 0) # doctest: +IGNORE_OUTPUT
|
|
64
|
+
[ x y z vx vy vz φ θ Ψ p q r ]
|
|
65
|
+
|
|
66
|
+
``Self-driving car``
|
|
67
|
+
|
|
68
|
+
.. code::
|
|
69
|
+
|
|
70
|
+
>>> s = c4d.state(x = 0, y = 0, v = 0, theta = 0, omega = 0) # doctest: +IGNORE_OUTPUT
|
|
71
|
+
[ x y v θ ω ]
|
|
72
|
+
|
|
73
|
+
``Robot arm``
|
|
74
|
+
|
|
75
|
+
.. code::
|
|
76
|
+
|
|
77
|
+
>>> s = c4d.state(theta1 = 0, theta2 = 0, omega1 = 0, omega2 = 0) # doctest: +IGNORE_OUTPUT
|
|
78
|
+
[ θ1 θ2 ω1 ω2 ]
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
'''
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
# Α α # Β β # Γ γ # Δ δ # Ε ε # Ζ ζ # Η η # Θ θ # Ι ι
|
|
85
|
+
# Κ κ # Λ λ # Μ μ # Ν ν # Ξ ξ # Ο ο # Π π # Ρ ρ # Σ σ/ς
|
|
86
|
+
# Τ τ # Υ υ # Φ φ # Χ χ # Ψ ψ # Ω ω
|
|
87
|
+
_greek_unicode = (
|
|
88
|
+
('alpha', '\u03B1'), ('beta', '\u03B2'), ('gamma', '\u03B3'), ('delta', '\u03B4'),
|
|
89
|
+
('epsilon', '\u03B5'), ('zeta', '\u03B6'), ('eta', '\u03B7'), ('theta', '\u03B8'),
|
|
90
|
+
('iota', '\u03B9'), ('kappa', '\u03BA'), ('lambda', '\u03BB'), ('mu', '\u03BC'),
|
|
91
|
+
('nu', '\u03BD'), ('xi', '\u03BE'), ('omicron', '\u03BF'), ('pi', '\u03C0'),
|
|
92
|
+
('rho', '\u03C1'), ('sigma', '\u03C3'), ('final_sigma', '\u03C2'), ('tau', '\u03C4'),
|
|
93
|
+
('upsilon', '\u03C5'), ('phi', '\u03C6'), ('chi', '\u03C7'), ('psi', '\u03C8'),
|
|
94
|
+
('omega', '\u03C9'), ('Alpha', '\u0391'), ('Beta', '\u0392'), ('Gamma', '\u0393'),
|
|
95
|
+
('Delta', '\u0394'), ('Epsilon', '\u0395'), ('Zeta', '\u0396'), ('Eta', '\u0397'),
|
|
96
|
+
('Theta', '\u0398'), ('Iota', '\u0399'), ('Kappa', '\u039A'), ('Lambda', '\u039B'),
|
|
97
|
+
('Mu', '\u039C'), ('Nu', '\u039D'), ('Xi', '\u039E'), ('Omicron', '\u039F'),
|
|
98
|
+
('Pi', '\u03A0'), ('Rho', '\u03A1'), ('Sigma', '\u03A3'), ('Tau', '\u03A4'),
|
|
99
|
+
('Upsilon', '\u03A5'), ('Phi', '\u03A6'), ('Chi', '\u03A7'), ('Psi', '\u03A8'), ('Omega', '\u03A9'))
|
|
100
|
+
#
|
|
101
|
+
_reserved_keys = ('X', 'X0', 'P', 'V')
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def __init__(self, **kwargs):
|
|
105
|
+
# TODO enable providing type for the setter.X output.
|
|
106
|
+
|
|
107
|
+
# the problem with this is it cannot be used with seeker and other c4d objects
|
|
108
|
+
# that takes datapoint objects.
|
|
109
|
+
# beacuse sometimes it misses attributes such as y, z that are necessary for poistion etc
|
|
110
|
+
|
|
111
|
+
# alternatives:
|
|
112
|
+
|
|
113
|
+
# 1. all the attributes always exist but they are muted and most importantly not reflected
|
|
114
|
+
# in the state vector when doing dp.X
|
|
115
|
+
|
|
116
|
+
# 2. they will not be used in this fucntions.
|
|
117
|
+
|
|
118
|
+
# 3. the user must provide his implementations for poisiton velcity etc.. like used in the P() function that there i
|
|
119
|
+
# encountered the problem.
|
|
120
|
+
|
|
121
|
+
self._data = [] # for permanent class variables (t, x, y .. )
|
|
122
|
+
self._prmdata = {} # for user additional variables
|
|
123
|
+
|
|
124
|
+
self._didx = {'t': 0}
|
|
125
|
+
|
|
126
|
+
for i, (k, v) in enumerate(kwargs.items()):
|
|
127
|
+
if k in self._reserved_keys:
|
|
128
|
+
raise ValueError(f"{k} is a reserved key. Keys {self._reserved_keys} cannot use as variable names.")
|
|
129
|
+
setattr(self, k, v)
|
|
130
|
+
setattr(self, k + '0', v)
|
|
131
|
+
self._didx[k] = 1 + i
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def __str__(self):
|
|
135
|
+
|
|
136
|
+
self_str = '[ '
|
|
137
|
+
for i, s in enumerate(self._didx.keys()):
|
|
138
|
+
if s == 't': continue
|
|
139
|
+
s = dict(self._greek_unicode).get(s, s)
|
|
140
|
+
if i < len(self._didx.keys()) - 1:
|
|
141
|
+
self_str += s + ' '
|
|
142
|
+
else:
|
|
143
|
+
self_str += s + ' ]'
|
|
144
|
+
|
|
145
|
+
return self_str
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def __repr__(self):
|
|
149
|
+
# NOTE i think maybe to switch repr and str so
|
|
150
|
+
# when i print >>> s it show the variables and when
|
|
151
|
+
# i do >>> print(s) it show the entire description.
|
|
152
|
+
# but then i need to iterate all the examples and remove the print from state presentations
|
|
153
|
+
param_names = ", ".join(self._prmdata.keys())
|
|
154
|
+
|
|
155
|
+
return (f"<state object>\n"
|
|
156
|
+
f"State Variables: {self.__str__()}\n"
|
|
157
|
+
f"Initial Conditions (X0): {self.X0}\n"
|
|
158
|
+
f"Current State Vector (X): {self.X}\n"
|
|
159
|
+
f"Parameters: {param_names if param_names else 'None'}")
|
|
160
|
+
|
|
161
|
+
# def __setattr__(self, param, value):
|
|
162
|
+
# if param in self._reserved_keys:
|
|
163
|
+
# raise AttributeError(f"{param} is a reserved key. Keys {self._reserved_keys} cannot use as parameter names.")
|
|
164
|
+
# else:
|
|
165
|
+
# super().__setattr__(param, value)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
#
|
|
170
|
+
# state operations
|
|
171
|
+
##
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
@property
|
|
175
|
+
def X(self) -> NDArray[Any]:
|
|
176
|
+
'''
|
|
177
|
+
Gets and sets the state vector variables.
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
Parameters
|
|
181
|
+
----------
|
|
182
|
+
x : array_like
|
|
183
|
+
Values vector to set the variables of the state.
|
|
184
|
+
|
|
185
|
+
Returns
|
|
186
|
+
-------
|
|
187
|
+
out : numpy.array
|
|
188
|
+
Values vector of the state.
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
Examples
|
|
193
|
+
--------
|
|
194
|
+
|
|
195
|
+
Getter:
|
|
196
|
+
|
|
197
|
+
.. code::
|
|
198
|
+
|
|
199
|
+
>>> s = c4d.state(x1 = 0, x2 = -1)
|
|
200
|
+
>>> s.X # doctest: +NUMPY_FORMAT
|
|
201
|
+
[0 -1]
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
Setter:
|
|
205
|
+
|
|
206
|
+
.. code::
|
|
207
|
+
|
|
208
|
+
>>> s = c4d.state(x1 = 0, x2 = -1)
|
|
209
|
+
>>> s.X += [0, 1] # equivalent to: s.X = s.X + [0, 1]
|
|
210
|
+
>>> s.X # doctest: +NUMPY_FORMAT
|
|
211
|
+
[0 0]
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
:class:`datapoint <c4dynamics.states.lib.datapoint.datapoint>` getter - setter:
|
|
215
|
+
|
|
216
|
+
.. code::
|
|
217
|
+
|
|
218
|
+
>>> dp = c4d.datapoint()
|
|
219
|
+
>>> dp.X # doctest: +NUMPY_FORMAT
|
|
220
|
+
[0 0 0 0 0 0]
|
|
221
|
+
>>> # x y z vx vy vz
|
|
222
|
+
>>> dp.X = [1000, 100, 0, 0, 0, -100]
|
|
223
|
+
>>> dp.X # doctest: +NUMPY_FORMAT
|
|
224
|
+
[1000 100 0 0 0 -100]
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
'''
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
xout = []
|
|
231
|
+
|
|
232
|
+
for k in self._didx.keys():
|
|
233
|
+
if k == 't': continue
|
|
234
|
+
# the alteast_1d() + the flatten() is necessary to
|
|
235
|
+
# cope with non-homogenuous array
|
|
236
|
+
xout.append(np.atleast_1d(eval('self.' + k)))
|
|
237
|
+
|
|
238
|
+
#
|
|
239
|
+
# XXX why float64? maybe it's just some default unlsess anything else required.
|
|
240
|
+
# pixelpoint:override the .X property: will distance the devs from a datapoint class.
|
|
241
|
+
# con: much easier
|
|
242
|
+
#
|
|
243
|
+
# return np.array(xout).flatten().astype(np.float64)
|
|
244
|
+
return np.array(xout).ravel().astype(np.float64)
|
|
245
|
+
|
|
246
|
+
@X.setter
|
|
247
|
+
def X(self, Xin):
|
|
248
|
+
# Xin = np.atleast_1d(Xin).flatten()
|
|
249
|
+
# i think to replace flatten() with ravel() is
|
|
250
|
+
# safe also here because Xin is iterated over its elements which are
|
|
251
|
+
# mutable. but lets keep tracking.
|
|
252
|
+
Xin = np.atleast_1d(Xin).ravel()
|
|
253
|
+
|
|
254
|
+
xlen = len(Xin)
|
|
255
|
+
Xlen = len(self.X)
|
|
256
|
+
|
|
257
|
+
if xlen < Xlen:
|
|
258
|
+
raise ValueError(f'Partial vector assignment, len(Xin) = {xlen}, len(X) = {Xlen}', 'r')
|
|
259
|
+
|
|
260
|
+
elif xlen > Xlen:
|
|
261
|
+
raise ValueError(f'The length of the input state is bigger than X, len(Xin) = {xlen}, len(X) = {Xlen}')
|
|
262
|
+
|
|
263
|
+
for i, k in enumerate(self._didx.keys()):
|
|
264
|
+
if k == 't': continue
|
|
265
|
+
if i > xlen: break
|
|
266
|
+
setattr(self, k, Xin[i - 1])
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
@property
|
|
270
|
+
def X0(self):
|
|
271
|
+
'''
|
|
272
|
+
Returns the initial conditions of the state vector.
|
|
273
|
+
|
|
274
|
+
The initial conditions are determined at the stage of constructing
|
|
275
|
+
the state object.
|
|
276
|
+
Modifying the initial conditions is possible by direct assignment
|
|
277
|
+
of the state variable with a '0' suffix. For a state variable
|
|
278
|
+
:math:`s.x`, its initial condition is modifyied by:
|
|
279
|
+
:code:`s.x0 = x0`, where :code:`x0` is an arbitrary parameter.
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
Returns
|
|
283
|
+
-------
|
|
284
|
+
out : numpy.array
|
|
285
|
+
An array representing the initial values of the state variables.
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
Examples
|
|
289
|
+
--------
|
|
290
|
+
|
|
291
|
+
.. code::
|
|
292
|
+
|
|
293
|
+
>>> s = c4d.state(x1 = 0, x2 = -1)
|
|
294
|
+
>>> s.X += [0, 1]
|
|
295
|
+
>>> s.X0 # doctest: +NUMPY_FORMAT
|
|
296
|
+
[0 -1]
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
.. code::
|
|
300
|
+
|
|
301
|
+
>>> s = c4d.state(x1 = 1, x2 = 1)
|
|
302
|
+
>>> s.X0 # doctest: +NUMPY_FORMAT
|
|
303
|
+
[1 1]
|
|
304
|
+
>>> s.x10 = s.x20 = 0
|
|
305
|
+
>>> s.X0 # doctest: +NUMPY_FORMAT
|
|
306
|
+
[0 0]
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
'''
|
|
310
|
+
xout = []
|
|
311
|
+
|
|
312
|
+
for k in self._didx.keys():
|
|
313
|
+
if k == 't': continue
|
|
314
|
+
xout.append(eval('self.' + k + '0'))
|
|
315
|
+
|
|
316
|
+
return np.array(xout)
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
def addvars(self, **kwargs):
|
|
320
|
+
'''
|
|
321
|
+
Add state variables.
|
|
322
|
+
|
|
323
|
+
Adding variables to the state outside the :class:`state <c4dynamics.states.state.state>`
|
|
324
|
+
constructor is possible by using :meth:`addvars() <c4dynamics.states.state.state.addvars>`.
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
Parameters
|
|
328
|
+
----------
|
|
329
|
+
|
|
330
|
+
**kwargs : float or int
|
|
331
|
+
Keyword arguments representing the variables and their initial conditions.
|
|
332
|
+
Each key is a variable name and each value is its initial condition.
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
Note
|
|
336
|
+
----
|
|
337
|
+
If :meth:`store() <c4dynamics.states.state.state.store>` is called before
|
|
338
|
+
adding the new variables, then the time histories of the new states
|
|
339
|
+
are filled with zeros to maintain the same size as the other state variables.
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
Examples
|
|
344
|
+
--------
|
|
345
|
+
|
|
346
|
+
.. code::
|
|
347
|
+
|
|
348
|
+
>>> s = c4d.state(x = 0, y = 0)
|
|
349
|
+
>>> print(s)
|
|
350
|
+
[ x y ]
|
|
351
|
+
>>> s.addvars(vx = 0, vy = 0)
|
|
352
|
+
>>> print(s)
|
|
353
|
+
[ x y vx vy ]
|
|
354
|
+
|
|
355
|
+
calling :meth:`store() <c4dynamics.states.state.state.store>` before
|
|
356
|
+
adding the new variables:
|
|
357
|
+
|
|
358
|
+
.. code::
|
|
359
|
+
|
|
360
|
+
>>> s = c4d.state(x = 1, y = 1)
|
|
361
|
+
>>> s.store()
|
|
362
|
+
>>> s.store()
|
|
363
|
+
>>> s.store()
|
|
364
|
+
>>> s.addvars(vx = 0, vy = 0)
|
|
365
|
+
>>> s.data('x')[1] # doctest: +NUMPY_FORMAT
|
|
366
|
+
[1 1 1]
|
|
367
|
+
>>> s.data('vx')[1] # doctest: +NUMPY_FORMAT
|
|
368
|
+
[0 0 0]
|
|
369
|
+
|
|
370
|
+
'''
|
|
371
|
+
b0 = len(self._didx)
|
|
372
|
+
|
|
373
|
+
for i, (k, v) in enumerate(kwargs.items()):
|
|
374
|
+
setattr(self, k, v)
|
|
375
|
+
setattr(self, k + '0', v)
|
|
376
|
+
self._didx[k] = b0 + i
|
|
377
|
+
|
|
378
|
+
if self._data:
|
|
379
|
+
# add zero columns at the size of the new vars to avoid wrong broadcasting.
|
|
380
|
+
dataarr = np.array(self._data)
|
|
381
|
+
dataarr = np.hstack((dataarr, np.zeros((dataarr.shape[0], b0))))
|
|
382
|
+
self._data = dataarr.tolist()
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
#
|
|
387
|
+
# data management operations
|
|
388
|
+
##
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
def store(self, t = -1):
|
|
392
|
+
'''
|
|
393
|
+
Stores the current state.
|
|
394
|
+
|
|
395
|
+
The current state is defined by the vector of variables
|
|
396
|
+
as given by :attr:`state.X <c4dynamics.states.state.state.X>`.
|
|
397
|
+
:meth:`store() <c4dynamics.states.state.state.store>` is used to store the
|
|
398
|
+
instantaneous state variables.
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
Parameters
|
|
402
|
+
----------
|
|
403
|
+
t : float or int, optional
|
|
404
|
+
Time stamp for the stored state.
|
|
405
|
+
|
|
406
|
+
Note
|
|
407
|
+
----
|
|
408
|
+
1. Time `t` is an optional parameter with a default value of :math:`t = -1`.
|
|
409
|
+
The time is always appended at the head of the array to store. However,
|
|
410
|
+
if `t` is not given, default :math:`t = -1` is stored instead.
|
|
411
|
+
|
|
412
|
+
2. The method :meth:`store() <c4dynamics.states.state.state.store>` goes together with
|
|
413
|
+
the methods :meth:`data() <c4dynamics.states.state.state.data>`
|
|
414
|
+
and :meth:`timestate() <c4dynamics.states.state.state.timestate>` as input and outputs.
|
|
415
|
+
|
|
416
|
+
3. :meth:`store() <c4dynamics.states.state.state.store>` only stores
|
|
417
|
+
state variables (those construct :attr:`state.X <c4dynamics.states.state.state.X>`).
|
|
418
|
+
For other parameters, use :meth:`storeparams() <c4dynamics.states.state.state.storeparams>`.
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
Examples
|
|
422
|
+
--------
|
|
423
|
+
|
|
424
|
+
.. code::
|
|
425
|
+
|
|
426
|
+
>>> s = c4d.state(x = 1, y = 0, z = 0)
|
|
427
|
+
>>> s.store()
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
**Store with time stamp:**
|
|
431
|
+
|
|
432
|
+
.. code::
|
|
433
|
+
|
|
434
|
+
>>> s = c4d.state(x = 1, y = 0, z = 0)
|
|
435
|
+
>>> s.store(t = 0.5)
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
**Store in a for-loop:**
|
|
440
|
+
|
|
441
|
+
.. code::
|
|
442
|
+
|
|
443
|
+
>>> s = c4d.state(x = 1, y = 0, z = 0)
|
|
444
|
+
>>> for t in np.linspace(0, 1, 3):
|
|
445
|
+
... s.X = np.random.rand(3)
|
|
446
|
+
... s.store(t)
|
|
447
|
+
|
|
448
|
+
|
|
449
|
+
Usage of :meth:`store() <c4dynamics.states.state.state.store>`
|
|
450
|
+
inside a program with a :class:`datapoint <c4dynamics.states.lib.datapoint.datapoint>`
|
|
451
|
+
from the :mod:`states library <c4dynamics.states.lib>`:
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
.. code::
|
|
455
|
+
|
|
456
|
+
>>> t = 0
|
|
457
|
+
>>> dt = 1e-3
|
|
458
|
+
>>> h0 = 100
|
|
459
|
+
>>> dp = c4d.datapoint(z = h0)
|
|
460
|
+
>>> while dp.z >= 0:
|
|
461
|
+
... dp.inteqm([0, 0, -c4d.g_ms2], dt) # doctest: +IGNORE_OUTPUT
|
|
462
|
+
... t += dt
|
|
463
|
+
... dp.store(t)
|
|
464
|
+
>>> for z in dp.data('z'): # doctest: +IGNORE_OUTPUT
|
|
465
|
+
... print(z)
|
|
466
|
+
99.9999950
|
|
467
|
+
99.9999803
|
|
468
|
+
99.9999558
|
|
469
|
+
...
|
|
470
|
+
0.00033469
|
|
471
|
+
-0.0439570
|
|
472
|
+
|
|
473
|
+
|
|
474
|
+
'''
|
|
475
|
+
|
|
476
|
+
|
|
477
|
+
self._data.append([t] + self.X.tolist())
|
|
478
|
+
|
|
479
|
+
|
|
480
|
+
def storeparams(self, params, t = -1.0):
|
|
481
|
+
'''
|
|
482
|
+
Stores parameters.
|
|
483
|
+
|
|
484
|
+
Parameters are data attributes which are not part of the state vector.
|
|
485
|
+
:meth:`storeparams() <c4dynamics.states.state.state.storeparams>` is
|
|
486
|
+
used to store the instantaneous parameters.
|
|
487
|
+
|
|
488
|
+
Parameters
|
|
489
|
+
----------
|
|
490
|
+
params : str or list of str
|
|
491
|
+
Name or names of the parameters to store.
|
|
492
|
+
t : float or int, optional
|
|
493
|
+
Time stamp for the stored state.
|
|
494
|
+
|
|
495
|
+
Note
|
|
496
|
+
----
|
|
497
|
+
1. Time `t` is an optional parameter with a default value of :math:`t = -1`.
|
|
498
|
+
The time is always appended at the head of the array to store. However,
|
|
499
|
+
if `t` is not given, default :math:`t = -1` is stored instead.
|
|
500
|
+
|
|
501
|
+
2. The method :meth:`storeparams() <c4dynamics.states.state.state.storeparams>`
|
|
502
|
+
goes together with the method :meth:`data() <c4dynamics.states.state.state.data>`
|
|
503
|
+
as input and output.
|
|
504
|
+
|
|
505
|
+
|
|
506
|
+
Examples
|
|
507
|
+
--------
|
|
508
|
+
|
|
509
|
+
.. code::
|
|
510
|
+
|
|
511
|
+
>>> s = c4d.state(x = 100, vx = 10)
|
|
512
|
+
>>> s.mass = 25
|
|
513
|
+
>>> s.storeparams('mass')
|
|
514
|
+
>>> s.data('mass')[1] # doctest: +NUMPY_FORMAT
|
|
515
|
+
[25]
|
|
516
|
+
|
|
517
|
+
|
|
518
|
+
**Store with time stamp:**
|
|
519
|
+
|
|
520
|
+
.. code::
|
|
521
|
+
|
|
522
|
+
>>> s = c4d.state(x = 100, vx = 10)
|
|
523
|
+
>>> s.mass = 25
|
|
524
|
+
>>> s.storeparams('mass', t = 0.1)
|
|
525
|
+
>>> s.data('mass')
|
|
526
|
+
(array([0.1]), array([25.]))
|
|
527
|
+
|
|
528
|
+
|
|
529
|
+
**Store multiple parameters:**
|
|
530
|
+
|
|
531
|
+
.. code::
|
|
532
|
+
|
|
533
|
+
>>> s = c4d.state(x = 100, vx = 10)
|
|
534
|
+
>>> s.x_std = 5
|
|
535
|
+
>>> s.vx_std = 10
|
|
536
|
+
>>> s.storeparams(['x_std', 'vx_std'])
|
|
537
|
+
>>> s.data('x_std')[1] # doctest: +NUMPY_FORMAT
|
|
538
|
+
[5]
|
|
539
|
+
>>> s.data('vx_std')[1] # doctest: +NUMPY_FORMAT
|
|
540
|
+
[10]
|
|
541
|
+
|
|
542
|
+
|
|
543
|
+
|
|
544
|
+
**Objects classification:**
|
|
545
|
+
|
|
546
|
+
.. code::
|
|
547
|
+
|
|
548
|
+
>>> s = c4d.state(x = 25, y = 25, w = 20, h = 10)
|
|
549
|
+
>>> np.random.seed(44)
|
|
550
|
+
>>> for i in range(3):
|
|
551
|
+
... s.X += 1
|
|
552
|
+
... s.w, s.h = np.random.randint(0, 50, 2)
|
|
553
|
+
... if s.w > 40 or s.h > 20:
|
|
554
|
+
... s.class_id = 'truck'
|
|
555
|
+
... else:
|
|
556
|
+
... s.class_id = 'car'
|
|
557
|
+
... s.store() # stores the state
|
|
558
|
+
... s.storeparams('class_id') # store the class_id parameter
|
|
559
|
+
>>> print(' x y w h class') # doctest: +IGNORE_OUTPUT
|
|
560
|
+
>>> print(np.hstack((s.data()[:, 1:].astype(int), np.atleast_2d(s.data('class_id')[1]).T))) # doctest: +IGNORE_OUTPUT
|
|
561
|
+
x y w h class
|
|
562
|
+
26 26 20 35 truck
|
|
563
|
+
27 27 49 45 car
|
|
564
|
+
28 28 3 32 car
|
|
565
|
+
|
|
566
|
+
|
|
567
|
+
The `morphospectra` implements a custom method `getdim` to update
|
|
568
|
+
the dimension parameter `dim` with respect to the position coordinates:
|
|
569
|
+
|
|
570
|
+
.. code::
|
|
571
|
+
|
|
572
|
+
>>> import types
|
|
573
|
+
>>> #
|
|
574
|
+
>>> def getdim(s):
|
|
575
|
+
... if s.X[2] != 0:
|
|
576
|
+
... # z
|
|
577
|
+
... s.dim = 3
|
|
578
|
+
... elif s.X[1] != 0:
|
|
579
|
+
... # y
|
|
580
|
+
... s.dim = 2
|
|
581
|
+
... elif s.X[0] != 0:
|
|
582
|
+
... # x
|
|
583
|
+
... s.dim = 1
|
|
584
|
+
... else:
|
|
585
|
+
... # none
|
|
586
|
+
... s.dim = 0
|
|
587
|
+
>>> #
|
|
588
|
+
>>> morphospectra = c4d.state(x = 0, y = 0, z = 0)
|
|
589
|
+
>>> morphospectra.dim = 0
|
|
590
|
+
>>> morphospectra.getdim = types.MethodType(getdim, morphospectra)
|
|
591
|
+
>>> #
|
|
592
|
+
>>> for r in range(10):
|
|
593
|
+
... morphospectra.X = np.random.choice([0, 1], 3)
|
|
594
|
+
... morphospectra.getdim()
|
|
595
|
+
... morphospectra.store()
|
|
596
|
+
... morphospectra.storeparams('dim')
|
|
597
|
+
>>> #
|
|
598
|
+
>>> print('x y z | dim') # doctest: +IGNORE_OUTPUT
|
|
599
|
+
>>> print('------------') # doctest: +IGNORE_OUTPUT
|
|
600
|
+
>>> for x, dim in zip(morphospectra.data().astype(int)[:, 1 : 4].tolist(), morphospectra.data('dim')[1].tolist()): # doctest: +IGNORE_OUTPUT
|
|
601
|
+
... print(*(x + [' | '] + [dim]))
|
|
602
|
+
x y z | dim
|
|
603
|
+
------------
|
|
604
|
+
0 1 0 | 2
|
|
605
|
+
1 1 0 | 2
|
|
606
|
+
1 0 0 | 1
|
|
607
|
+
0 1 1 | 3
|
|
608
|
+
1 0 0 | 1
|
|
609
|
+
1 1 1 | 3
|
|
610
|
+
1 1 0 | 2
|
|
611
|
+
1 1 0 | 2
|
|
612
|
+
1 0 1 | 3
|
|
613
|
+
1 0 1 | 3
|
|
614
|
+
|
|
615
|
+
'''
|
|
616
|
+
# TODO show example of multiple vars
|
|
617
|
+
# maybe the example of the kalman variables store
|
|
618
|
+
# from the detect track exmaple.
|
|
619
|
+
# TODO add test if the params is not 0 or 1 dim throw error or warning. why?
|
|
620
|
+
# TODO document about the two if's down here: nonscalar param and empty param.
|
|
621
|
+
from functools import reduce
|
|
622
|
+
|
|
623
|
+
lparams = params if isinstance(params, list) else [params]
|
|
624
|
+
for p in lparams:
|
|
625
|
+
if p not in self._prmdata:
|
|
626
|
+
self._prmdata[p] = []
|
|
627
|
+
|
|
628
|
+
vval = np.atleast_1d(reduce(getattr, p.split('.'), self)).flatten()
|
|
629
|
+
if len(vval) == 0: # empty, convert to none to keep homogenuous array
|
|
630
|
+
vval = np.atleast_1d(np.nan)
|
|
631
|
+
elif len(vval) > 1:
|
|
632
|
+
# c4d.cprint(f'{p} is not a scalar. only first item is stored', 'r')
|
|
633
|
+
warnings.warn(f'{p} is not a scalar. Only first item is stored', c4d.c4warn)
|
|
634
|
+
vval = vval[:1]
|
|
635
|
+
self._prmdata[p].append([t] + vval.tolist())
|
|
636
|
+
|
|
637
|
+
|
|
638
|
+
def data(self, var = None, scale = 1.):
|
|
639
|
+
'''
|
|
640
|
+
Returns arrays of stored time and data.
|
|
641
|
+
|
|
642
|
+
:meth:`data() <c4dynamics.states.state.state.data>` returns a tuple containing two numpy arrays:
|
|
643
|
+
the first consists of timestamps, and the second
|
|
644
|
+
contains the values of a `var` corresponding to those timestamps.
|
|
645
|
+
|
|
646
|
+
`var` may be each one of the state variables or the parameters.
|
|
647
|
+
|
|
648
|
+
If `var` is not introduced, :meth:`data() <c4dynamics.states.state.state.data>`
|
|
649
|
+
returns a single array of the entire state histories.
|
|
650
|
+
|
|
651
|
+
If data were not stored, :meth:`data() <c4dynamics.states.state.state.data>`
|
|
652
|
+
returns an empty array.
|
|
653
|
+
|
|
654
|
+
|
|
655
|
+
Parameters
|
|
656
|
+
----------
|
|
657
|
+
var : str
|
|
658
|
+
The name of the variable or parameter of the required histories.
|
|
659
|
+
|
|
660
|
+
scale : float or int, optional
|
|
661
|
+
A scaling factor to apply to the variable values, by default 1.
|
|
662
|
+
|
|
663
|
+
Returns
|
|
664
|
+
-------
|
|
665
|
+
out : array or tuple of numpy arrays
|
|
666
|
+
if `var` is introduced, `out` is a tuple of a timestamps array
|
|
667
|
+
and an array of `var` values corresponding to those timestamps.
|
|
668
|
+
If `var` is not introduced, then :math:`n \\times m+1` numpy array is returned,
|
|
669
|
+
where `n` is the number of stored samples, and `m+1` is the
|
|
670
|
+
number of state variables and times.
|
|
671
|
+
|
|
672
|
+
|
|
673
|
+
|
|
674
|
+
Examples
|
|
675
|
+
--------
|
|
676
|
+
|
|
677
|
+
Get all stored data:
|
|
678
|
+
|
|
679
|
+
.. code::
|
|
680
|
+
|
|
681
|
+
>>> np.random.seed(100) # to reproduce results
|
|
682
|
+
>>> s = c4d.state(x = 1, y = 0, z = 0)
|
|
683
|
+
>>> for t in np.linspace(0, 1, 3):
|
|
684
|
+
... s.X = np.random.rand(3)
|
|
685
|
+
... s.store(t)
|
|
686
|
+
>>> s.data() # doctest: +NUMPY_FORMAT
|
|
687
|
+
[[0. 0.543 0.278 0.424]
|
|
688
|
+
[0.5 0.845 0.005 0.121]
|
|
689
|
+
[1. 0.671 0.826 0.137]]
|
|
690
|
+
|
|
691
|
+
|
|
692
|
+
Data of a variable:
|
|
693
|
+
|
|
694
|
+
.. code::
|
|
695
|
+
|
|
696
|
+
>>> time, x_data = s.data('x')
|
|
697
|
+
>>> time # doctest: +NUMPY_FORMAT
|
|
698
|
+
[0. 0.5 1.]
|
|
699
|
+
>>> x_data # doctest: +NUMPY_FORMAT
|
|
700
|
+
[0.543 0.845 0.671]
|
|
701
|
+
>>> s.data('y')[1] # doctest: +NUMPY_FORMAT
|
|
702
|
+
[0.278 0.005 0.826]
|
|
703
|
+
|
|
704
|
+
|
|
705
|
+
Get data with scaling:
|
|
706
|
+
|
|
707
|
+
.. code::
|
|
708
|
+
|
|
709
|
+
>>> s = c4d.state(phi = 0)
|
|
710
|
+
>>> for p in np.linspace(0, c4d.pi):
|
|
711
|
+
... s.phi = p
|
|
712
|
+
... s.store()
|
|
713
|
+
>>> s.data('phi', c4d.r2d)[1] # doctest: +IGNORE_OUTPUT
|
|
714
|
+
[0 3.7 7.3 ... 176.3 180]
|
|
715
|
+
|
|
716
|
+
|
|
717
|
+
Data of a parameter
|
|
718
|
+
|
|
719
|
+
.. code::
|
|
720
|
+
|
|
721
|
+
>>> s = c4d.state(x = 100, vx = 10)
|
|
722
|
+
>>> s.mass = 25
|
|
723
|
+
>>> s.storeparams('mass', t = 0.1)
|
|
724
|
+
>>> s.data('mass')
|
|
725
|
+
(array([0.1]), array([25.]))
|
|
726
|
+
|
|
727
|
+
'''
|
|
728
|
+
|
|
729
|
+
if var is None:
|
|
730
|
+
# return all
|
|
731
|
+
# XXX not sure this is applicable to the new change where arrays\ matrices
|
|
732
|
+
# are also stored.
|
|
733
|
+
# in fact the matrices are not stored in _data but in _prmdata
|
|
734
|
+
return np.array(self._data)
|
|
735
|
+
|
|
736
|
+
idx = self._didx.get(var, -1)
|
|
737
|
+
if idx >= 0:
|
|
738
|
+
if not self._data:
|
|
739
|
+
# empty array
|
|
740
|
+
# c4d.cprint('Warning: no history of state samples.', 'r')
|
|
741
|
+
warnings.warn(f"""No history of state samples.""" , c4d.c4warn)
|
|
742
|
+
|
|
743
|
+
return np.array([])
|
|
744
|
+
|
|
745
|
+
if idx == 0:
|
|
746
|
+
return np.array(self._data)[:, 0]
|
|
747
|
+
|
|
748
|
+
return (np.array(self._data)[:, 0], np.array(self._data)[:, idx] * scale)
|
|
749
|
+
|
|
750
|
+
# else \ user defined variables
|
|
751
|
+
if var not in self._prmdata:
|
|
752
|
+
# c4d.cprint('Warning: no history samples of ' + var + '.', 'r')
|
|
753
|
+
warnings.warn(f"""No history samples of {var}.""" , c4d.c4warn)
|
|
754
|
+
|
|
755
|
+
return np.array([])
|
|
756
|
+
|
|
757
|
+
# if the var is text, dont multiply by scale
|
|
758
|
+
if np.issubdtype(np.array(self._prmdata[var])[:, 1].dtype, np.number):
|
|
759
|
+
return (np.array(self._prmdata[var])[:, 0], np.array(self._prmdata[var])[:, 1] * scale)
|
|
760
|
+
else:
|
|
761
|
+
return (np.array(self._prmdata[var])[:, 0], np.array(self._prmdata[var])[:, 1])
|
|
762
|
+
|
|
763
|
+
|
|
764
|
+
def timestate(self, t):
|
|
765
|
+
'''
|
|
766
|
+
Returns the state as stored at time `t`.
|
|
767
|
+
|
|
768
|
+
The method searches the closest time
|
|
769
|
+
to time `t` in the sampled histories and
|
|
770
|
+
returns the state that stored at the time.
|
|
771
|
+
|
|
772
|
+
If data were not stored returns None.
|
|
773
|
+
|
|
774
|
+
Parameters
|
|
775
|
+
----------
|
|
776
|
+
t : float or int
|
|
777
|
+
The time at the required sample.
|
|
778
|
+
|
|
779
|
+
Returns
|
|
780
|
+
-------
|
|
781
|
+
X : numpy.array
|
|
782
|
+
An array of the state vector
|
|
783
|
+
:attr:`state.X <c4dynamics.states.state.state.X>`
|
|
784
|
+
at time `t`.
|
|
785
|
+
|
|
786
|
+
|
|
787
|
+
Examples
|
|
788
|
+
--------
|
|
789
|
+
|
|
790
|
+
.. code::
|
|
791
|
+
|
|
792
|
+
>>> s = c4d.state(x = 0, y = 0, z = 0)
|
|
793
|
+
>>> for t in np.linspace(0, 1, 3):
|
|
794
|
+
... s.X += 1
|
|
795
|
+
... s.store(t)
|
|
796
|
+
>>> s.timestate(0.5) # doctest: +NUMPY_FORMAT
|
|
797
|
+
[2 2 2]
|
|
798
|
+
|
|
799
|
+
|
|
800
|
+
.. code::
|
|
801
|
+
|
|
802
|
+
>>> s = c4d.state(x = 1, y = 0, z = 0)
|
|
803
|
+
>>> s.timestate(0.5) # doctest: +IGNORE_OUTPUT
|
|
804
|
+
Warning: no history of state samples.
|
|
805
|
+
None
|
|
806
|
+
|
|
807
|
+
|
|
808
|
+
|
|
809
|
+
'''
|
|
810
|
+
|
|
811
|
+
# TODO what about throwing a warning when dt is too long?
|
|
812
|
+
# \\ what is dt and so what if its long?
|
|
813
|
+
times = self.data('t')
|
|
814
|
+
if len(times) == 0:
|
|
815
|
+
X = None
|
|
816
|
+
else:
|
|
817
|
+
idx = min(range(len(times)), key = lambda i: abs(times[i] - t))
|
|
818
|
+
X = np.array(self._data[idx][1:])
|
|
819
|
+
|
|
820
|
+
return X
|
|
821
|
+
|
|
822
|
+
|
|
823
|
+
def plot(self, var, scale = 1, ax = None, filename = None, darkmode = True, **kwargs):
|
|
824
|
+
'''
|
|
825
|
+
Draws plots of variable evolution over time.
|
|
826
|
+
|
|
827
|
+
This method plots the evolution of a state variable over time.
|
|
828
|
+
The resulting plot can be saved to a directory if specified.
|
|
829
|
+
|
|
830
|
+
Parameters
|
|
831
|
+
----------
|
|
832
|
+
var : str
|
|
833
|
+
The name of the variable or parameter to be plotted.
|
|
834
|
+
|
|
835
|
+
scale : float or int, optional
|
|
836
|
+
A scaling factor to apply to the variable values. Defaults to `1`.
|
|
837
|
+
|
|
838
|
+
ax : matplotlib.axes.Axes, optional
|
|
839
|
+
An existing Matplotlib axis to plot on.
|
|
840
|
+
If None, a new figure and axis will be created, by default None.
|
|
841
|
+
|
|
842
|
+
filename : str, optional
|
|
843
|
+
Full file name to save the plot image.
|
|
844
|
+
If None, the plot will not be saved, by default None.
|
|
845
|
+
|
|
846
|
+
darkmode : bool, optional
|
|
847
|
+
Directory path to save the plot image.
|
|
848
|
+
If None, the plot will not be saved, by default None.
|
|
849
|
+
|
|
850
|
+
**kwargs : dict, optional
|
|
851
|
+
Additional key-value arguments passed to `matplotlib.pyplot.plot`.
|
|
852
|
+
These can include any keyword arguments accepted by `plot`,
|
|
853
|
+
such as `color`, `linestyle`, `marker`, etc.
|
|
854
|
+
|
|
855
|
+
|
|
856
|
+
Returns
|
|
857
|
+
-------
|
|
858
|
+
ax : matplotlib.axes.Axes.
|
|
859
|
+
Matplotlib axis of the derived plot.
|
|
860
|
+
|
|
861
|
+
|
|
862
|
+
Note
|
|
863
|
+
----
|
|
864
|
+
- The default `color` is set to `'m'` (magenta).
|
|
865
|
+
- The default `linewidth` is set to `1.2`.
|
|
866
|
+
|
|
867
|
+
|
|
868
|
+
Examples
|
|
869
|
+
--------
|
|
870
|
+
|
|
871
|
+
Import required packages:
|
|
872
|
+
|
|
873
|
+
.. code::
|
|
874
|
+
|
|
875
|
+
>>> import c4dynamics as c4d
|
|
876
|
+
>>> from matplotlib import pyplot as plt
|
|
877
|
+
>>> import numpy as np
|
|
878
|
+
|
|
879
|
+
Plot an arbitrary state variable and save:
|
|
880
|
+
|
|
881
|
+
.. code::
|
|
882
|
+
|
|
883
|
+
>>> s = c4d.state(x = 0, y = 0)
|
|
884
|
+
>>> s.store()
|
|
885
|
+
>>> for _ in range(100):
|
|
886
|
+
... s.x = np.random.randint(0, 100, 1)
|
|
887
|
+
... s.store()
|
|
888
|
+
>>> s.plot('x', filename = 'x.png') # doctest: +IGNORE_OUTPUT
|
|
889
|
+
>>> plt.show()
|
|
890
|
+
|
|
891
|
+
.. figure:: /_examples/state/plot_x.png
|
|
892
|
+
|
|
893
|
+
|
|
894
|
+
**Interactive mode:**
|
|
895
|
+
|
|
896
|
+
.. code::
|
|
897
|
+
|
|
898
|
+
>>> plt.switch_backend('TkAgg')
|
|
899
|
+
>>> s.plot('x') # doctest: +IGNORE_OUTPUT
|
|
900
|
+
>>> plt.show(block = True)
|
|
901
|
+
|
|
902
|
+
|
|
903
|
+
**Dark mode off:**
|
|
904
|
+
|
|
905
|
+
>>> s = c4d.state(x = 0)
|
|
906
|
+
>>> s.xstd = 0.2
|
|
907
|
+
>>> for t in np.linspace(-2 * c4d.pi, 2 * c4d.pi, 1000):
|
|
908
|
+
... s.x = c4d.sin(t) + np.random.randn() * s.xstd
|
|
909
|
+
... s.store(t)
|
|
910
|
+
>>> s.plot('x', darkmode = False) # doctest: +IGNORE_OUTPUT
|
|
911
|
+
>>> plt.show()
|
|
912
|
+
|
|
913
|
+
.. figure:: /_examples/state/plot_darkmode.png
|
|
914
|
+
|
|
915
|
+
|
|
916
|
+
|
|
917
|
+
**Scale plot:**
|
|
918
|
+
|
|
919
|
+
.. code::
|
|
920
|
+
|
|
921
|
+
>>> s = c4d.state(phi = 0)
|
|
922
|
+
>>> for y in c4d.tan(np.linspace(-c4d.pi, c4d.pi, 500)):
|
|
923
|
+
... s.phi = c4d.atan(y)
|
|
924
|
+
... s.store()
|
|
925
|
+
>>> s.plot('phi', scale = c4d.r2d) # doctest: +IGNORE_OUTPUT
|
|
926
|
+
>>> plt.gca().set_ylabel('deg') # doctest: +IGNORE_OUTPUT
|
|
927
|
+
>>> plt.show()
|
|
928
|
+
|
|
929
|
+
.. figure:: /_examples/state/plot_scale.png
|
|
930
|
+
|
|
931
|
+
|
|
932
|
+
**Given axis:**
|
|
933
|
+
|
|
934
|
+
.. code::
|
|
935
|
+
|
|
936
|
+
>>> plt.subplots(1, 1) # doctest: +IGNORE_OUTPUT
|
|
937
|
+
>>> plt.plot(np.linspace(-c4d.pi, c4d.pi, 500) * c4d.r2d, 'm') # doctest: +IGNORE_OUTPUT
|
|
938
|
+
>>> s.plot('phi', scale = c4d.r2d, ax = plt.gca(), color = 'c') # doctest: +IGNORE_OUTPUT
|
|
939
|
+
>>> plt.gca().set_ylabel('deg') # doctest: +IGNORE_OUTPUT
|
|
940
|
+
>>> plt.legend(['θ', 'φ']) # doctest: +IGNORE_OUTPUT
|
|
941
|
+
>>> plt.show()
|
|
942
|
+
|
|
943
|
+
.. figure:: /_examples/state/plot_axis.png
|
|
944
|
+
|
|
945
|
+
|
|
946
|
+
Top view + side view - options of :class:`datapoint <c4dynamics.states.lib.datapoint.datapoint>`
|
|
947
|
+
and :class:`rigidbody <c4dynamics.states.lib.rigidbody.rigidbody>` objects:
|
|
948
|
+
|
|
949
|
+
.. code::
|
|
950
|
+
|
|
951
|
+
>>> dt = 0.01
|
|
952
|
+
>>> floating_balloon = c4d.datapoint(vx = 10 * c4d.k2ms)
|
|
953
|
+
>>> floating_balloon.mass = 0.1
|
|
954
|
+
>>> for t in np.arange(0, 10, dt):
|
|
955
|
+
... floating_balloon.inteqm(forces = [0, 0, .05], dt = dt) # doctest: +IGNORE_OUTPUT
|
|
956
|
+
... floating_balloon.store(t)
|
|
957
|
+
>>> floating_balloon.plot('side')
|
|
958
|
+
>>> plt.gca().invert_yaxis()
|
|
959
|
+
>>> plt.show()
|
|
960
|
+
|
|
961
|
+
.. figure:: /_examples/state/plot_dp_inteqm3.png
|
|
962
|
+
|
|
963
|
+
|
|
964
|
+
|
|
965
|
+
'''
|
|
966
|
+
|
|
967
|
+
from matplotlib import pyplot as plt
|
|
968
|
+
#
|
|
969
|
+
# points to consider
|
|
970
|
+
# -------------------
|
|
971
|
+
#
|
|
972
|
+
# figsize
|
|
973
|
+
# -------
|
|
974
|
+
#
|
|
975
|
+
# i think the challenge here is to get view + save images with
|
|
976
|
+
# 1920 x 1080 pixels (Full HD)
|
|
977
|
+
# 72 DPI (the standard for web images). no way. 72dpi is poor res. at least 300.
|
|
978
|
+
# --> alternative: 960x540 600dpi. #
|
|
979
|
+
#
|
|
980
|
+
# get the screen dpi to get the desired resolution.
|
|
981
|
+
#
|
|
982
|
+
#
|
|
983
|
+
#
|
|
984
|
+
# backends
|
|
985
|
+
# --------
|
|
986
|
+
#
|
|
987
|
+
# non-interactive backends: Agg, SVG, PDF:
|
|
988
|
+
# When saving plots to files.
|
|
989
|
+
# include plt.show()
|
|
990
|
+
# interactive backends:
|
|
991
|
+
# TkAgg, Qt5Agg, etc.
|
|
992
|
+
# dont include plt.show()
|
|
993
|
+
#
|
|
994
|
+
# Check Backend: matplotlib.get_backend().
|
|
995
|
+
# avoid hardcoding backend settings.
|
|
996
|
+
# Avoid using features that are specific to certain backends.
|
|
997
|
+
# Users should be able to override backend settings.
|
|
998
|
+
#
|
|
999
|
+
# Test your plotting functions across different backends.
|
|
1000
|
+
#
|
|
1001
|
+
# finally i think the best soultion regardint backends is not
|
|
1002
|
+
# to do anythink and let the user select the backend from outside.
|
|
1003
|
+
##
|
|
1004
|
+
if var not in self._didx:
|
|
1005
|
+
warnings.warn(f"""{var} is not a state variable.""" , c4d.c4warn)
|
|
1006
|
+
return None
|
|
1007
|
+
if not self._data:
|
|
1008
|
+
warnings.warn(f"""No stored data for {var}.""" , c4d.c4warn)
|
|
1009
|
+
return None
|
|
1010
|
+
|
|
1011
|
+
|
|
1012
|
+
if darkmode:
|
|
1013
|
+
plt.style.use('dark_background')
|
|
1014
|
+
else:
|
|
1015
|
+
plt.style.use('default')
|
|
1016
|
+
|
|
1017
|
+
|
|
1018
|
+
# plt.switch_backend('TkAgg')
|
|
1019
|
+
# plt.switch_backend('TkAgg')
|
|
1020
|
+
|
|
1021
|
+
# try:
|
|
1022
|
+
# from IPython import get_ipython
|
|
1023
|
+
# if get_ipython() is None:
|
|
1024
|
+
# return False
|
|
1025
|
+
# else:
|
|
1026
|
+
# return True
|
|
1027
|
+
# except ImportError:
|
|
1028
|
+
# return False
|
|
1029
|
+
|
|
1030
|
+
|
|
1031
|
+
# Set default values in kwargs only if the user hasn't provided them
|
|
1032
|
+
kwargs.setdefault('color', 'm')
|
|
1033
|
+
kwargs.setdefault('linewidth', 1.2)
|
|
1034
|
+
|
|
1035
|
+
if ax is None:
|
|
1036
|
+
# factorsize = 4
|
|
1037
|
+
# aspectratio = 1080 / 1920
|
|
1038
|
+
# _, ax = plt.subplots(1, 1, dpi = 200
|
|
1039
|
+
# , figsize = (factorsize, factorsize * aspectratio)
|
|
1040
|
+
# , gridspec_kw = {'left': 0.15, 'right': .9
|
|
1041
|
+
# , 'top': .9, 'bottom': .2})
|
|
1042
|
+
_, ax = c4d._figdef()
|
|
1043
|
+
|
|
1044
|
+
|
|
1045
|
+
if not len(np.flatnonzero(self.data('t') != -1)): # values for t weren't stored
|
|
1046
|
+
x = range(len(self.data('t'))) # t is just indices
|
|
1047
|
+
xlabel = 'Samples'
|
|
1048
|
+
else:
|
|
1049
|
+
x = self.data('t')
|
|
1050
|
+
xlabel = 'Time'
|
|
1051
|
+
y = np.array(self._data)[:, self._didx[var]] * scale if self._data else np.empty(1) # used selection
|
|
1052
|
+
|
|
1053
|
+
if dict(self._greek_unicode).get(var, '') != '':
|
|
1054
|
+
title = '$\\' + var + '$'
|
|
1055
|
+
else:
|
|
1056
|
+
title = var
|
|
1057
|
+
|
|
1058
|
+
ax.plot(x, y, **kwargs)
|
|
1059
|
+
c4d.plotdefaults(ax, title, xlabel, '', 8)
|
|
1060
|
+
|
|
1061
|
+
|
|
1062
|
+
|
|
1063
|
+
if filename:
|
|
1064
|
+
# plt.tight_layout(pad = 0)
|
|
1065
|
+
plt.savefig(filename, bbox_inches = 'tight', pad_inches = .2, dpi = 600)
|
|
1066
|
+
|
|
1067
|
+
|
|
1068
|
+
# plt.show(block = True)
|
|
1069
|
+
return ax
|
|
1070
|
+
|
|
1071
|
+
|
|
1072
|
+
|
|
1073
|
+
|
|
1074
|
+
#
|
|
1075
|
+
# math operations
|
|
1076
|
+
##
|
|
1077
|
+
|
|
1078
|
+
|
|
1079
|
+
@property
|
|
1080
|
+
def norm(self):
|
|
1081
|
+
'''
|
|
1082
|
+
Returns the Euclidean norm of the state vector.
|
|
1083
|
+
|
|
1084
|
+
|
|
1085
|
+
Returns
|
|
1086
|
+
-------
|
|
1087
|
+
out : float
|
|
1088
|
+
The computed norm of the state vector. The return type specifically is a numpy.float64.
|
|
1089
|
+
|
|
1090
|
+
|
|
1091
|
+
Examples
|
|
1092
|
+
--------
|
|
1093
|
+
|
|
1094
|
+
.. code::
|
|
1095
|
+
|
|
1096
|
+
>>> s = c4d.state(x1 = 1, x2 = -1)
|
|
1097
|
+
>>> s.norm # doctest: +ELLIPSIS
|
|
1098
|
+
1.414...
|
|
1099
|
+
|
|
1100
|
+
'''
|
|
1101
|
+
return np.linalg.norm(self.X)
|
|
1102
|
+
|
|
1103
|
+
|
|
1104
|
+
@property
|
|
1105
|
+
def normalize(self):
|
|
1106
|
+
'''
|
|
1107
|
+
Returns a unit vector representation of the state vector.
|
|
1108
|
+
|
|
1109
|
+
|
|
1110
|
+
Returns
|
|
1111
|
+
-------
|
|
1112
|
+
out : numpy.array
|
|
1113
|
+
A normalized vector of the same direction and shape as `self.X`, where the norm of the vector is `1`.
|
|
1114
|
+
|
|
1115
|
+
|
|
1116
|
+
Examples
|
|
1117
|
+
--------
|
|
1118
|
+
|
|
1119
|
+
.. code::
|
|
1120
|
+
|
|
1121
|
+
>>> s = c4d.state(x = 1, y = 2, z = 3)
|
|
1122
|
+
>>> s.normalize # doctest: +NUMPY_FORMAT
|
|
1123
|
+
[0.267 0.534 0.801]
|
|
1124
|
+
|
|
1125
|
+
'''
|
|
1126
|
+
|
|
1127
|
+
return self.X / np.linalg.norm(self.X)
|
|
1128
|
+
|
|
1129
|
+
|
|
1130
|
+
# cartesian operations
|
|
1131
|
+
|
|
1132
|
+
|
|
1133
|
+
@property
|
|
1134
|
+
def position(self):
|
|
1135
|
+
'''
|
|
1136
|
+
Returns a vector of position coordinates.
|
|
1137
|
+
|
|
1138
|
+
If the state doesn't include any position coordinate (x, y, z),
|
|
1139
|
+
an empty array is returned.
|
|
1140
|
+
|
|
1141
|
+
Note
|
|
1142
|
+
----
|
|
1143
|
+
In the context of :attr:`position <c4dynamics.states.state.state.position>`,
|
|
1144
|
+
only x, y, z, (case sensitive) are considered position coordinates.
|
|
1145
|
+
|
|
1146
|
+
|
|
1147
|
+
Returns
|
|
1148
|
+
-------
|
|
1149
|
+
out : numpy.array
|
|
1150
|
+
A vector containing the values of three position coordinates.
|
|
1151
|
+
|
|
1152
|
+
|
|
1153
|
+
Examples
|
|
1154
|
+
--------
|
|
1155
|
+
|
|
1156
|
+
.. code::
|
|
1157
|
+
|
|
1158
|
+
>>> s = c4d.state(theta = 3.14, x = 1, y = 2)
|
|
1159
|
+
>>> s.position # doctest: +NUMPY_FORMAT
|
|
1160
|
+
[1 2 0]
|
|
1161
|
+
|
|
1162
|
+
|
|
1163
|
+
.. code::
|
|
1164
|
+
|
|
1165
|
+
>>> s = c4d.state(theta = 3.14, x = 1, y = 2, z = 3)
|
|
1166
|
+
>>> s.position # doctest: +NUMPY_FORMAT
|
|
1167
|
+
[1 2 3]
|
|
1168
|
+
|
|
1169
|
+
|
|
1170
|
+
.. code::
|
|
1171
|
+
|
|
1172
|
+
>>> s = c4d.state(theta = 3.14, z = -100)
|
|
1173
|
+
>>> s.position # doctest: +NUMPY_FORMAT
|
|
1174
|
+
[0 0 -100]
|
|
1175
|
+
|
|
1176
|
+
|
|
1177
|
+
.. code::
|
|
1178
|
+
|
|
1179
|
+
>>> s = c4d.state(theta = 3.14)
|
|
1180
|
+
>>> s.position # doctest: +IGNORE_OUTPUT
|
|
1181
|
+
Position is valid when at least one cartesian coordinate variable (x, y, z) exists...
|
|
1182
|
+
[]
|
|
1183
|
+
'''
|
|
1184
|
+
|
|
1185
|
+
if not self.cartesian():
|
|
1186
|
+
# c4d.cprint('Warning: position is valid when at least one cartesian'
|
|
1187
|
+
# ' coordinate variable (x, y, z) exists.', 'm')
|
|
1188
|
+
warnings.warn(f"""Position is valid when at least one cartesian """
|
|
1189
|
+
"""coordinate variable (x, y, z) exists.""" , c4d.c4warn)
|
|
1190
|
+
return np.array([])
|
|
1191
|
+
|
|
1192
|
+
return np.array([getattr(self, var, 0) for var in ['x', 'y', 'z']])
|
|
1193
|
+
|
|
1194
|
+
|
|
1195
|
+
@property
|
|
1196
|
+
def velocity(self):
|
|
1197
|
+
'''
|
|
1198
|
+
Returns a vector of velocity coordinates.
|
|
1199
|
+
|
|
1200
|
+
If the state doesn't include any velocity coordinate (vx, vy, vz),
|
|
1201
|
+
an empty array is returned.
|
|
1202
|
+
|
|
1203
|
+
Note
|
|
1204
|
+
----
|
|
1205
|
+
In the context of :attr:`velocity <c4dynamics.states.state.state.velocity>`,
|
|
1206
|
+
only vx, vy, vz, (case sensitive) are considered velocity coordinates.
|
|
1207
|
+
|
|
1208
|
+
Returns
|
|
1209
|
+
-------
|
|
1210
|
+
out : numpy.array
|
|
1211
|
+
A vector containing the values of three velocity coordinates.
|
|
1212
|
+
|
|
1213
|
+
|
|
1214
|
+
Examples
|
|
1215
|
+
--------
|
|
1216
|
+
|
|
1217
|
+
.. code::
|
|
1218
|
+
|
|
1219
|
+
>>> s = c4d.state(x = 100, y = 0, vx = -10, vy = 5)
|
|
1220
|
+
>>> s.velocity # doctest: +NUMPY_FORMAT
|
|
1221
|
+
[-10 5 0]
|
|
1222
|
+
|
|
1223
|
+
|
|
1224
|
+
.. code::
|
|
1225
|
+
|
|
1226
|
+
>>> s = c4d.state(x = 100, vz = -100)
|
|
1227
|
+
>>> s.velocity # doctest: +NUMPY_FORMAT
|
|
1228
|
+
[0 0 -100]
|
|
1229
|
+
|
|
1230
|
+
|
|
1231
|
+
.. code::
|
|
1232
|
+
|
|
1233
|
+
>>> s = c4d.state(z = 100)
|
|
1234
|
+
>>> s.velocity # doctest: +IGNORE_OUTPUT
|
|
1235
|
+
Warning: velocity is valid when at least one velocity coordinate variable (vx, vy, vz) exists.
|
|
1236
|
+
[]
|
|
1237
|
+
|
|
1238
|
+
|
|
1239
|
+
'''
|
|
1240
|
+
|
|
1241
|
+
if self.cartesian() < 2:
|
|
1242
|
+
# c4d.cprint('Warning: velocity is valid when at least one velocity '
|
|
1243
|
+
# 'coordinate variable (vx, vy, vz) exists.', 'm')
|
|
1244
|
+
warnings.warn(f"""Velocity is valid when at least one velocity """
|
|
1245
|
+
"""coordinate variable (vx, vy, vz) exists.""" , c4d.c4warn)
|
|
1246
|
+
return np.array([])
|
|
1247
|
+
|
|
1248
|
+
return np.array([getattr(self, var, 0) for var in ['vx', 'vy', 'vz']])
|
|
1249
|
+
|
|
1250
|
+
|
|
1251
|
+
def P(self, state2 = None):
|
|
1252
|
+
'''
|
|
1253
|
+
Euclidean distance.
|
|
1254
|
+
|
|
1255
|
+
Calculates the Euclidean distance between the self state object and
|
|
1256
|
+
a second object `state2`. If `state2` is not provided, then the self
|
|
1257
|
+
Euclidean distance is calculated.
|
|
1258
|
+
|
|
1259
|
+
|
|
1260
|
+
When a second state object is provided:
|
|
1261
|
+
|
|
1262
|
+
.. math::
|
|
1263
|
+
|
|
1264
|
+
P = \\sum_{k=x,y,z} (self.k - state2.k)^2
|
|
1265
|
+
|
|
1266
|
+
|
|
1267
|
+
Otherwise:
|
|
1268
|
+
|
|
1269
|
+
.. math::
|
|
1270
|
+
|
|
1271
|
+
P = \\sum_{k=x,y,z} self.k^2
|
|
1272
|
+
|
|
1273
|
+
|
|
1274
|
+
|
|
1275
|
+
Raises
|
|
1276
|
+
------
|
|
1277
|
+
TypeError
|
|
1278
|
+
If the states don't include any position coordinate (x, y, z).
|
|
1279
|
+
|
|
1280
|
+
|
|
1281
|
+
Note
|
|
1282
|
+
----
|
|
1283
|
+
1. The provided states must have at least oneposition coordinate (x, y, z).
|
|
1284
|
+
2. In the context of :meth:`P() <c4dynamics.states.state.state.P>`,
|
|
1285
|
+
x, y, z, (case sensitive) are considered position coordinates.
|
|
1286
|
+
|
|
1287
|
+
|
|
1288
|
+
|
|
1289
|
+
Parameters
|
|
1290
|
+
----------
|
|
1291
|
+
state2 : :class:`state <c4dynamics.states.state.state>`
|
|
1292
|
+
A second state object for which the relative distance is calculated.
|
|
1293
|
+
|
|
1294
|
+
|
|
1295
|
+
Returns
|
|
1296
|
+
-------
|
|
1297
|
+
out : float
|
|
1298
|
+
Euclidean norm of the distance vector. The return type specifically is a numpy.float64.
|
|
1299
|
+
|
|
1300
|
+
|
|
1301
|
+
Examples
|
|
1302
|
+
--------
|
|
1303
|
+
|
|
1304
|
+
.. code::
|
|
1305
|
+
|
|
1306
|
+
>>> import c4dynamics as c4d
|
|
1307
|
+
|
|
1308
|
+
.. code::
|
|
1309
|
+
|
|
1310
|
+
>>> s = c4d.state(theta = 3.14, x = 1, y = 1)
|
|
1311
|
+
>>> s.P() # doctest: +ELLIPSIS
|
|
1312
|
+
1.414...
|
|
1313
|
+
|
|
1314
|
+
.. code::
|
|
1315
|
+
|
|
1316
|
+
>>> s = c4d.state(theta = 3.14, x = 1, y = 1)
|
|
1317
|
+
>>> s2 = c4d.state(x = 1)
|
|
1318
|
+
>>> s.P(s2)
|
|
1319
|
+
1.0
|
|
1320
|
+
|
|
1321
|
+
.. code::
|
|
1322
|
+
|
|
1323
|
+
>>> s = c4d.state(theta = 3.14, x = 1, y = 1)
|
|
1324
|
+
>>> s2 = c4d.state(z = 1)
|
|
1325
|
+
>>> s.P(s2) # doctest: +ELLIPSIS
|
|
1326
|
+
1.73...
|
|
1327
|
+
|
|
1328
|
+
|
|
1329
|
+
For final example, import required packages:
|
|
1330
|
+
|
|
1331
|
+
.. code::
|
|
1332
|
+
|
|
1333
|
+
>>> import numpy as np
|
|
1334
|
+
>>> from matplotlib import pyplot as plt
|
|
1335
|
+
|
|
1336
|
+
|
|
1337
|
+
Settings and initial conditions:
|
|
1338
|
+
|
|
1339
|
+
.. code::
|
|
1340
|
+
|
|
1341
|
+
>>> camera = c4d.state(x = 0, y = 0)
|
|
1342
|
+
>>> car = c4d.datapoint(x = -100, vx = 40, vy = -7)
|
|
1343
|
+
>>> dist = []
|
|
1344
|
+
>>> time = np.linspace(0, 10, 1000)
|
|
1345
|
+
|
|
1346
|
+
|
|
1347
|
+
|
|
1348
|
+
Main loop:
|
|
1349
|
+
|
|
1350
|
+
.. code::
|
|
1351
|
+
|
|
1352
|
+
>>> for t in time:
|
|
1353
|
+
... car.inteqm(np.zeros(3), time[1] - time[0]) # doctest: +IGNORE_OUTPUT
|
|
1354
|
+
... dist.append(camera.P(car))
|
|
1355
|
+
|
|
1356
|
+
|
|
1357
|
+
Show results:
|
|
1358
|
+
|
|
1359
|
+
.. code::
|
|
1360
|
+
|
|
1361
|
+
>>> plt.plot(time, dist, 'm') # doctest: +IGNORE_OUTPUT
|
|
1362
|
+
>>> c4d.plotdefaults(plt.gca(), 'Distance', 'Time (s)', '(m)')
|
|
1363
|
+
>>> plt.show()
|
|
1364
|
+
|
|
1365
|
+
.. figure:: /_examples/states/state_P.png
|
|
1366
|
+
|
|
1367
|
+
|
|
1368
|
+
|
|
1369
|
+
'''
|
|
1370
|
+
|
|
1371
|
+
|
|
1372
|
+
if not self.cartesian():
|
|
1373
|
+
raise TypeError('state must have at least one position coordinate (x, y, or z)')
|
|
1374
|
+
|
|
1375
|
+
if state2 is None:
|
|
1376
|
+
state2 = c4d.datapoint()
|
|
1377
|
+
else:
|
|
1378
|
+
if not hasattr(state2, 'cartesian') or not state2.cartesian():
|
|
1379
|
+
raise TypeError('state2 must be a state object with at least one position coordinate (x, y, or z)')
|
|
1380
|
+
|
|
1381
|
+
dist = 0
|
|
1382
|
+
for var in ['x', 'y', 'z']:
|
|
1383
|
+
dist += (getattr(self, var, 0) - getattr(state2, var, 0))**2
|
|
1384
|
+
|
|
1385
|
+
return np.sqrt(dist)
|
|
1386
|
+
|
|
1387
|
+
|
|
1388
|
+
def V(self):
|
|
1389
|
+
'''
|
|
1390
|
+
Velocity Magnitude.
|
|
1391
|
+
|
|
1392
|
+
Calculates the magnitude of the object velocity :
|
|
1393
|
+
|
|
1394
|
+
.. math::
|
|
1395
|
+
|
|
1396
|
+
V = \\sum_{k=v_x,v_y,v_z} self.k^2
|
|
1397
|
+
|
|
1398
|
+
If the state doesn't include any velocity coordinate (vx, vy, vz),
|
|
1399
|
+
a `ValueError` is raised.
|
|
1400
|
+
|
|
1401
|
+
|
|
1402
|
+
|
|
1403
|
+
Returns
|
|
1404
|
+
-------
|
|
1405
|
+
out : float
|
|
1406
|
+
Euclidean norm of the velocity vector. The return type specifically is a numpy.float64.
|
|
1407
|
+
|
|
1408
|
+
|
|
1409
|
+
Raises
|
|
1410
|
+
------
|
|
1411
|
+
TypeError
|
|
1412
|
+
If the state does not include any velocity coordinate (vx, vy, vz).
|
|
1413
|
+
|
|
1414
|
+
Note
|
|
1415
|
+
----
|
|
1416
|
+
In the context of :meth:`V() <c4dynamics.states.state.state.V>`,
|
|
1417
|
+
vx, vy, vz, (case sensitive) are considered velocity coordinates.
|
|
1418
|
+
|
|
1419
|
+
|
|
1420
|
+
Examples
|
|
1421
|
+
--------
|
|
1422
|
+
|
|
1423
|
+
.. code::
|
|
1424
|
+
|
|
1425
|
+
>>> s = c4d.state(vx = 7, vy = 24)
|
|
1426
|
+
>>> s.V()
|
|
1427
|
+
25.0
|
|
1428
|
+
|
|
1429
|
+
.. code::
|
|
1430
|
+
|
|
1431
|
+
>>> s = c4d.state(x = 100, y = 0, vx = -10, vy = 7)
|
|
1432
|
+
>>> s.V() # doctest: +ELLIPSIS
|
|
1433
|
+
12.2...
|
|
1434
|
+
|
|
1435
|
+
|
|
1436
|
+
Uncommenting the following line throws a type error:
|
|
1437
|
+
|
|
1438
|
+
.. code::
|
|
1439
|
+
|
|
1440
|
+
>>> s = c4d.state(x = 100, y = 0)
|
|
1441
|
+
>>> # s.V()
|
|
1442
|
+
TypeError: state must have at least one velocity coordinate (vx, vy, or vz)
|
|
1443
|
+
|
|
1444
|
+
|
|
1445
|
+
'''
|
|
1446
|
+
|
|
1447
|
+
if self.cartesian() < 2:
|
|
1448
|
+
raise TypeError('state must have at least one velocity coordinate (vx, vy, or vz)')
|
|
1449
|
+
|
|
1450
|
+
return np.linalg.norm(self.velocity)
|
|
1451
|
+
|
|
1452
|
+
|
|
1453
|
+
def cartesian(self):
|
|
1454
|
+
# TODO document!
|
|
1455
|
+
if any([var for var in ['vx', 'vy', 'vz'] if hasattr(self, var)]):
|
|
1456
|
+
return 2
|
|
1457
|
+
elif any([var for var in ['x', 'y', 'z'] if hasattr(self, var)]):
|
|
1458
|
+
return 1
|
|
1459
|
+
else:
|
|
1460
|
+
return 0
|
|
1461
|
+
|
|
1462
|
+
|
|
1463
|
+
if __name__ == "__main__":
|
|
1464
|
+
|
|
1465
|
+
import doctest, contextlib
|
|
1466
|
+
from c4dynamics import IgnoreOutputChecker, cprint
|
|
1467
|
+
|
|
1468
|
+
# Register the custom OutputChecker
|
|
1469
|
+
doctest.OutputChecker = IgnoreOutputChecker
|
|
1470
|
+
|
|
1471
|
+
tofile = False
|
|
1472
|
+
optionflags = doctest.FAIL_FAST
|
|
1473
|
+
|
|
1474
|
+
if tofile:
|
|
1475
|
+
with open(os.path.join('tests', '_out', 'output.txt'), 'w') as f:
|
|
1476
|
+
with contextlib.redirect_stdout(f), contextlib.redirect_stderr(f):
|
|
1477
|
+
result = doctest.testmod(optionflags = optionflags)
|
|
1478
|
+
else:
|
|
1479
|
+
result = doctest.testmod(optionflags = optionflags)
|
|
1480
|
+
|
|
1481
|
+
if result.failed == 0:
|
|
1482
|
+
cprint(os.path.basename(__file__) + ": all tests passed!", 'g')
|
|
1483
|
+
else:
|
|
1484
|
+
print(f"{result.failed}")
|
|
1485
|
+
|
|
1486
|
+
|