Skip to content

Commit abcd104

Browse files
committed
Merge remote-tracking branch 'origin/refactor/symplectic' into update-v1
2 parents 40a574d + fecb75e commit abcd104

7 files changed

Lines changed: 132 additions & 399 deletions

File tree

docs/advanced/PackageDesign.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ Elastica package uses [structural subtyping](https://peps.python.org/pep-0544/)
2020
direction RL
2121
subgraph Systems Protocol
2222
direction RL
23-
SymST["SymplecticSystemProtocol<br/>(Necessary to be stepped by the timestepper)"]
23+
SymST["SymplecticSystemProtocol<br/>(Mixin for timestepper)<br/>• update_kinematics<br/>• update_dynamics"]
2424
style SymST text-align:left
2525
StaticSystemType["Static System Type"<br/>• Plane]
2626
SystemType["(Dynamic) System Type<br/>• CosseratRod (Rod)<br/>• Sphere (RigidBody)<br/>• Cylinder (RigidBody)"]
@@ -46,7 +46,7 @@ Elastica package uses [structural subtyping](https://peps.python.org/pep-0544/)
4646

4747
- Any object that conforms to `StaticSystemProtocol` can be added to the system collection.
4848
- If you want to add custom type to the system, you can use `append_allowed_types` to add it to the system collection. To add associated block support, you can use `enable_block_supports`.
49-
- Among the systems added to the system collection, only objects that conform to `SystemProtocol` will be integrated by the timestepper.
49+
- Among the systems added to the system collection, only objects that conform to `SymplecticSystemProtocol` will be integrated by the symplectic timestepper. This protocol requires `update_kinematics(time, prefac)` and `update_dynamics(time, prefac)` methods to be implemented.
5050
- If block support is available for a system, they will be collected together during the `finalize` step, and passed to the timestepper.
5151

5252

elastica/memory_block/memory_block_rod.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -260,9 +260,6 @@ def __init__(
260260
self.rest_kappa, self.periodic_boundary_voronoi_idx
261261
)
262262

263-
# Initialize the mixin class for symplectic time-stepper.
264-
_RodSymplecticStepperMixin.__init__(self)
265-
266263
def _allocate_block_variables_in_nodes(self, systems: list[RodType]) -> None:
267264
"""
268265
This function takes system collection and allocates the variables on

elastica/rod/data_structures.py

Lines changed: 98 additions & 148 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
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+
"""
29

310
from typing import TYPE_CHECKING
411
import numpy as np
@@ -14,201 +21,144 @@
1421

1522

1623
class _RodSymplecticStepperMixin:
24+
"""
25+
Mixin class providing necessary methods for integration of the kinematic and
26+
dynamic equations of the rod.
1727
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
1837
position_collection: NDArray[np.float64]
1938
director_collection: NDArray[np.float64]
39+
# Velocity state
2040
velocity_collection: NDArray[np.float64]
2141
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.
2255
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.
2557
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,
3369
self.velocity_collection,
3470
self.omega_collection,
3571
)
3672

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,
4575
time: np.float64,
4676
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],
7177
) -> None:
7278
"""
79+
Update dynamic state.
80+
81+
Typically called after acceleration and alpha (angular acceleration) have been updated.
82+
7383
Parameters
7484
----------
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.
7789
"""
78-
# super(_KinematicState, self).__init__()
90+
overload_operator_dynamic_numba(
91+
prefac,
92+
self.v_w_collection,
93+
self.dvdt_dwdt_collection,
94+
)
95+
7996

80-
self.position_collection = position_collection_view
81-
self.director_collection = director_collection_view
97+
"""
98+
Symplectic stepper operation
99+
"""
82100

83101

84102
@njit(cache=True) # type: ignore
85103
def overload_operator_kinematic_numba(
86-
n_nodes: int,
87104
prefac: np.float64,
88105
position_collection: NDArray[np.float64],
89106
director_collection: NDArray[np.float64],
90107
velocity_collection: NDArray[np.float64],
91108
omega_collection: NDArray[np.float64],
92109
) -> None:
93-
"""overloaded += operator
110+
"""Performs in-place update of kinematic states (position and director) using Numba.
94111
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
96114
formula.
115+
97116
Parameters
98117
----------
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.
109128
"""
110129
# x += v*dt
130+
blocksize = position_collection.shape[1]
111131
for i in range(3):
112-
for k in range(n_nodes):
132+
for k in range(blocksize):
113133
position_collection[i, k] += prefac * velocity_collection[i, k]
114134
rotation_matrix = _get_rotation_matrix(1.0, prefac * omega_collection)
115135
director_collection[:] = _batch_matmul(rotation_matrix, director_collection)
116136

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-
186137

187138
@njit(cache=True) # type: ignore
188139
def overload_operator_dynamic_numba(
140+
prefac: np.float64,
189141
rate_collection: NDArray[np.float64],
190-
scaled_second_deriv_array: NDArray[np.float64],
142+
second_deriv_array: NDArray[np.float64],
191143
) -> 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+
193149
Parameters
194150
----------
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.
204157
"""
205158
# 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]
210162
for i in range(2):
211163
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]

elastica/systems/protocol.py

Lines changed: 5 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@
44

55
from abc import abstractmethod
66

7-
from elastica.rod.data_structures import _KinematicState, _DynamicState
8-
97
import numpy as np
108
from numpy.typing import NDArray
119

@@ -46,10 +44,7 @@ class SymplecticSystemProtocol(SystemProtocol, Protocol):
4644
(e.g., :class:`PositionVerlet`, :class:`PEFRL`) must satisfy this protocol.
4745
4846
The symplectic stepper accesses:
49-
- ``n_nodes``
50-
- ``kinematic_states`` (position_collection, director_collection)
51-
- ``dynamic_states`` (velocity_collection, omega_collection, rate_collection)
52-
- ``dynamic_rates(time, prefac)`` to compute acceleration updates
47+
- ``update_kinematics`` and ``update_dynamics``: called by the timestepper
5348
5449
See Also
5550
--------
@@ -58,26 +53,10 @@ class SymplecticSystemProtocol(SystemProtocol, Protocol):
5853
5954
"""
6055

61-
n_nodes: int
62-
63-
@property
64-
def kinematic_states(self) -> _KinematicState:
65-
"""Return kinematic state."""
66-
...
67-
68-
@property
69-
def dynamic_states(self) -> _DynamicState:
70-
"""Return dynamic state."""
71-
...
72-
73-
def kinematic_rates(
74-
self, time: np.float64, prefac: np.float64
75-
) -> tuple[NDArray[np.float64], NDArray[np.float64]]:
76-
"""Compute kinematic rates."""
56+
def update_kinematics(self, time: np.float64, prefac: np.float64) -> None:
57+
"""Update kinematic state. Typically called after compute_internal_forces_and_torques."""
7758
...
7859

79-
def dynamic_rates(
80-
self, time: np.float64, prefac: np.float64
81-
) -> NDArray[np.float64]:
82-
"""Compute dynamic rates."""
60+
def update_dynamics(self, time: np.float64, prefac: np.float64) -> None:
61+
"""Update dynamic state. Typically called after ``update_accelerations``."""
8362
...

0 commit comments

Comments
 (0)