__doc__ = """Symplectic time steppers and concepts for integrating the kinematic and dynamic equations of rod-like objects. """
import numpy as np
# from elastica.timestepper._stepper_interface import (
# _TimeStepper,
# _LinearExponentialIntegratorMixin,
# )
from elastica import IMPORT_NUMBA
"""
Developer Note
--------------
For the reasons why we define Mixin classes here, the developer
is referred to the same section on `explicit_steppers.py`.
"""
class _SystemInstanceStepper:
@staticmethod
def do_step(
TimeStepper, _steps_and_prefactors, System, time: np.float64, dt: np.float64
):
for (kin_prefactor, kin_step, dyn_step) in _steps_and_prefactors[:-1]:
kin_step(TimeStepper, System, time, dt)
time += kin_prefactor(TimeStepper, dt)
System.update_internal_forces_and_torques(time)
dyn_step(TimeStepper, System, time, dt)
# Peel the last kinematic step and prefactor alone
last_kin_prefactor = _steps_and_prefactors[-1][0]
last_kin_step = _steps_and_prefactors[-1][1]
last_kin_step(TimeStepper, System, time, dt)
return time + last_kin_prefactor(TimeStepper, dt)
[docs]class _SystemCollectionStepper:
"""
Symplectic stepper collection class
"""
[docs] @staticmethod
def do_step(
TimeStepper,
_steps_and_prefactors,
SystemCollection,
time: np.float64,
dt: np.float64,
):
"""
Function for doing symplectic stepper over the user defined rods (system).
Parameters
----------
SystemCollection: rod object
time: float
dt: float
Returns
-------
"""
for (kin_prefactor, kin_step, dyn_step) in _steps_and_prefactors[:-1]:
for system in SystemCollection:
kin_step(TimeStepper, system, time, dt)
time += kin_prefactor(TimeStepper, dt)
# TODO: remove below line, it should be some other function synchronizeBC
# SystemCollection.synchronizeBC(time)
# Constrain only values
SystemCollection.constrain_values(time)
# We need internal forces and torques because they are used by interaction module.
for system in SystemCollection:
system.update_internal_forces_and_torques(time)
# system.update_internal_forces_and_torques()
# Add external forces, controls etc.
SystemCollection.synchronize(time)
for system in SystemCollection:
dyn_step(TimeStepper, system, time, dt)
# TODO: remove below line, it should be some other function synchronizeBC
# Constrain only rates
SystemCollection.constrain_rates(time)
# Peel the last kinematic step and prefactor alone
last_kin_prefactor = _steps_and_prefactors[-1][0]
last_kin_step = _steps_and_prefactors[-1][1]
for system in SystemCollection:
last_kin_step(TimeStepper, system, time, dt)
time += last_kin_prefactor(TimeStepper, dt)
SystemCollection.constrain_values(time)
# Call back function, will call the user defined call back functions and store data
SystemCollection.apply_callbacks(time, int(time / dt))
return time
class SymplecticStepperMethods:
def __init__(self, timestepper_instance):
take_methods_from = timestepper_instance
# Let the total number of steps for the Symplectic method
# be (2*n + 1) (for time-symmetry). What we do is collect
# the first n + 1 entries down in _steps and _prefac below, and then
# reverse and append it to itself.
self._steps = [
v
for (k, v) in take_methods_from.__class__.__dict__.items()
if k.endswith("step")
]
# Prefac here is necessary because the linear-exponential integrator
# needs only the prefactor and not the dt.
self._prefactors = [
v
for (k, v) in take_methods_from.__class__.__dict__.items()
if k.endswith("prefactor")
]
# # We are getting function named as _update_internal_forces_torques from dictionary,
# # it turns a list.
# self._update_internal_forces_torques = [
# v
# for (k, v) in take_methods_from.__class__.__dict__.items()
# if k.endswith("forces_torques")
# ]
def mirror(in_list):
"""Mirrors an input list ignoring the last element
If steps = [A, B, C]
then this call makes it [A, B, C, B, A]
Parameters
----------
in_list : input list to be mirrored, modified in-place
Returns
-------
"""
# syntax is very ugly
if len(in_list) > 1:
in_list.extend(in_list[-2::-1])
elif in_list:
in_list.append(in_list[0])
mirror(self._steps)
mirror(self._prefactors)
assert (
len(self._steps) == 2 * len(self._prefactors) - 1
), "Size mismatch in the number of steps and prefactors provided for a Symplectic Stepper!"
self._kinematic_steps = self._steps[::2]
self._dynamic_steps = self._steps[1::2]
# Avoid this check for MockClasses
if len(self._kinematic_steps) > 0:
assert (
len(self._kinematic_steps) == len(self._dynamic_steps) + 1
), "Size mismatch in the number of kinematic and dynamic steps provided for a Symplectic Stepper!"
from itertools import zip_longest
def NoOp(*args):
pass
self._steps_and_prefactors = tuple(
zip_longest(
self._prefactors,
self._kinematic_steps,
self._dynamic_steps,
fillvalue=NoOp,
)
)
def step_methods(self):
return self._steps_and_prefactors
@property
def n_stages(self):
return len(self._steps_and_prefactors)
if IMPORT_NUMBA:
from elastica._elastica_numba._timestepper._symplectic_steppers import (
SymplecticStepperTag,
PositionVerlet,
PEFRL,
)
else:
from elastica._elastica_numpy._timestepper._symplectic_steppers import (
SymplecticStepperTag,
PositionVerlet,
PEFRL,
)