|
1 | | -__doc__ = "Data structure wrapper for rod components" |
| 1 | +""" |
| 2 | +Data structures and Numba-jitted operators for handling rod components |
| 3 | +and their integration in a symplectic time-stepping scheme. |
| 4 | +
|
| 5 | +This module provides the `_RodSymplecticStepperMixin` for managing |
| 6 | +kinematic and dynamic states of rods, and optimized functions for |
| 7 | +their in-place updates. |
| 8 | +""" |
2 | 9 |
|
3 | 10 | from typing import TYPE_CHECKING |
4 | 11 | import numpy as np |
|
14 | 21 |
|
15 | 22 |
|
16 | 23 | class _RodSymplecticStepperMixin: |
| 24 | + """ |
| 25 | + Mixin class providing necessary methods for integration of the kinematic and |
| 26 | + dynamic equations of the rod. |
17 | 27 |
|
| 28 | + This mixin manages the rod's posture (position and directors), velocity |
| 29 | + (linear and angular), and acceleration states. It provides `update_kinematics` |
| 30 | + and `update_dynamics` methods to apply updates to these states, typically |
| 31 | + called by a symplectic time-stepper. |
| 32 | + """ |
| 33 | + |
| 34 | + n_nodes: int |
| 35 | + |
| 36 | + # Posture state |
18 | 37 | position_collection: NDArray[np.float64] |
19 | 38 | director_collection: NDArray[np.float64] |
| 39 | + # Velocity state |
20 | 40 | velocity_collection: NDArray[np.float64] |
21 | 41 | omega_collection: NDArray[np.float64] |
| 42 | + v_w_collection: NDArray[np.float64] # Rate collection |
| 43 | + # Acceleration state |
| 44 | + # acceleration_collection: NDArray[np.float64] |
| 45 | + # alpha_collection: NDArray[np.float64] |
| 46 | + dvdt_dwdt_collection: NDArray[np.float64] # Second derivative collection |
| 47 | + |
| 48 | + def update_kinematics( |
| 49 | + self, |
| 50 | + time: np.float64, |
| 51 | + prefac: np.float64, |
| 52 | + ) -> None: |
| 53 | + """ |
| 54 | + Update kinematic state. |
22 | 55 |
|
23 | | - v_w_collection: NDArray[np.float64] |
24 | | - dvdt_dwdt_collection: NDArray[np.float64] |
| 56 | + Typically called after velocity and omega (angular velocity) have been updated. |
25 | 57 |
|
26 | | - def __init__(self) -> None: |
27 | | - self.kinematic_states = _KinematicState( |
28 | | - self.position_collection, self.director_collection |
29 | | - ) |
30 | | - self.dynamic_states = _DynamicState( |
31 | | - self.v_w_collection, |
32 | | - self.dvdt_dwdt_collection, |
| 58 | + Parameters |
| 59 | + ---------- |
| 60 | + time : float |
| 61 | + Current time. |
| 62 | + prefac : float |
| 63 | + Integration prefactor. |
| 64 | + """ |
| 65 | + overload_operator_kinematic_numba( |
| 66 | + prefac, |
| 67 | + self.position_collection, |
| 68 | + self.director_collection, |
33 | 69 | self.velocity_collection, |
34 | 70 | self.omega_collection, |
35 | 71 | ) |
36 | 72 |
|
37 | | - # Expose rate returning functions in the interface |
38 | | - # to be used by the time-stepping algorithm |
39 | | - # dynamic rates needs to call update_accelerations and henc |
40 | | - # is another function |
41 | | - self.kinematic_rates = self.dynamic_states.kinematic_rates |
42 | | - |
43 | | - def dynamic_rates( |
44 | | - self: SymplecticSystemProtocol, |
| 73 | + def update_dynamics( |
| 74 | + self, |
45 | 75 | time: np.float64, |
46 | 76 | prefac: np.float64, |
47 | | - ) -> NDArray[np.float64]: |
48 | | - self.update_accelerations(time) |
49 | | - return self.dynamic_states.dynamic_rates(time, prefac) |
50 | | - |
51 | | - |
52 | | -""" |
53 | | -Symplectic stepper interface |
54 | | -""" |
55 | | - |
56 | | - |
57 | | -class _KinematicState: |
58 | | - """State storing (x,Q) for symplectic steppers. |
59 | | - Wraps data as state, with overloaded methods for symplectic steppers. |
60 | | - Allows for separating implementation of stepper from actual |
61 | | - addition/multiplication/other formulae used. |
62 | | -
|
63 | | - Symplectic steppers rely only on in-place modifications to state and so |
64 | | - only these methods are provided. |
65 | | - """ |
66 | | - |
67 | | - def __init__( |
68 | | - self, |
69 | | - position_collection_view: NDArray[np.float64], |
70 | | - director_collection_view: NDArray[np.float64], |
71 | 77 | ) -> None: |
72 | 78 | """ |
| 79 | + Update dynamic state. |
| 80 | +
|
| 81 | + Typically called after acceleration and alpha (angular acceleration) have been updated. |
| 82 | +
|
73 | 83 | Parameters |
74 | 84 | ---------- |
75 | | - position_collection_view : view of positions (or) x |
76 | | - director_collection_view : view of directors (or) Q |
| 85 | + time : float |
| 86 | + Current time. |
| 87 | + prefac : float |
| 88 | + Integration prefactor. |
77 | 89 | """ |
78 | | - # super(_KinematicState, self).__init__() |
| 90 | + overload_operator_dynamic_numba( |
| 91 | + prefac, |
| 92 | + self.v_w_collection, |
| 93 | + self.dvdt_dwdt_collection, |
| 94 | + ) |
| 95 | + |
79 | 96 |
|
80 | | - self.position_collection = position_collection_view |
81 | | - self.director_collection = director_collection_view |
| 97 | +""" |
| 98 | +Symplectic stepper operation |
| 99 | +""" |
82 | 100 |
|
83 | 101 |
|
84 | 102 | @njit(cache=True) # type: ignore |
85 | 103 | def overload_operator_kinematic_numba( |
86 | | - n_nodes: int, |
87 | 104 | prefac: np.float64, |
88 | 105 | position_collection: NDArray[np.float64], |
89 | 106 | director_collection: NDArray[np.float64], |
90 | 107 | velocity_collection: NDArray[np.float64], |
91 | 108 | omega_collection: NDArray[np.float64], |
92 | 109 | ) -> None: |
93 | | - """overloaded += operator |
| 110 | + """Performs in-place update of kinematic states (position and director) using Numba. |
94 | 111 |
|
95 | | - The add for directors is customized to reflect Rodrigues' rotation |
| 112 | + This operator updates the position and director collections of a rod based on |
| 113 | + its velocity and angular velocity. The director update uses Rodrigues' rotation |
96 | 114 | formula. |
| 115 | +
|
97 | 116 | Parameters |
98 | 117 | ---------- |
99 | | - scaled_deriv_array : np.ndarray containing dt * (v, ω), |
100 | | - as retured from _DynamicState's `kinematic_rates` method |
101 | | - Returns |
102 | | - ------- |
103 | | - self : _KinematicState instance with inplace modified data |
104 | | - Caveats |
105 | | - ------- |
106 | | - Takes a np.ndarray and not a _KinematicState object (as one expects). |
107 | | - This is done for efficiency reasons, see _DynamicState's `kinematic_rates` |
108 | | - method |
| 118 | + prefac : numpy.float64 |
| 119 | + Pre-factor (e.g., time step `dt`) to scale the velocity and angular velocity. |
| 120 | + position_collection : numpy.ndarray |
| 121 | + Position of the rod nodes. Modified in-place. |
| 122 | + director_collection : numpy.ndarray |
| 123 | + Director (orientation) of the rod elements. Modified in-place. |
| 124 | + velocity_collection : numpy.ndarray |
| 125 | + Linear velocity of the rod nodes. |
| 126 | + omega_collection : numpy.ndarray |
| 127 | + Angular velocity of the rod elements. |
109 | 128 | """ |
110 | 129 | # x += v*dt |
| 130 | + blocksize = position_collection.shape[1] |
111 | 131 | for i in range(3): |
112 | | - for k in range(n_nodes): |
| 132 | + for k in range(blocksize): |
113 | 133 | position_collection[i, k] += prefac * velocity_collection[i, k] |
114 | 134 | rotation_matrix = _get_rotation_matrix(1.0, prefac * omega_collection) |
115 | 135 | director_collection[:] = _batch_matmul(rotation_matrix, director_collection) |
116 | 136 |
|
117 | | - return |
118 | | - |
119 | | - |
120 | | -class _DynamicState: |
121 | | - """State storing (v,ω, dv/dt, dω/dt) for symplectic steppers. |
122 | | -
|
123 | | - Wraps data as state, with overloaded methods for symplectic steppers. |
124 | | - Allows for separating implementation of stepper from actual |
125 | | - addition/multiplication/other formulae used. |
126 | | - Symplectic steppers rely only on in-place modifications to state and so |
127 | | - only these methods are provided. |
128 | | - """ |
129 | | - |
130 | | - def __init__( |
131 | | - self, |
132 | | - v_w_collection: NDArray[np.float64], |
133 | | - dvdt_dwdt_collection: NDArray[np.float64], |
134 | | - velocity_collection: NDArray[np.float64], |
135 | | - omega_collection: NDArray[np.float64], |
136 | | - ) -> None: |
137 | | - """ |
138 | | - Parameters |
139 | | - ---------- |
140 | | - n_elems : int, number of rod elements |
141 | | - rate_collection_view : np.ndarray containing (v, ω, dv/dt, dω/dt) |
142 | | - v_w_collection : numpy.ndarray |
143 | | -
|
144 | | - """ |
145 | | - super(_DynamicState, self).__init__() |
146 | | - # Limit at which (v, w) end |
147 | | - # Create views for dynamic state |
148 | | - self.rate_collection = v_w_collection |
149 | | - self.dvdt_dwdt_collection = dvdt_dwdt_collection |
150 | | - self.velocity_collection = velocity_collection |
151 | | - self.omega_collection = omega_collection |
152 | | - |
153 | | - def kinematic_rates( |
154 | | - self, time: np.float64, prefac: np.float64 |
155 | | - ) -> tuple[NDArray[np.float64], NDArray[np.float64]]: |
156 | | - """Yields kinematic rates to interact with _KinematicState |
157 | | -
|
158 | | - Returns |
159 | | - ------- |
160 | | - v_and_omega : np.ndarray consisting of (v,ω) |
161 | | - Caveats |
162 | | - ------- |
163 | | - Doesn't return a _KinematicState with (dt*v, dt*w) as members, |
164 | | - as one expects the _Kinematic __add__ operator to interact |
165 | | - with another _KinematicState. This is done for efficiency purposes. |
166 | | - """ |
167 | | - # RHS functino call, gives v,w so that |
168 | | - # Comes from kin_state -> (x,Q) += dt * (v,w) <- First part of dyn_state |
169 | | - return self.velocity_collection, self.omega_collection |
170 | | - |
171 | | - def dynamic_rates( |
172 | | - self, time: np.float64, prefac: np.float64 |
173 | | - ) -> NDArray[np.float64]: |
174 | | - """Yields dynamic rates to add to with _DynamicState |
175 | | - Returns |
176 | | - ------- |
177 | | - acc_and_alpha : np.ndarray consisting of (dv/dt,dω/dt) |
178 | | - Caveats |
179 | | - ------- |
180 | | - Doesn't return a _DynamicState with (dt*v, dt*w) as members, |
181 | | - as one expects the _Dynamic __add__ operator to interact |
182 | | - with another _DynamicState. This is done for efficiency purposes. |
183 | | - """ |
184 | | - return prefac * self.dvdt_dwdt_collection |
185 | | - |
186 | 137 |
|
187 | 138 | @njit(cache=True) # type: ignore |
188 | 139 | def overload_operator_dynamic_numba( |
| 140 | + prefac: np.float64, |
189 | 141 | rate_collection: NDArray[np.float64], |
190 | | - scaled_second_deriv_array: NDArray[np.float64], |
| 142 | + second_deriv_array: NDArray[np.float64], |
191 | 143 | ) -> None: |
192 | | - """overloaded += operator, updating dynamic_rates |
| 144 | + """Performs in-place update of dynamic states (linear and angular velocities) using Numba. |
| 145 | +
|
| 146 | + This operator updates the rate collection (which stores linear and angular velocities) |
| 147 | + of a rod based on the second derivative array (linear and angular accelerations). |
| 148 | +
|
193 | 149 | Parameters |
194 | 150 | ---------- |
195 | | - scaled_second_deriv_array : np.ndarray containing dt * (dvdt, dωdt), |
196 | | - as retured from _DynamicState's `dynamic_rates` method |
197 | | - Returns |
198 | | - ------- |
199 | | - self : _DynamicState instance with inplace modified data |
200 | | - Caveats |
201 | | - ------- |
202 | | - Takes a np.ndarray and not a _DynamicState object (as one expects). |
203 | | - This is done for efficiency reasons, see `dynamic_rates`. |
| 151 | + prefac : numpy.float64 |
| 152 | + Pre-factor (e.g., time step `dt`) to scale the second derivative terms. |
| 153 | + rate_collection : numpy.ndarray |
| 154 | + Collection of linear and angular velocities of the rod. Modified in-place. |
| 155 | + second_deriv_array : numpy.ndarray |
| 156 | + Collection of linear and angular accelerations (dv/dt, dω/dt) of the rod. |
204 | 157 | """ |
205 | 158 | # Always goes in LHS : that means the update is on the rates alone |
206 | | - # (v,ω) += dt * (dv/dt, dω/dt) -> self.dynamic_rates |
207 | | - # rate_collection[..., : n_kinematic_rates] += scaled_second_deriv_array |
208 | | - blocksize = scaled_second_deriv_array.shape[1] |
209 | | - |
| 159 | + # (v,ω) += dt * (dv/dt, dω/dt) |
| 160 | + # rate_collection[..., : n_kinematic_rates] += second_deriv_aray |
| 161 | + blocksize = second_deriv_array.shape[1] |
210 | 162 | for i in range(2): |
211 | 163 | for k in range(blocksize): |
212 | | - rate_collection[i, k] += scaled_second_deriv_array[i, k] |
213 | | - |
214 | | - return |
| 164 | + rate_collection[i, k] += prefac * second_deriv_array[i, k] |
0 commit comments