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,327 @@
1
+ '''
2
+
3
+ .. currentmodule:: c4dynamics.states.state
4
+
5
+
6
+ This page is an `introduction` to the states module.
7
+ For the different state objects, go to :ref:`objects-header`.
8
+
9
+
10
+ State Data-Structure
11
+ --------------------
12
+
13
+
14
+ C4dynamics offers a versatile data-structures for managing state variables.
15
+
16
+
17
+ Users from a range of disciplines, particularly those involving mathematical modeling,
18
+ simulations, and dynamic systems, can define a state with any desired variables, for example:
19
+
20
+
21
+
22
+ **Control Systems**
23
+
24
+ - `Pendulum`
25
+ - :math:`X = [\\theta, \\omega]^T`
26
+ - Angle, angular velocity.
27
+ - :code:`s = c4d.state(theta = 10 * c4d.d2r, omega = 0)`
28
+
29
+
30
+
31
+ **Navigation**
32
+
33
+ - `Strapdown navigation system`
34
+ - :math:`X = [x, y, z, v_x, v_y, v_z, q_0, q_1, q_2, q_3, b_{ax}, b_{ay}, b_{az}]^T`
35
+ - Position, velocity, quaternions, biases.
36
+ - :code:`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)`
37
+
38
+
39
+
40
+ **Computer Vision**
41
+
42
+ - `Objects tracker`
43
+ - :math:`X = [x, y, w, h]^T`
44
+ - Center pixel, bounding box size.
45
+ - :code:`s = c4d.state(x = 960, y = 540, w = 20, h = 10)`
46
+
47
+
48
+
49
+ **Aerospace**
50
+
51
+ - `Aircraft`
52
+ - :math:`X = [x, y, z, v_x, v_y, v_z, \\varphi, \\theta, \\psi, p, q, r]^T`
53
+ - Position, velocity, angles, angular velocities.
54
+ - :code:`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)`
55
+
56
+
57
+
58
+ **Autonomous Systems**
59
+
60
+ - `Self-driving car`
61
+ - :math:`X = [x, y, z, \\theta, \\omega]^T`
62
+ - Position and velocity, heading and angular velocity.
63
+ - :code:`s = c4d.state(x = 0, y = 0, v = 0, theta = 0, omega = 0)`
64
+
65
+
66
+
67
+ **Robotics**
68
+
69
+ - `Robot arm`
70
+ - :math:`X = [\\theta_1, \\theta_2, \\omega_1, \\omega_2]^T`
71
+ - Joint angles, angular velocities.
72
+ - :code:`s = c4d.state(theta1 = 0, theta2 = 0, omega1 = 0, omega2 = 0)`
73
+
74
+
75
+
76
+ And many others.
77
+
78
+
79
+ These data-structures encapsulate the variables into a state vector :math:`X` (a numpy array),
80
+ allows for seamless execution of vector operations on the state,
81
+ enabling efficient and intuitive manipulations of the state data.
82
+
83
+
84
+
85
+
86
+ Operations
87
+ ----------
88
+
89
+ Operations on state vectors are categorized into two main types:
90
+ `mathematical operations` and `data management operations`.
91
+
92
+ The mathematical operations involve direct manipulation of the state vectors
93
+ using mathematical methods. These operations include multiplication, addition, and normalization,
94
+ and can be performed by standard `numpy` methods.
95
+
96
+ The data management operations involve managing the state vector data,
97
+ such as storing and retrieving states at different times or handling time series data.
98
+ To perform these operations, `c4dynamics` provides a variety of methods under the :class:`state object <state>`.
99
+
100
+
101
+ The following tables summarize the mathematical and data management operations
102
+ on a state vector.
103
+
104
+ Let an arbitrary state vector with variables :math:`x = 1, y = 0, z = 0`:
105
+
106
+ Import c4dynamics:
107
+
108
+ .. code::
109
+
110
+ >>> import c4dynamics as c4d
111
+
112
+ .. code::
113
+
114
+ >>> s = c4d.state(x = 1, y = 0, z = 0)
115
+ >>> print(s)
116
+ [ x y z ]
117
+ >>> s.X # doctest: +NUMPY_FORMAT
118
+ [1 0 0]
119
+
120
+ .. list-table:: Mathematical Operations
121
+ :widths: 30 70
122
+ :header-rows: 1
123
+
124
+ * - Operation
125
+ - Example
126
+
127
+ * - Scalar Multiplication
128
+ - | :code:`>>> s.X * 2`
129
+ | :code:`[2 0 0]`
130
+
131
+ * - Matrix Multiplication
132
+ - | :code:`>>> R = c4d.rotmat.dcm321(psi = c4d.pi / 2)`
133
+ | :code:`>>> s.X @ R`
134
+ | :code:`[0 1 0]`
135
+
136
+ * - Norm Calculation
137
+ - | :code:`>>> np.linalg.norm(s.X)`
138
+ | :code:`1`
139
+
140
+ * - Addition/Subtraction
141
+ - | :code:`>>> s.X + [-1, 0, 0]`
142
+ | :code:`[0 0 0]`
143
+
144
+ * - Dot Product
145
+ - | :code:`>>> s.X @ s.X`
146
+ | :code:`1`
147
+
148
+ * - Normalization
149
+ - | :code:`>>> s.X / np.linalg.norm(s.X)`
150
+ | :code:`[1 0 0]`
151
+
152
+
153
+
154
+
155
+ .. list-table:: Data Management Operations
156
+ :widths: 30 70
157
+ :header-rows: 1
158
+
159
+ * - Operation
160
+ - Example
161
+
162
+ * - Store the current state
163
+ - :code:`>>> s.store()`
164
+
165
+ * - Store with time-stamp
166
+ - :code:`>>> s.store(t = 0)`
167
+
168
+ * - Store the state in a for-loop
169
+ - | :code:`>>> for t in np.linspace(0, 1, 3):`
170
+ | :code:`... s.X = np.random.rand(3)`
171
+ | :code:`... s.store(t)`
172
+
173
+ * - Get the stored data
174
+ - | :code:`>>> s.data()`
175
+ | :code:`[[0 0.37 0.76 0.20]`
176
+ | :code:`[0.5 0.93 0.28 0.59]`
177
+ | :code:`[1 0.79 0.39 0.33]]`
178
+
179
+ * - Get the time-series of the data
180
+ - | :code:`>>> s.data('t')`
181
+ | :code:`[0 0.5 1]`
182
+
183
+ * - Get data of a variable
184
+ - | :code:`>>> s.data('x')[1]`
185
+ | :code:`[0.37 0.93 0.79]`
186
+
187
+ * - Get time-series and data of a variable
188
+ - | :code:`>>> time, y_data = s.data('y')`
189
+ | :code:`>>> time`
190
+ | :code:`[0 0.5 1]`
191
+ | :code:`>>> y_data`
192
+ | :code:`[0.76 0.28 0.39]`
193
+
194
+ * - Get the state at a given time
195
+ - | :code:`>>> s.timestate(t = 0.5)`
196
+ | :code:`[0.93 0.28 0.59]`
197
+
198
+ * - Plot the histories of a variable
199
+ - | :code:`>>> s.plot('z')`
200
+ | ...
201
+
202
+
203
+
204
+
205
+
206
+ State Construction
207
+ ------------------
208
+
209
+ A state instance is created by calling the
210
+ :class:`state` constructor with
211
+ pairs of variables that compose the state and their initial conditions.
212
+ For example, a state of two
213
+ variables, :math:`var1` and :math:`var2`, is created by:
214
+
215
+ .. code::
216
+
217
+ >>> s = c4d.state(var1 = 0, var2 = 0)
218
+
219
+
220
+ The list of the variables that form the state is given by :code:`print(s)`.
221
+
222
+ .. code::
223
+
224
+ >>> print(s)
225
+ [ var1 var2 ]
226
+
227
+
228
+ **Initial conditions**
229
+
230
+ The variables must be passed with initial values. These values may be
231
+ retrieved later by calling :attr:`state.X0`:
232
+
233
+ .. code::
234
+
235
+ >>> s.X0 # doctest: +NUMPY_FORMAT
236
+ [0 0]
237
+
238
+ When the initial values are not known at the stage of constructing
239
+ the state object, it's possible to pass zeros and override them later
240
+ by direct assignment of the state variable with a `0` suffix, :code:`s.var10 = 100`, :code:`s.var20 = 200`.
241
+ See more at :attr:`state.X0`.
242
+
243
+
244
+ **Adding variables**
245
+
246
+
247
+ Adding state variables outside the
248
+ constructor is possible by using :meth:`addvars(**kwargs) <c4dynamics.states.state.state.addvars>`,
249
+ where `kwargs` represent the pairs of variables and their initial conditions as calling the
250
+ `state` constructor:
251
+
252
+ .. code::
253
+
254
+ >>> s.addvars(var3 = 0)
255
+ >>> print(s)
256
+ [ var1 var2 var3 ]
257
+
258
+
259
+
260
+
261
+ **Parameters**
262
+
263
+
264
+
265
+ All the variables that passed to the :class:`state` constructor are considered
266
+ state variables, and only these variables. Parameters, i.e. data attributes that are
267
+ added to the object outside the constructor (the `__init__` method), as in:
268
+
269
+ .. code::
270
+
271
+ >>> s.parameter = 0
272
+
273
+ are considered part of the object attributes, but are not part of the object state:
274
+
275
+ .. code::
276
+
277
+ >>> print(s)
278
+ [ var1 var2 var3 ]
279
+
280
+
281
+
282
+ **Predefined states**
283
+
284
+ Another way to create a state instance is by using one of the pre-defined objects from
285
+ the :mod:`states library <c4dynamics.states.lib>`. These state objects may be useful
286
+ as they are optimized for particular tasks.
287
+
288
+ '''
289
+
290
+
291
+ # TODO how come the overall title is not word-capatilized and the smaller are.
292
+
293
+
294
+ import sys
295
+ sys.path.append('.')
296
+ from c4dynamics.states.lib import *
297
+
298
+
299
+
300
+
301
+ if __name__ == "__main__":
302
+
303
+ import doctest, contextlib, os
304
+ from c4dynamics import IgnoreOutputChecker, cprint
305
+
306
+ # Register the custom OutputChecker
307
+ doctest.OutputChecker = IgnoreOutputChecker
308
+
309
+ tofile = False
310
+ optionflags = doctest.FAIL_FAST
311
+
312
+ if tofile:
313
+ with open(os.path.join('tests', '_out', 'output.txt'), 'w') as f:
314
+ with contextlib.redirect_stdout(f), contextlib.redirect_stderr(f):
315
+ result = doctest.testmod(optionflags = optionflags)
316
+ else:
317
+ result = doctest.testmod(optionflags = optionflags)
318
+
319
+ if result.failed == 0:
320
+ cprint(os.path.basename(__file__) + ": all tests passed!", 'g')
321
+ else:
322
+ print(f"{result.failed}")
323
+
324
+
325
+
326
+
327
+
@@ -0,0 +1,320 @@
1
+ '''
2
+ This page is an `introduction` to the states library.
3
+ For the different pre-defined states themselves, go to :ref:`states-header`.
4
+
5
+ .. currentmodule:: c4dynamics.states.lib
6
+
7
+ Each one of the states in the library is inherited from the
8
+ :class:`state <c4dynamics.states.state.state>`
9
+ class and has the benefit of its attributes, like
10
+ :meth:`store() <c4dynamics.states.state.state.store>`
11
+ :meth:`data() <c4dynamics.states.state.state.data>`
12
+ etc.
13
+
14
+
15
+ 1. Data Point
16
+ -------------
17
+
18
+ C4dynamics provides built-in entities for developing
19
+ and analyzing algorithms of objects in space and time:
20
+
21
+ :class:`datapoint <datapoint.datapoint>`:
22
+ a class defining a point in space: position, velocity, and mass.
23
+
24
+ :class:`rigidbody <rigidbody.rigidbody>`:
25
+ a class rigidbody a class defining a rigid body in space, i.e.
26
+ an object with length and angular position.
27
+
28
+
29
+ .. figure:: /_architecture/body_states.svg
30
+ :width: 482px
31
+ :height: 534px
32
+
33
+ **Figure:**
34
+ Conceptual diagram showing the relationship between the two
35
+ fundamental objects used to describe bodies in space: 1) the
36
+ datapoint, 2) the rigidbody. A rigidbody object extends the
37
+ datapoint by adding on it body rotational motion.
38
+
39
+
40
+ The :class:`datapoint <datapoint.datapoint>`
41
+ is the most basic element in translational dynamics; it's a point in space.
42
+
43
+ A `datapoint` serves as the building block for modeling and simulating
44
+ the motion of objects in a three-dimensional space.
45
+ In the context of translational dynamics, a datapoint represents
46
+ a point mass in space with defined Cartesian coordinates :math:`(x, y, z)`
47
+ and associated velocities :math:`(v_x, v_y, v_z)`.
48
+
49
+
50
+ Data Attributes
51
+ ^^^^^^^^^^^^^^^
52
+
53
+ State variables:
54
+
55
+ .. math::
56
+
57
+ X = [x, y, z, v_x, v_y, v_z]^T
58
+
59
+ - Position coordinates, velocity coordinates.
60
+
61
+ Parameters:
62
+
63
+ - `mass`: point mass.
64
+
65
+
66
+ Construction
67
+ ^^^^^^^^^^^^
68
+
69
+ A `datapoint` instance is created by making a direct call to the datapoint constructor:
70
+
71
+ .. code::
72
+
73
+ >>> from c4dynamics import datapoint
74
+ >>> dp = datapoint()
75
+
76
+ .. code::
77
+
78
+ >>> print(dp)
79
+ [ x y z vx vy vz ]
80
+
81
+
82
+
83
+
84
+
85
+ Initialization of an instance does not require any mandatory parameters.
86
+ However,
87
+ setting values to any of the state variables uses as initial conditions:
88
+
89
+ .. code::
90
+
91
+ >>> dp = datapoint(x = 1000, vx = -100)
92
+
93
+
94
+ Functionality
95
+ ^^^^^^^^^^^^^
96
+
97
+ The :meth:`inteqm() <datapoint.datapoint.inteqm>` method uses
98
+ the Runge-Kutta integration technique
99
+ to evolve the state in response to external forces.
100
+ The mechanics underlying the equations of motion can be found
101
+ :mod:`here <c4dynamics.eqm>`.
102
+
103
+ The method :meth:`plot() <datapoint.datapoint.plot>` adds on
104
+ the standard :meth:`state.plot() <c4dynamics.states.state.state.plot>`
105
+ the option to draw trajectories from side view and from top view.
106
+
107
+
108
+
109
+
110
+ See Also
111
+ ^^^^^^^^
112
+ .datapoint
113
+ .eqm
114
+
115
+
116
+
117
+ 2. Rigid Body
118
+ -------------
119
+
120
+ The :class:`rigidbody <c4dynamics.states.lib.rigidbody.rigidbody>`
121
+ class extends the functionality of the :class:`datapoint <datapoint.datapoint>`.
122
+
123
+ It introduces additional attributes related to rotational
124
+ dynamics, such as angular position, angular velocity, and moment of inertia.
125
+ The class leverages the capabilities of the datapoint
126
+ class for handling translational dynamics and extends
127
+ it to include rotational aspects. See the figure above.
128
+
129
+
130
+
131
+ Data Attributes
132
+ ^^^^^^^^^^^^^^^
133
+
134
+ State variables:
135
+
136
+ .. math::
137
+
138
+ X = [x, y, z, v_x, v_y, v_z, {\\varphi}, {\\theta}, {\\psi}, p, q, r]^T
139
+
140
+ - Position, velocity, angles, angle rates.
141
+
142
+
143
+ Parameters:
144
+
145
+ - `mass`: point mass.
146
+ - `I`: vector of moments of inertia about 3 axes.
147
+
148
+ Construction
149
+ ^^^^^^^^^^^^
150
+
151
+ A `rigidbody` instance is created by making a direct call to the rigidbody constructor:
152
+
153
+ .. code::
154
+
155
+ >>> from c4dynamics import rigidbody
156
+ >>> rb = rigidbody()
157
+
158
+
159
+ .. code::
160
+
161
+ >>> print(rb)
162
+ [ x y z vx vy vz φ θ ψ p q r ]
163
+
164
+
165
+
166
+
167
+
168
+
169
+ Similar to the datapoint,
170
+ initialization of an instance does not require any mandatory parameters.
171
+ Setting values to any of the state variables uses as initial conditions:
172
+
173
+ .. code::
174
+
175
+ >>> from c4dynamics import d2r
176
+ >>> rb = rigidbody(theta = 10 * d2r, q = -1 * d2r)
177
+
178
+
179
+
180
+ Functionality
181
+ ^^^^^^^^^^^^^
182
+
183
+ The :meth:`inteqm() <rigidbody.rigidbody.inteqm>` method uses
184
+ the Runge-Kutta integration technique
185
+ to evolve the state in response to external forces and moments.
186
+ The mechanics underlying the equations of motion can be found
187
+ :mod:`here <c4dynamics.eqm>` and :mod:`here <c4dynamics.rotmat>`.
188
+
189
+ :attr:`BR <rigidbody.rigidbody.BR>` and
190
+ :attr:`RB <rigidbody.rigidbody.RB>` return
191
+ Direction Cosine Matrices, Body from Reference (`[BR]`)
192
+ and Reference from Body (`[RB]`), with respect to the
193
+ instantaneous Euler angles (:math:`\\varphi, \\theta, \\psi`).
194
+
195
+ When a 3D model is provided, the method
196
+ :meth:`animate() <rigidbody.rigidbody.animate>`
197
+ animates the object with respect to the histories of
198
+ the rigidbody attitude.
199
+
200
+
201
+ See Also
202
+ ^^^^^^^^
203
+ .rigidbody
204
+ .eqm
205
+ .rotmat
206
+
207
+
208
+
209
+ 3. Pixel Point
210
+ --------------
211
+
212
+ The :class:`pixelpoint <c4dynamics.states.lib.pixelpoint.pixelpoint>`
213
+ class representing a data point in a video frame with a
214
+ bounding box.
215
+
216
+ This class is particularly useful for applications in computer vision,
217
+ such as object detection and tracking.
218
+
219
+
220
+ Data Attributes
221
+ ^^^^^^^^^^^^^^^
222
+
223
+ State variables:
224
+
225
+ .. math::
226
+
227
+ X = [x, y, w, h]^T
228
+
229
+ - Center pixel, box size.
230
+
231
+ Parameters:
232
+
233
+ - `fsize`: frame size.
234
+ - `class_id`: object classification.
235
+
236
+
237
+ Construction
238
+ ^^^^^^^^^^^^
239
+
240
+ Usually, the `pixelpoint` instance is created immediately after an object
241
+ detection:
242
+
243
+ .. code::
244
+
245
+ >>> from c4dynamics import pixelpoint
246
+ >>> pp = pixelpoint(x = 50, y = 50, w = 15, h = 25) # (50, 50) detected object center, (15, 25) object bounding box
247
+ >>> pp.fsize = (100, 100) # frame width and frame height
248
+ >>> pp.class_id = 'fox'
249
+
250
+
251
+
252
+ .. code::
253
+
254
+ >>> print(pp)
255
+ [ x y w h ]
256
+
257
+
258
+
259
+ Functionality
260
+ ^^^^^^^^^^^^^
261
+
262
+ :attr:`box <c4dynamics.states.lib.pixelpoint.pixelpoint.box>`
263
+ returns the bounding box in terms of top-left and bottom-right coordinates.
264
+
265
+
266
+
267
+
268
+ See Also
269
+ ^^^^^^^^
270
+ .pixelpoint
271
+ .yolov3
272
+
273
+
274
+
275
+
276
+
277
+
278
+
279
+ '''
280
+ import sys
281
+ sys.path.append('.')
282
+
283
+
284
+
285
+
286
+
287
+ # :class:`pixelspoint <c4dynamics.states.lib.pixelpoint.pixelpoint>` has
288
+ # two types of coordinate :attr:`units <c4dynamics.states.lib.pixelpoint.pixelpoint.units>`:
289
+ # `pixels` (default) and `normalized`.
290
+ # When `normalized` mode is selected, the method
291
+ # :attr:`Xpixels <c4dynamics.states.lib.pixelpoint.pixelpoint.Xpixels>`
292
+ # uses to retrun the state vector in pixel coordinates.
293
+
294
+ if __name__ == "__main__":
295
+
296
+ import doctest, contextlib, os
297
+ from c4dynamics import IgnoreOutputChecker, cprint
298
+
299
+ # Register the custom OutputChecker
300
+ doctest.OutputChecker = IgnoreOutputChecker
301
+
302
+ tofile = False
303
+ optionflags = doctest.FAIL_FAST
304
+
305
+ if tofile:
306
+ with open(os.path.join('tests', '_out', 'output.txt'), 'w') as f:
307
+ with contextlib.redirect_stdout(f), contextlib.redirect_stderr(f):
308
+ result = doctest.testmod(optionflags = optionflags)
309
+ else:
310
+ result = doctest.testmod(optionflags = optionflags)
311
+
312
+ if result.failed == 0:
313
+ cprint(os.path.basename(__file__) + ": all tests passed!", 'g')
314
+ else:
315
+ print(f"{result.failed}")
316
+
317
+
318
+
319
+
320
+