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.
Files changed (50) hide show
  1. c4dynamics/__init__.py +240 -0
  2. c4dynamics/datasets/__init__.py +95 -0
  3. c4dynamics/datasets/_manager.py +596 -0
  4. c4dynamics/datasets/_registry.py +80 -0
  5. c4dynamics/detectors/__init__.py +37 -0
  6. c4dynamics/detectors/yolo3_opencv.py +686 -0
  7. c4dynamics/detectors/yolo3_tf.py +124 -0
  8. c4dynamics/eqm/__init__.py +324 -0
  9. c4dynamics/eqm/derivs.py +212 -0
  10. c4dynamics/eqm/integrate.py +359 -0
  11. c4dynamics/filters/__init__.py +1373 -0
  12. c4dynamics/filters/a.py +48 -0
  13. c4dynamics/filters/ekf.py +320 -0
  14. c4dynamics/filters/kalman.py +725 -0
  15. c4dynamics/filters/kalman_v0.py +1071 -0
  16. c4dynamics/filters/kalman_v1.py +821 -0
  17. c4dynamics/filters/lowpass.py +123 -0
  18. c4dynamics/filters/luenberger.py +97 -0
  19. c4dynamics/rotmat/__init__.py +141 -0
  20. c4dynamics/rotmat/animate.py +465 -0
  21. c4dynamics/rotmat/rotmat.py +351 -0
  22. c4dynamics/sensors/__init__.py +72 -0
  23. c4dynamics/sensors/lineofsight.py +78 -0
  24. c4dynamics/sensors/radar.py +740 -0
  25. c4dynamics/sensors/seeker.py +1030 -0
  26. c4dynamics/states/__init__.py +327 -0
  27. c4dynamics/states/lib/__init__.py +320 -0
  28. c4dynamics/states/lib/datapoint.py +660 -0
  29. c4dynamics/states/lib/pixelpoint.py +776 -0
  30. c4dynamics/states/lib/rigidbody.py +677 -0
  31. c4dynamics/states/state.py +1486 -0
  32. c4dynamics/utils/__init__.py +44 -0
  33. c4dynamics/utils/_struct.py +6 -0
  34. c4dynamics/utils/const.py +130 -0
  35. c4dynamics/utils/cprint.py +80 -0
  36. c4dynamics/utils/gen_gif.py +142 -0
  37. c4dynamics/utils/idx2keys.py +4 -0
  38. c4dynamics/utils/images_loader.py +63 -0
  39. c4dynamics/utils/math.py +136 -0
  40. c4dynamics/utils/plottools.py +140 -0
  41. c4dynamics/utils/plottracks.py +304 -0
  42. c4dynamics/utils/printpts.py +36 -0
  43. c4dynamics/utils/slides_gen.py +64 -0
  44. c4dynamics/utils/tictoc.py +167 -0
  45. c4dynamics/utils/video_gen.py +300 -0
  46. c4dynamics/utils/vidgen.py +182 -0
  47. c4dynamics-2.0.3.dist-info/METADATA +242 -0
  48. c4dynamics-2.0.3.dist-info/RECORD +50 -0
  49. c4dynamics-2.0.3.dist-info/WHEEL +5 -0
  50. 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
+