Source code for elastica.timestepper.symplectic_steppers

__doc__ = """Symplectic time steppers and concepts for integrating the kinematic and dynamic equations of rod-like objects.  """

import numpy as np

# from elastica._elastica_numba._timestepper._symplectic_steppers import (
#     SymplecticStepperTag,
#     PositionVerlet,
#     PEFRL,
# )

# from elastica.timestepper._stepper_interface import (
#     _TimeStepper,
#     _LinearExponentialIntegratorMixin,
# )
from elastica.rod.data_structures import (
    overload_operator_kinematic_numba,
    overload_operator_dynamic_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)


class _SystemCollectionStepper:
    """
    Symplectic stepper collection class
    """

    @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._memory_blocks:
                kin_step(TimeStepper, system, time, dt)

            time += kin_prefactor(TimeStepper, dt)

            # Constrain only values
            SystemCollection.constrain_values(time)

            # We need internal forces and torques because they are used by interaction module.
            for system in SystemCollection._memory_blocks:
                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._memory_blocks:
                dyn_step(TimeStepper, system, time, dt)

            # 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._memory_blocks:
            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, round(time / dt))

        # Zero out the external forces and torques
        for system in SystemCollection._memory_blocks:
            system.reset_external_forces_and_torques(time)

        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)


class SymplecticStepperTag:
    def __init__(self):
        pass


[docs]class PositionVerlet: """ Position Verlet symplectic time stepper class, which includes methods for second-order position Verlet. """ Tag = SymplecticStepperTag() def __init__(self): pass def _first_prefactor(self, dt): return 0.5 * dt def _first_kinematic_step(self, System, time: np.float64, dt: np.float64): prefac = self._first_prefactor(dt) overload_operator_kinematic_numba( System.n_nodes, prefac, System.kinematic_states.position_collection, System.kinematic_states.director_collection, System.velocity_collection, System.omega_collection, ) def _first_dynamic_step(self, System, time: np.float64, dt: np.float64): overload_operator_dynamic_numba( System.dynamic_states.rate_collection, System.dynamic_rates(time, dt), )
[docs]class PEFRL: """ Position Extended Forest-Ruth Like Algorithm of I.M. Omelyan, I.M. Mryglod and R. Folk, Computer Physics Communications 146, 188 (2002), http://arxiv.org/abs/cond-mat/0110585 """ # xi and chi are confusing, but be careful! ξ = np.float64(0.1786178958448091e0) # ξ λ = -np.float64(0.2123418310626054e0) # λ χ = -np.float64(0.6626458266981849e-1) # χ # Pre-calculate other coefficients lambda_dash_coeff = 0.5 * (1.0 - 2.0 * λ) xi_chi_dash_coeff = 1.0 - 2.0 * (ξ + χ) Tag = SymplecticStepperTag() def __init__(self): pass def _first_kinematic_prefactor(self, dt): return self.ξ * dt def _first_kinematic_step(self, System, time: np.float64, dt: np.float64): prefac = self._first_kinematic_prefactor(dt) overload_operator_kinematic_numba( System.n_nodes, prefac, System.kinematic_states.position_collection, System.kinematic_states.director_collection, System.velocity_collection, System.omega_collection, ) # System.kinematic_states += prefac * System.kinematic_rates(time, prefac) def _first_dynamic_step(self, System, time: np.float64, dt: np.float64): prefac = self.lambda_dash_coeff * dt overload_operator_dynamic_numba( System.dynamic_states.rate_collection, System.dynamic_rates(time, prefac), ) # System.dynamic_states += prefac * System.dynamic_rates(time, prefac) def _second_kinematic_prefactor(self, dt): return self.χ * dt def _second_kinematic_step(self, System, time: np.float64, dt: np.float64): prefac = self._second_kinematic_prefactor(dt) overload_operator_kinematic_numba( System.n_nodes, prefac, System.kinematic_states.position_collection, System.kinematic_states.director_collection, System.velocity_collection, System.omega_collection, ) # System.kinematic_states += prefac * System.kinematic_rates(time, prefac) def _second_dynamic_step(self, System, time: np.float64, dt: np.float64): prefac = self.λ * dt overload_operator_dynamic_numba( System.dynamic_states.rate_collection, System.dynamic_rates(time, prefac), ) # System.dynamic_states += prefac * System.dynamic_rates(time, prefac) def _third_kinematic_prefactor(self, dt): return self.xi_chi_dash_coeff * dt def _third_kinematic_step(self, System, time: np.float64, dt: np.float64): prefac = self._third_kinematic_prefactor(dt) # Need to fill in overload_operator_kinematic_numba( System.n_nodes, prefac, System.kinematic_states.position_collection, System.kinematic_states.director_collection, System.velocity_collection, System.omega_collection, )
# System.kinematic_states += prefac * System.kinematic_rates(time, prefac)