From 71a566c3fc7236717f25cb01f633a70aa956f6ea Mon Sep 17 00:00:00 2001 From: Seung Hyun Kim Date: Wed, 1 May 2024 10:30:52 -0500 Subject: [PATCH 01/12] refactor: Stateful -> Symplectic --- elastica/py.typed | 1 - elastica/timestepper/__init__.py | 37 +++++++++++++------------------ elastica/timestepper/protocol.py | 38 +++++++++++++------------------- elastica/typing.py | 19 ++++++++++------ 4 files changed, 42 insertions(+), 53 deletions(-) diff --git a/elastica/py.typed b/elastica/py.typed index 8b1378917..e69de29bb 100644 --- a/elastica/py.typed +++ b/elastica/py.typed @@ -1 +0,0 @@ - diff --git a/elastica/timestepper/__init__.py b/elastica/timestepper/__init__.py index 66f8190fe..10fed9d96 100644 --- a/elastica/timestepper/__init__.py +++ b/elastica/timestepper/__init__.py @@ -1,7 +1,7 @@ __doc__ = """Timestepping utilities to be used with Rod and RigidBody classes""" -from typing import Tuple, List, Callable, Type -from elastica.typing import SystemType +from typing import Tuple, List, Callable, Type, Any +from elastica.typing import SystemType, SystemCollectionType, SteppersOperatorsType import numpy as np from tqdm import tqdm @@ -12,30 +12,24 @@ from .explicit_steppers import RungeKutta4, EulerForward from .tag import SymplecticStepperTag, ExplicitStepperTag -from .protocol import StepperProtocol, StatefulStepperProtocol -from .protocol import MethodCollectorProtocol +from .protocol import StepperProtocol, SymplecticStepperProtocol # TODO: Both extend_stepper_interface and integrate should be in separate file. # __init__ is probably not an ideal place to have these scripts. def extend_stepper_interface( - Stepper: StepperProtocol, System: SystemType + Stepper: StepperProtocol, System: SystemType | SystemCollectionType ) -> Tuple[Callable, Tuple[Callable]]: - # StepperMethodCollector: Type[MethodCollectorProtocol] - # SystemStepper: Type[StepperProtocol] - if isinstance(Stepper.Tag, SymplecticStepperTag): + # SystemStepper: StepperProtocol + if Stepper.Tag == SymplecticStepperTag: from elastica.timestepper.symplectic_steppers import ( - _SystemInstanceStepper, - _SystemCollectionStepper, SymplecticStepperMethods, ) StepperMethodCollector = SymplecticStepperMethods - elif isinstance(Stepper.Tag, ExplicitStepperTag): # type: ignore[no-redef] + elif Stepper.Tag == ExplicitStepperTag: # type: ignore[no-redef] from elastica.timestepper.explicit_steppers import ( - _SystemInstanceStepper, - _SystemCollectionStepper, ExplicitStepperMethods, ) @@ -48,19 +42,18 @@ def extend_stepper_interface( ) # Check if system is a "collection" of smaller systems - if is_system_a_collection(System): - SystemStepper = _SystemCollectionStepper - else: - SystemStepper = _SystemInstanceStepper + assert is_system_a_collection(System) - stepper_methods: Tuple[Callable] = StepperMethodCollector(Stepper).step_methods() - do_step_method: Callable = SystemStepper.do_step + stepper_methods: SteppersOperatorsType = StepperMethodCollector( + Stepper + ).step_methods() + do_step_method: Callable = stepper_methods.do_step return do_step_method, stepper_methods def integrate( - StatefulStepper: StatefulStepperProtocol, - System: SystemType, + StatefulStepper: SymplecticStepperProtocol, + System: SystemType | SystemCollectionType, final_time: float, n_steps: int = 1000, restart_time: float = 0.0, @@ -70,7 +63,7 @@ def integrate( Parameters ---------- - StatefulStepper : StatefulStepperProtocol + StatefulStepper : SymplecticStepperProtocol Stepper algorithm to use. System : SystemType The elastica-system to simulate. diff --git a/elastica/timestepper/protocol.py b/elastica/timestepper/protocol.py index 63a9a52d6..27086a1ea 100644 --- a/elastica/timestepper/protocol.py +++ b/elastica/timestepper/protocol.py @@ -1,8 +1,9 @@ __doc__ = "Time stepper interface" -from typing import Protocol, Tuple, Callable, Type, Any +from typing import Protocol, Callable, Literal, ClassVar -from elastica.typing import SystemType +from elastica.typing import SystemType, SteppersOperatorsType, OperatorType +from .tag import StepperTags import numpy as np @@ -10,37 +11,28 @@ class StepperProtocol(Protocol): """Protocol for all time-steppers""" - def do_step(self, *args: Any, **kwargs: Any) -> float: ... + Tag: StepperTags @property - def Tag(self) -> Type: ... + def n_stages(self) -> int: ... + def step_methods(self) -> SteppersOperatorsType: ... -class StatefulStepperProtocol(StepperProtocol, Protocol): - """ - Stateful explicit, symplectic stepper wrapper. - """ - # For stateful steppes, bind memory to self - def do_step(self, System: SystemType, time: np.floating, dt: np.floating) -> float: - """ - Perform one time step of the simulation. - Return the new time. - """ - ... +class SymplecticStepperProtocol(StepperProtocol, Protocol): + """symplectic stepper protocol.""" - @property - def n_stages(self) -> int: ... + def get_steps(self) -> list[OperatorType]: ... + + def get_prefactors(self) -> list[OperatorType]: ... -class MethodCollectorProtocol(Protocol): - """ - Protocol for collecting stepper methods. - """ +class ExplicitStepperProtocol(StepperProtocol, Protocol): + """symplectic stepper protocol.""" - def __init__(self, timestepper_instance: StepperProtocol): ... + def get_steps(self) -> list[OperatorType]: ... - def step_methods(self) -> Tuple[Callable]: ... + def get_prefactors(self) -> list[OperatorType]: ... class MemoryProtocol(Protocol): diff --git a/elastica/typing.py b/elastica/typing.py index b6bd654e6..7c3df8702 100644 --- a/elastica/typing.py +++ b/elastica/typing.py @@ -18,7 +18,11 @@ from .rod.data_structures import _State as State from .systems.protocol import SymplecticSystemProtocol, ExplicitSystemProtocol - from .timestepper.protocol import StatefulStepperProtocol, MemoryProtocol + from .timestepper.protocol import ( + StepperProtocol, + SymplecticStepperProtocol, + MemoryProtocol, + ) else: RodBase = None RigidBodyBase = None @@ -28,7 +32,9 @@ State = "State" SymplecticSystemProtocol = None ExplicitSystemProtocol = None - StatefulStepperProtocol = None + + StepperProtocol = None + SymplecticStepperProtocol = None MemoryProtocol = None @@ -41,10 +47,10 @@ # NoOpt stepper # Symplectic stepper # StepOperatorType = Callable[ -# [StatefulStepperProtocol, SymplecticSystemProtocol, np.floating, np.floating], None +# [SymplecticStepperProtocol, SymplecticSystemProtocol, np.floating, np.floating], None # ] # PrefactorOperatorType = Callable[ -# [StatefulStepperProtocol, np.floating], np.floating +# [SymplecticStepperProtocol, np.floating], np.floating # ] OperatorType: TypeAlias = Callable[ Any, Any @@ -54,7 +60,7 @@ # Explicit stepper # ExplicitStageOperatorType = Callable[ # [ -# StatefulStepperProtocol, +# SymplecticStepperProtocol, # ExplicitSystemProtocol, # MemoryProtocol, # np.floating, @@ -64,7 +70,7 @@ # ] # ExplicitUpdateOperatorType = Callable[ # [ -# StatefulStepperProtocol, +# SymplecticStepperProtocol, # ExplicitSystemProtocol, # MemoryProtocol, # np.floating, @@ -72,7 +78,6 @@ # ], # np.floating, # ] -ExplicitOperatorsType: TypeAlias = tuple[tuple[OperatorType, ...], ...] RodType: TypeAlias = Type[RodBase] SystemCollectionType: TypeAlias = BaseSystemCollection From c033bd7dc30a92a458741eb56455b8b16938dc5d Mon Sep 17 00:00:00 2001 From: Seung Hyun Kim Date: Wed, 1 May 2024 10:34:25 -0500 Subject: [PATCH 02/12] update: use string-literal taging instead of class-typing --- elastica/timestepper/explicit_steppers.py | 24 +-- elastica/timestepper/symplectic_steppers.py | 198 ++++++++------------ elastica/timestepper/tag.py | 33 +--- 3 files changed, 100 insertions(+), 155 deletions(-) diff --git a/elastica/timestepper/explicit_steppers.py b/elastica/timestepper/explicit_steppers.py index 11ac06e97..271256baf 100644 --- a/elastica/timestepper/explicit_steppers.py +++ b/elastica/timestepper/explicit_steppers.py @@ -8,12 +8,12 @@ from elastica.typing import ( SystemCollectionType, OperatorType, - ExplicitOperatorsType, + SteppersOperatorsType, StateType, ) from elastica.systems.protocol import ExplicitSystemProtocol -from .tag import tag, ExplicitStepperTag -from .protocol import StatefulStepperProtocol, MemoryProtocol +from .tag import StepperTags, ExplicitStepperTag +from .protocol import ExplicitStepperProtocol, MemoryProtocol """ @@ -69,8 +69,8 @@ class _SystemInstanceStepper: # # noinspection PyUnresolvedReferences @staticmethod def do_step( - TimeStepper: StatefulStepperProtocol, - _stages_and_updates: ExplicitOperatorsType, + TimeStepper: ExplicitStepperProtocol, + _stages_and_updates: SteppersOperatorsType, System: ExplicitSystemProtocol, Memory: MemoryProtocol, time: np.floating, @@ -86,8 +86,8 @@ class _SystemCollectionStepper: # # noinspection PyUnresolvedReferences @staticmethod def do_step( - TimeStepper: StatefulStepperProtocol, - _stages_and_updates: ExplicitOperatorsType, + TimeStepper: ExplicitStepperProtocol, + _stages_and_updates: SteppersOperatorsType, SystemCollection: SystemCollectionType, MemoryCollection: Tuple[MemoryProtocol, ...], time: np.floating, @@ -111,7 +111,7 @@ class ExplicitStepperMethods: Can also be used as a mixin with optional cls argument below """ - def __init__(self, timestepper_instance: StatefulStepperProtocol): + def __init__(self, timestepper_instance: ExplicitStepperProtocol): take_methods_from = timestepper_instance __stages: list[OperatorType] = [ v @@ -134,7 +134,7 @@ def __init__(self, timestepper_instance: StatefulStepperProtocol): self._stages_and_updates = tuple(zip(__stages, __updates)) - def step_methods(self) -> ExplicitOperatorsType: + def step_methods(self) -> SteppersOperatorsType: return self._stages_and_updates @property @@ -147,12 +147,13 @@ def __init__(self, initial_state: StateType) -> None: self.initial_state = initial_state -@tag(ExplicitStepperTag) class EulerForward: """ Classical Euler Forward stepper. Stateless, coordinates operations only. """ + Tag: StepperTags = ExplicitStepperTag + def _first_stage( self, System: ExplicitSystemProtocol, @@ -196,13 +197,14 @@ def __init__( self.k_4 = k_4 -@tag(ExplicitStepperTag) class RungeKutta4: """ Stateless runge-kutta4. coordinates operations only, memory needs to be externally managed and allocated. """ + Tag: StepperTags = ExplicitStepperTag + # These methods should be static, but because we need to enable automatic # discovery in ExplicitStepper, these are bound to the RungeKutta4 class # For automatic discovery, the order of declaring stages here is very important diff --git a/elastica/timestepper/symplectic_steppers.py b/elastica/timestepper/symplectic_steppers.py index ea28aca6f..7b6a34bb4 100644 --- a/elastica/timestepper/symplectic_steppers.py +++ b/elastica/timestepper/symplectic_steppers.py @@ -1,6 +1,8 @@ __doc__ = """Symplectic time steppers and concepts for integrating the kinematic and dynamic equations of rod-like objects. """ -from typing import Any, Callable, List +from typing import Callable, Any + +from itertools import zip_longest from elastica.typing import ( SystemCollectionType, @@ -17,8 +19,8 @@ overload_operator_dynamic_numba, ) from elastica.systems.protocol import SymplecticSystemProtocol -from .protocol import StatefulStepperProtocol -from .tag import tag, SymplecticStepperTag +from .protocol import SymplecticStepperProtocol +from .tag import StepperTags, SymplecticStepperTag """ Developer Note @@ -29,38 +31,53 @@ """ -class _SystemInstanceStepper: - @staticmethod - def do_step( - TimeStepper: StatefulStepperProtocol, - _steps_and_prefactors: SteppersOperatorsType, - System: SymplecticSystemProtocol, - time: np.floating, - dt: np.floating, - ) -> np.floating: - 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) +class SymplecticStepperMixin: + def step_methods(self) -> SteppersOperatorsType: + # 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. + _steps: list[OperatorType] = self.get_steps() + _steps = _steps + _steps[-2::-1] - # Peel the last kinematic step and prefactor alone - last_kin_prefactor: OperatorType = _steps_and_prefactors[-1][0] - last_kin_step = _steps_and_prefactors[-1][1] + # Prefac here is necessary because the linear-exponential integrator + # needs only the prefactor and not the dt. + _prefactors: list[OperatorType] = self.get_prefactors() + _prefactors = _prefactors + _prefactors[-2::-1] + assert len(_steps) == 2 * len(_prefactors) - 1 - last_kin_step(TimeStepper, System, time, dt) - return time + last_kin_prefactor(TimeStepper, dt) + # Separate the kinematic and dynamic steps + _kinematic_steps: list[OperatorType] = _steps[::2] + _dynamic_steps: list[OperatorType] = _steps[1::2] + def no_operation(*args: Any) -> None: + pass -class _SystemCollectionStepper: - """ - Symplectic stepper collection class - """ + return tuple( + zip_longest( + _prefactors, + _kinematic_steps, + _dynamic_steps, + fillvalue=no_operation, + ) + ) + @property + def n_stages(self) -> int: + return len(self.get_prefactors()) + + def step( + self, SystemCollection: SystemCollectionType, time: np.floating, dt: np.floating + ): + steps_and_prefactors = self.step_methods() + return self.do_step(self, steps_and_prefactors, SystemCollection, time, dt) + + # TODO: Merge with .step method in the future. + # DEPRECATED: Use .step instead. @staticmethod def do_step( - TimeStepper: StatefulStepperProtocol, - _steps_and_prefactors: SteppersOperatorsType, + TimeStepper: SymplecticStepperProtocol, + steps_and_prefactors: SteppersOperatorsType, SystemCollection: SystemCollectionType, time: np.floating, dt: np.floating, @@ -68,17 +85,13 @@ def do_step( """ Function for doing symplectic stepper over the user defined rods (system). - Parameters - ---------- - SystemCollection: SystemCollectionType - time: float - dt: float - Returns ------- + time: float + The time after the integration step. """ - for kin_prefactor, kin_step, dyn_step in _steps_and_prefactors[:-1]: + for kin_prefactor, kin_step, dyn_step in steps_and_prefactors[:-1]: for system in SystemCollection._memory_blocks: kin_step(TimeStepper, system, time, dt) @@ -103,8 +116,8 @@ def do_step( 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] + 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) @@ -121,87 +134,25 @@ def do_step( return time -class SymplecticStepperMethods: - def __init__(self, timestepper_instance: StatefulStepperProtocol): - 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: List[OperatorType] = [ - 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: List[OperatorType] = [ - v - for (k, v) in take_methods_from.__class__.__dict__.items() - if k.endswith("prefactor") - ] - - def mirror(in_list: List[Callable]) -> None: - """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 - - """ - # 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: List[OperatorType] = self._steps[::2] - self._dynamic_steps: List[OperatorType] = 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: Any) -> None: - pass - - self._steps_and_prefactors: SteppersOperatorsType = tuple( - zip_longest( - self._prefactors, - self._kinematic_steps, - self._dynamic_steps, - fillvalue=NoOp, - ) - ) - - def step_methods(self) -> SteppersOperatorsType: - return self._steps_and_prefactors - - @property - def n_stages(self) -> int: - return len(self._steps_and_prefactors) - - -@tag(SymplecticStepperTag) -class PositionVerlet: +class PositionVerlet(SymplecticStepperMixin): """ Position Verlet symplectic time stepper class, which includes methods for second-order position Verlet. """ + Tag: StepperTags = SymplecticStepperTag + + def get_steps(self) -> list[OperatorType]: + return [ + self._first_kinematic_step, + self._first_dynamic_step, + ] + + def get_prefactors(self) -> list[OperatorType]: + return [ + self._first_prefactor, + ] + def _first_prefactor(self, dt: np.floating) -> np.floating: return 0.5 * dt @@ -227,14 +178,15 @@ def _first_dynamic_step( ) -@tag(SymplecticStepperTag) -class PEFRL: +class PEFRL(SymplecticStepperMixin): """ 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 """ + Tag: StepperTags = SymplecticStepperTag + # xi and chi are confusing, but be careful! ξ: np.float64 = np.float64(0.1786178958448091e0) # ξ λ: np.float64 = -np.float64(0.2123418310626054e0) # λ @@ -244,6 +196,22 @@ class PEFRL: lambda_dash_coeff: np.float64 = 0.5 * (1.0 - 2.0 * λ) xi_chi_dash_coeff: np.float64 = 1.0 - 2.0 * (ξ + χ) + def get_steps(self) -> list[OperatorType]: + return [ + self._first_kinematic_step, + self._first_dynamic_step, + self._second_kinematic_step, + self._second_dynamic_step, + self._third_kinematic_step, + ] + + def get_prefactors(self) -> list[OperatorType]: + return [ + self._first_prefactor, + self._second_prefactor, + self._third_prefactor, + ] + def _first_kinematic_prefactor(self, dt: np.floating) -> np.floating: return self.ξ * dt diff --git a/elastica/timestepper/tag.py b/elastica/timestepper/tag.py index 85814e490..14a852832 100644 --- a/elastica/timestepper/tag.py +++ b/elastica/timestepper/tag.py @@ -1,31 +1,6 @@ -from typing import Callable, Type +from typing import Literal, TypeAlias, Final -# TODO: Maybe move this for common utility -def tag(Tag: Type) -> Callable[[Type], Type]: - """ - Tag a class with arbitrary type-class - - example: - class ATag: ... - - @tag(ATag) - class A1: - ... - - assert isinstance(A1.tag, ATag) - """ - - def wrapper(cls: Type) -> Type: - cls.Tag = Tag() - return cls - - return wrapper - - -class SymplecticStepperTag: - pass - - -class ExplicitStepperTag: - pass +ExplicitStepperTag: str = "ExplicitStepper" +SymplecticStepperTag: str = "SymplecticStepper" +StepperTags: TypeAlias = Literal["SymplecticStepper", "ExplicitStepper"] From edcc4f9f765af4ddc4b7980eaa981c83a544149c Mon Sep 17 00:00:00 2001 From: Seung Hyun Kim Date: Wed, 1 May 2024 10:39:38 -0500 Subject: [PATCH 03/12] redesign integration flow: mypy checked --- elastica/timestepper/__init__.py | 61 +++---- elastica/timestepper/explicit_steppers.py | 169 ++++++++++---------- elastica/timestepper/protocol.py | 29 ++-- elastica/timestepper/symplectic_steppers.py | 19 ++- elastica/timestepper/tag.py | 4 +- examples/CatenaryCase/catenary.py | 13 +- tests/test_math/test_timestepper.py | 53 ++---- 7 files changed, 156 insertions(+), 192 deletions(-) diff --git a/elastica/timestepper/__init__.py b/elastica/timestepper/__init__.py index 10fed9d96..d385edc28 100644 --- a/elastica/timestepper/__init__.py +++ b/elastica/timestepper/__init__.py @@ -15,57 +15,39 @@ from .protocol import StepperProtocol, SymplecticStepperProtocol -# TODO: Both extend_stepper_interface and integrate should be in separate file. -# __init__ is probably not an ideal place to have these scripts. +# Deprecated: Remove in the future version +# Many script still uses this method to control timestep. Keep it for backward compatibility def extend_stepper_interface( Stepper: StepperProtocol, System: SystemType | SystemCollectionType -) -> Tuple[Callable, Tuple[Callable]]: - - # SystemStepper: StepperProtocol - if Stepper.Tag == SymplecticStepperTag: - from elastica.timestepper.symplectic_steppers import ( - SymplecticStepperMethods, - ) - - StepperMethodCollector = SymplecticStepperMethods - elif Stepper.Tag == ExplicitStepperTag: # type: ignore[no-redef] - from elastica.timestepper.explicit_steppers import ( - ExplicitStepperMethods, - ) - - StepperMethodCollector = ExplicitStepperMethods - else: - raise NotImplementedError( - "Only explicit and symplectic steppers are supported, given stepper is {}".format( - Stepper.__class__.__name__ - ) - ) - +) -> Tuple[ + Callable[ + [StepperProtocol, SystemCollectionType, np.floating, np.floating], np.floating + ], + SteppersOperatorsType, +]: # Check if system is a "collection" of smaller systems assert is_system_a_collection(System) - stepper_methods: SteppersOperatorsType = StepperMethodCollector( - Stepper - ).step_methods() - do_step_method: Callable = stepper_methods.do_step + stepper_methods: SteppersOperatorsType = Stepper.step_methods() + do_step_method: Callable = Stepper.do_step # type: ignore[attr-defined] return do_step_method, stepper_methods def integrate( - StatefulStepper: SymplecticStepperProtocol, - System: SystemType | SystemCollectionType, + Stepper: StepperProtocol, + SystemCollection: SystemCollectionType, final_time: float, n_steps: int = 1000, restart_time: float = 0.0, progress_bar: bool = True, -) -> float: +) -> np.floating: """ Parameters ---------- - StatefulStepper : SymplecticStepperProtocol + Stepper : StepperProtocol Stepper algorithm to use. - System : SystemType + SystemCollection : SystemType The elastica-system to simulate. final_time : float Total simulation time. The timestep is determined by final_time / n_steps. @@ -78,18 +60,13 @@ def integrate( """ assert final_time > 0.0, "Final time is negative!" assert n_steps > 0, "Number of integration steps is negative!" + assert is_system_a_collection(SystemCollection) - # Extend the stepper's interface after introspecting the properties - # of the system. If system is a collection of small systems (whose - # states cannot be aggregated), then stepper now loops over the system - # state - do_step, stages_and_updates = extend_stepper_interface(StatefulStepper, System) - - dt = np.float64(float(final_time) / n_steps) - time = restart_time + dt = np.float_(float(final_time) / n_steps) + time = np.float_(restart_time) for i in tqdm(range(n_steps), disable=(not progress_bar)): - time = do_step(StatefulStepper, stages_and_updates, System, time, dt) + time = Stepper.step(SystemCollection, time, dt) print("Final time of simulation is : ", time) return time diff --git a/elastica/timestepper/explicit_steppers.py b/elastica/timestepper/explicit_steppers.py index 271256baf..9b564e2e2 100644 --- a/elastica/timestepper/explicit_steppers.py +++ b/elastica/timestepper/explicit_steppers.py @@ -1,6 +1,6 @@ __doc__ = """Explicit timesteppers and concepts""" -from typing import Tuple +from typing import Type, Any import numpy as np from copy import copy @@ -65,35 +65,78 @@ """ -class _SystemInstanceStepper: - # # noinspection PyUnresolvedReferences - @staticmethod - def do_step( - TimeStepper: ExplicitStepperProtocol, - _stages_and_updates: SteppersOperatorsType, - System: ExplicitSystemProtocol, - Memory: MemoryProtocol, +class EulerForwardMemory: + def __init__(self, initial_state: StateType) -> None: + self.initial_state = initial_state + + +class RungeKutta4Memory: + """ + Stores all states of Rk within the time-stepper. Works as long as the states + are all one big numpy array, made possible by carefully using views. + + Convenience wrapper around Stateless that provides memory + """ + + def __init__( + self, + initial_state: StateType, + ) -> None: + self.initial_state = initial_state + self.k_1 = initial_state + self.k_2 = initial_state + self.k_3 = initial_state + self.k_4 = initial_state + + +class ExplicitStepperMixin: + """Base class for all explicit steppers + Can also be used as a mixin with optional cls argument below + """ + + def step_methods(self: ExplicitStepperProtocol) -> SteppersOperatorsType: + stages = self.get_stages() + updates = self.get_updates() + + assert len(stages) == len( + updates + ), "Number of stages and updates should be equal to one another" + return tuple(zip(stages, updates)) + + @property + def n_stages(self: ExplicitStepperProtocol) -> int: + return len(self.get_stages()) + + def step( + self: ExplicitStepperProtocol, + SystemCollection: SystemCollectionType, time: np.floating, dt: np.floating, ) -> np.floating: - for stage, update in _stages_and_updates: - stage(TimeStepper, System, Memory, time, dt) - time = update(TimeStepper, System, Memory, time, dt) - return time - + steps_and_prefactors = self.step_methods() + if isinstance( + self, EulerForward + ): # TODO: Cleanup - use depedency injection instead + Memory = EulerForwardMemory + elif isinstance(self, RungeKutta4): + Memory = RungeKutta4Memory # type: ignore[assignment] + else: + raise NotImplementedError(f"Memory class not defined for {self}") + memory_collection = tuple( + [Memory(initial_state=system.state) for system in SystemCollection] + ) + return ExplicitStepperMixin.do_step(self, steps_and_prefactors, SystemCollection, memory_collection, time, dt) # type: ignore[attr-defined] -class _SystemCollectionStepper: - # # noinspection PyUnresolvedReferences @staticmethod def do_step( TimeStepper: ExplicitStepperProtocol, - _stages_and_updates: SteppersOperatorsType, + stages_and_updates: SteppersOperatorsType, SystemCollection: SystemCollectionType, - MemoryCollection: Tuple[MemoryProtocol, ...], + MemoryCollection: Any, # TODO time: np.floating, dt: np.floating, ) -> np.floating: - for stage, update in _stages_and_updates: + for stage, update in stages_and_updates: SystemCollection.synchronize(time) for system, memory in zip(SystemCollection[:-1], MemoryCollection[:-1]): stage(TimeStepper, system, memory, time, dt) @@ -106,54 +149,19 @@ def do_step( return time -class ExplicitStepperMethods: - """Base class for all explicit steppers - Can also be used as a mixin with optional cls argument below - """ - - def __init__(self, timestepper_instance: ExplicitStepperProtocol): - take_methods_from = timestepper_instance - __stages: list[OperatorType] = [ - v - for (k, v) in take_methods_from.__class__.__dict__.items() - if k.endswith("stage") - ] - __updates: list[OperatorType] = [ - v - for (k, v) in take_methods_from.__class__.__dict__.items() - if k.endswith("update") - ] - - # Tuples are almost immutable - _n_stages: int = len(__stages) - _n_updates: int = len(__updates) - - assert ( - _n_stages == _n_updates - ), "Number of stages and updates should be equal to one another" - - self._stages_and_updates = tuple(zip(__stages, __updates)) - - def step_methods(self) -> SteppersOperatorsType: - return self._stages_and_updates - - @property - def n_stages(self) -> int: - return len(self._stages_and_updates) - - -class EulerForwardMemory: - def __init__(self, initial_state: StateType) -> None: - self.initial_state = initial_state - - -class EulerForward: +class EulerForward(ExplicitStepperMixin): """ Classical Euler Forward stepper. Stateless, coordinates operations only. """ Tag: StepperTags = ExplicitStepperTag + def get_stages(self) -> list[OperatorType]: + return [self._first_stage] + + def get_updates(self) -> list[OperatorType]: + return [self._first_update] + def _first_stage( self, System: ExplicitSystemProtocol, @@ -174,29 +182,6 @@ def _first_update( return time + dt -class RungeKutta4Memory: - """ - Stores all states of Rk within the time-stepper. Works as long as the states - are all one big numpy array, made possible by carefully using views. - - Convenience wrapper around Stateless that provides memory - """ - - def __init__( - self, - initial_state: StateType, - k_1: StateType, - k_2: StateType, - k_3: StateType, - k_4: StateType, - ) -> None: - self.initial_state = initial_state - self.k_1 = k_1 - self.k_2 = k_2 - self.k_3 = k_3 - self.k_4 = k_4 - - class RungeKutta4: """ Stateless runge-kutta4. coordinates operations only, memory needs @@ -205,6 +190,22 @@ class RungeKutta4: Tag: StepperTags = ExplicitStepperTag + def get_stages(self) -> list[OperatorType]: + return [ + self._first_stage, + self._second_stage, + self._third_stage, + self._fourth_stage, + ] + + def get_updates(self) -> list[OperatorType]: + return [ + self._first_update, + self._second_update, + self._third_update, + self._fourth_update, + ] + # These methods should be static, but because we need to enable automatic # discovery in ExplicitStepper, these are bound to the RungeKutta4 class # For automatic discovery, the order of declaring stages here is very important diff --git a/elastica/timestepper/protocol.py b/elastica/timestepper/protocol.py index 27086a1ea..f9101160b 100644 --- a/elastica/timestepper/protocol.py +++ b/elastica/timestepper/protocol.py @@ -1,8 +1,13 @@ __doc__ = "Time stepper interface" -from typing import Protocol, Callable, Literal, ClassVar - -from elastica.typing import SystemType, SteppersOperatorsType, OperatorType +from typing import Protocol, Callable, Literal, ClassVar, Type + +from elastica.typing import ( + SystemType, + SteppersOperatorsType, + OperatorType, + SystemCollectionType, +) from .tag import StepperTags import numpy as np @@ -18,6 +23,10 @@ def n_stages(self) -> int: ... def step_methods(self) -> SteppersOperatorsType: ... + def step( + self, SystemCollection: SystemCollectionType, time: np.floating, dt: np.floating + ) -> np.floating: ... + class SymplecticStepperProtocol(StepperProtocol, Protocol): """symplectic stepper protocol.""" @@ -27,17 +36,17 @@ def get_steps(self) -> list[OperatorType]: ... def get_prefactors(self) -> list[OperatorType]: ... -class ExplicitStepperProtocol(StepperProtocol, Protocol): - """symplectic stepper protocol.""" +class MemoryProtocol(Protocol): + @property + def initial_state(self) -> bool: ... - def get_steps(self) -> list[OperatorType]: ... - def get_prefactors(self) -> list[OperatorType]: ... +class ExplicitStepperProtocol(StepperProtocol, Protocol): + """symplectic stepper protocol.""" + def get_stages(self) -> list[OperatorType]: ... -class MemoryProtocol(Protocol): - @property - def initial_state(self) -> bool: ... + def get_updates(self) -> list[OperatorType]: ... # class _LinearExponentialIntegratorMixin: diff --git a/elastica/timestepper/symplectic_steppers.py b/elastica/timestepper/symplectic_steppers.py index 7b6a34bb4..f415680e2 100644 --- a/elastica/timestepper/symplectic_steppers.py +++ b/elastica/timestepper/symplectic_steppers.py @@ -32,7 +32,7 @@ class SymplecticStepperMixin: - def step_methods(self) -> SteppersOperatorsType: + def step_methods(self: SymplecticStepperProtocol) -> SteppersOperatorsType: # 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 @@ -63,14 +63,17 @@ def no_operation(*args: Any) -> None: ) @property - def n_stages(self) -> int: + def n_stages(self: SymplecticStepperProtocol) -> int: return len(self.get_prefactors()) def step( - self, SystemCollection: SystemCollectionType, time: np.floating, dt: np.floating - ): + self: SymplecticStepperProtocol, + SystemCollection: SystemCollectionType, + time: np.floating, + dt: np.floating, + ) -> np.floating: steps_and_prefactors = self.step_methods() - return self.do_step(self, steps_and_prefactors, SystemCollection, time, dt) + return SymplecticStepperMixin.do_step(self, steps_and_prefactors, SystemCollection, time, dt) # type: ignore[attr-defined] # TODO: Merge with .step method in the future. # DEPRECATED: Use .step instead. @@ -207,9 +210,9 @@ def get_steps(self) -> list[OperatorType]: def get_prefactors(self) -> list[OperatorType]: return [ - self._first_prefactor, - self._second_prefactor, - self._third_prefactor, + self._first_kinematic_prefactor, + self._second_kinematic_prefactor, + self._third_kinematic_prefactor, ] def _first_kinematic_prefactor(self, dt: np.floating) -> np.floating: diff --git a/elastica/timestepper/tag.py b/elastica/timestepper/tag.py index 14a852832..d23788750 100644 --- a/elastica/timestepper/tag.py +++ b/elastica/timestepper/tag.py @@ -1,6 +1,6 @@ from typing import Literal, TypeAlias, Final -ExplicitStepperTag: str = "ExplicitStepper" -SymplecticStepperTag: str = "SymplecticStepper" +ExplicitStepperTag: Final = "ExplicitStepper" +SymplecticStepperTag: Final = "SymplecticStepper" StepperTags: TypeAlias = Literal["SymplecticStepper", "ExplicitStepper"] diff --git a/examples/CatenaryCase/catenary.py b/examples/CatenaryCase/catenary.py index d6fd787b0..6a2d9e88e 100644 --- a/examples/CatenaryCase/catenary.py +++ b/examples/CatenaryCase/catenary.py @@ -1,5 +1,6 @@ import numpy as np from elastica import * +from elastica.typing import SystemType, SystemCollectionType, SymplecticStepperProtocol from post_processing import ( plot_video, @@ -11,7 +12,7 @@ class CatenarySimulator(BaseSystemCollection, Constraints, Forcing, Damping, Cal pass -catenary_sim = CatenarySimulator() +catenary_sim: SystemCollectionType = CatenarySimulator() final_time = 10 damping_constant = 0.3 time_step = 1e-4 @@ -37,7 +38,7 @@ class CatenarySimulator(BaseSystemCollection, Constraints, Forcing, Damping, Cal poisson_ratio = 0.5 shear_modulus = E / (poisson_ratio + 1.0) -base_rod = CosseratRod.straight_rod( +base_rod: SystemType = CosseratRod.straight_rod( n_elem, start, direction, @@ -76,11 +77,11 @@ class CatenaryCallBack(CallBackBaseClass): """ def __init__(self, step_skip: int, callback_params: dict): - CallBackBaseClass.__init__(self) + super().__init__() self.every = step_skip self.callback_params = callback_params - def make_callback(self, system, time, current_step: int): + def make_callback(self, system: SystemType, time: float, current_step: int) -> None: if current_step % self.every == 0: @@ -93,7 +94,7 @@ def make_callback(self, system, time, current_step: int): return -pp_list = defaultdict(list) +pp_list: dict = defaultdict(list) catenary_sim.collect_diagnostics(base_rod).using( CatenaryCallBack, step_skip=step_skip, callback_params=pp_list ) @@ -102,7 +103,7 @@ def make_callback(self, system, time, current_step: int): catenary_sim.finalize() -timestepper = PositionVerlet() +timestepper: SymplecticStepperProtocol = PositionVerlet() integrate(timestepper, catenary_sim, final_time, total_steps) position = np.array(pp_list["position"]) diff --git a/tests/test_math/test_timestepper.py b/tests/test_math/test_timestepper.py index 78c9cf605..98599a5aa 100644 --- a/tests/test_math/test_timestepper.py +++ b/tests/test_math/test_timestepper.py @@ -18,17 +18,14 @@ from elastica.timestepper.explicit_steppers import ( RungeKutta4, EulerForward, + ExplicitStepperMixin, ) - -# from elastica.timestepper.explicit_steppers import ( -# StatefulRungeKutta4, -# StatefulEulerForward, -# ) from elastica.timestepper.symplectic_steppers import ( PositionVerlet, PEFRL, + SymplecticStepperMixin, ) -from elastica.timestepper.tag import tag, SymplecticStepperTag, ExplicitStepperTag +from elastica.timestepper.tag import SymplecticStepperTag, ExplicitStepperTag from elastica.utils import Tolerance @@ -37,8 +34,8 @@ class TestExtendStepperInterface: """TODO add documentation""" - @tag(SymplecticStepperTag) - class MockSymplecticStepper: + class MockSymplecticStepper(SymplecticStepperMixin): + Tag = SymplecticStepperTag def _first_prefactor(self): pass @@ -49,8 +46,8 @@ def _first_kinematic_step(self): def _first_dynamic_step(self): pass - @tag(ExplicitStepperTag) - class MockExplicitStepper: + class MockExplicitStepper(ExplicitStepperMixin): + Tag = ExplicitStepperTag def _first_stage(self): pass @@ -58,20 +55,6 @@ def _first_stage(self): def _first_update(self): pass - from elastica.timestepper.symplectic_steppers import ( - _SystemInstanceStepper as symplectic_instance_stepper, - ) - from elastica.timestepper.symplectic_steppers import ( - _SystemCollectionStepper as symplectic_collection_stepper, - ) - - from elastica.timestepper.explicit_steppers import ( - _SystemInstanceStepper as explicit_instance_stepper, - ) - from elastica.timestepper.explicit_steppers import ( - _SystemCollectionStepper as explicit_collection_stepper, - ) - # We cannot call a stepper on a system until both the stepper # and system "see" one another (for performance reasons, mostly) # So before "seeing" the system, the stepper should not have @@ -79,16 +62,13 @@ def _first_update(self): # after "seeing" the system, via extend_stepper_interface @pytest.mark.parametrize( "stepper_and_interface", - [ - (MockSymplecticStepper, symplectic_instance_stepper), - (MockExplicitStepper, explicit_instance_stepper), - ], + [MockSymplecticStepper, MockExplicitStepper], ) def test_symplectic_stepper_interface_for_simple_systems( self, stepper_and_interface ): system = ScalarExponentialDecaySystem() - (stepper_cls, interface_cls) = stepper_and_interface + stepper_cls = stepper_and_interface stepper = stepper_cls() stepper_methods = None @@ -100,16 +80,13 @@ def test_symplectic_stepper_interface_for_simple_systems( @pytest.mark.parametrize( "stepper_and_interface", - [ - (MockSymplecticStepper, symplectic_collection_stepper), - (MockExplicitStepper, explicit_collection_stepper), - ], + [MockSymplecticStepper, MockExplicitStepper], ) def test_symplectic_stepper_interface_for_collective_systems( self, stepper_and_interface ): system = SymplecticUndampedHarmonicOscillatorCollectiveSystem() - (stepper_cls, interface_cls) = stepper_and_interface + stepper_cls = stepper_and_interface stepper = stepper_cls() stepper_methods = None @@ -122,16 +99,12 @@ def test_symplectic_stepper_interface_for_collective_systems( class MockBadStepper: Tag = int() # an arbitrary tag that doesn't mean anything - @pytest.mark.parametrize( - "stepper_and_interface", [(MockBadStepper, symplectic_collection_stepper)] - ) + @pytest.mark.parametrize("stepper_and_interface", [MockBadStepper]) def test_symplectic_stepper_throws_for_bad_stepper(self, stepper_and_interface): system = ScalarExponentialDecaySystem() - (stepper_cls, interface_cls) = stepper_and_interface + stepper_cls = stepper_and_interface stepper = stepper_cls() - assert interface_cls not in stepper.__class__.__bases__ - with pytest.raises(NotImplementedError) as excinfo: extend_stepper_interface(stepper, system) assert "steppers are supported" in str(excinfo.value) From 643fa7dfb61dc82da41757ef45210ca3fb5ae2c6 Mon Sep 17 00:00:00 2001 From: Seung Hyun Kim Date: Thu, 2 May 2024 10:41:52 -0500 Subject: [PATCH 04/12] update: save operator sequence without running at each timestep --- elastica/timestepper/explicit_steppers.py | 10 ++++++---- elastica/timestepper/protocol.py | 5 +++++ elastica/timestepper/symplectic_steppers.py | 10 ++++++---- 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/elastica/timestepper/explicit_steppers.py b/elastica/timestepper/explicit_steppers.py index 9b564e2e2..3064f00e1 100644 --- a/elastica/timestepper/explicit_steppers.py +++ b/elastica/timestepper/explicit_steppers.py @@ -1,6 +1,6 @@ __doc__ = """Explicit timesteppers and concepts""" -from typing import Type, Any +from typing import Type, Any, Final import numpy as np from copy import copy @@ -94,6 +94,9 @@ class ExplicitStepperMixin: Can also be used as a mixin with optional cls argument below """ + def __init__(self: ExplicitStepperProtocol): + self.steps_and_prefactors = self.step_methods() + def step_methods(self: ExplicitStepperProtocol) -> SteppersOperatorsType: stages = self.get_stages() updates = self.get_updates() @@ -105,7 +108,7 @@ def step_methods(self: ExplicitStepperProtocol) -> SteppersOperatorsType: @property def n_stages(self: ExplicitStepperProtocol) -> int: - return len(self.get_stages()) + return len(self.steps_and_prefactors) def step( self: ExplicitStepperProtocol, @@ -113,7 +116,6 @@ def step( time: np.floating, dt: np.floating, ) -> np.floating: - steps_and_prefactors = self.step_methods() if isinstance( self, EulerForward ): # TODO: Cleanup - use depedency injection instead @@ -125,7 +127,7 @@ def step( memory_collection = tuple( [Memory(initial_state=system.state) for system in SystemCollection] ) - return ExplicitStepperMixin.do_step(self, steps_and_prefactors, SystemCollection, memory_collection, time, dt) # type: ignore[attr-defined] + return ExplicitStepperMixin.do_step(self, self.steps_and_prefactors, SystemCollection, memory_collection, time, dt) # type: ignore[attr-defined] @staticmethod def do_step( diff --git a/elastica/timestepper/protocol.py b/elastica/timestepper/protocol.py index f9101160b..f923e62eb 100644 --- a/elastica/timestepper/protocol.py +++ b/elastica/timestepper/protocol.py @@ -18,9 +18,14 @@ class StepperProtocol(Protocol): Tag: StepperTags + def __init__(self) -> None: ... + @property def n_stages(self) -> int: ... + @property + def steps_and_prefactors(self) -> SteppersOperatorsType: ... + def step_methods(self) -> SteppersOperatorsType: ... def step( diff --git a/elastica/timestepper/symplectic_steppers.py b/elastica/timestepper/symplectic_steppers.py index f415680e2..a09124594 100644 --- a/elastica/timestepper/symplectic_steppers.py +++ b/elastica/timestepper/symplectic_steppers.py @@ -1,6 +1,6 @@ __doc__ = """Symplectic time steppers and concepts for integrating the kinematic and dynamic equations of rod-like objects. """ -from typing import Callable, Any +from typing import Callable, Any, Final from itertools import zip_longest @@ -32,6 +32,9 @@ class SymplecticStepperMixin: + def __init__(self: SymplecticStepperProtocol): + self.steps_and_prefactors: Final = self.step_methods() + def step_methods(self: SymplecticStepperProtocol) -> SteppersOperatorsType: # Let the total number of steps for the Symplectic method # be (2*n + 1) (for time-symmetry). What we do is collect @@ -64,7 +67,7 @@ def no_operation(*args: Any) -> None: @property def n_stages(self: SymplecticStepperProtocol) -> int: - return len(self.get_prefactors()) + return len(self.steps_and_prefactors) def step( self: SymplecticStepperProtocol, @@ -72,8 +75,7 @@ def step( time: np.floating, dt: np.floating, ) -> np.floating: - steps_and_prefactors = self.step_methods() - return SymplecticStepperMixin.do_step(self, steps_and_prefactors, SystemCollection, time, dt) # type: ignore[attr-defined] + return SymplecticStepperMixin.do_step(self, self.steps_and_prefactors, SystemCollection, time, dt) # type: ignore[attr-defined] # TODO: Merge with .step method in the future. # DEPRECATED: Use .step instead. From c5a9c5acee4c9ac7f618121cf790e7dad96f1e0f Mon Sep 17 00:00:00 2001 From: Seung Hyun Kim Date: Thu, 2 May 2024 13:58:35 -0500 Subject: [PATCH 05/12] test: remove symple system stepping - no longer support --- tests/test_math/test_timestepper.py | 24 +++--------------------- 1 file changed, 3 insertions(+), 21 deletions(-) diff --git a/tests/test_math/test_timestepper.py b/tests/test_math/test_timestepper.py index 98599a5aa..3868013f9 100644 --- a/tests/test_math/test_timestepper.py +++ b/tests/test_math/test_timestepper.py @@ -61,32 +61,14 @@ def _first_update(self): # the interface (interface_cls). It should however have the interface # after "seeing" the system, via extend_stepper_interface @pytest.mark.parametrize( - "stepper_and_interface", - [MockSymplecticStepper, MockExplicitStepper], - ) - def test_symplectic_stepper_interface_for_simple_systems( - self, stepper_and_interface - ): - system = ScalarExponentialDecaySystem() - stepper_cls = stepper_and_interface - stepper = stepper_cls() - - stepper_methods = None - assert stepper_methods is None - - _, stepper_methods = extend_stepper_interface(stepper, system) - - assert stepper_methods - - @pytest.mark.parametrize( - "stepper_and_interface", + "stepper_module", [MockSymplecticStepper, MockExplicitStepper], ) def test_symplectic_stepper_interface_for_collective_systems( - self, stepper_and_interface + self, stepper_module ): system = SymplecticUndampedHarmonicOscillatorCollectiveSystem() - stepper_cls = stepper_and_interface + stepper_cls = stepper_module.steps_and_prefactors stepper = stepper_cls() stepper_methods = None From 3fb7dac7b8f6ecd7b2b4f4db04260ff729ed92b6 Mon Sep 17 00:00:00 2001 From: Seung Hyun Kim Date: Thu, 2 May 2024 14:31:25 -0500 Subject: [PATCH 06/12] fix: tuple is not supposed to be mutable --- elastica/modules/base_system.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/elastica/modules/base_system.py b/elastica/modules/base_system.py index 34f42c7d4..4c3d46528 100644 --- a/elastica/modules/base_system.py +++ b/elastica/modules/base_system.py @@ -5,7 +5,8 @@ Basic coordinating for multiple, smaller systems that have an independently integrable interface (i.e. works with symplectic or explicit routines `timestepper.py`.) """ -from typing import Iterable, Callable, AnyStr +from typing import Iterable, Callable, AnyStr, Type +from elastica.typing import SystemType import numpy as np @@ -57,7 +58,7 @@ def __init__(self): # We need to initialize our mixin classes super(BaseSystemCollection, self).__init__() # List of system types/bases that are allowed - self.allowed_sys_types = (RodBase, RigidBodyBase, SurfaceBase) + self.allowed_sys_types:tuple[Type[SystemType], ...] = (RodBase, RigidBodyBase, SurfaceBase) # List of systems to be integrated self._systems = [] # Flag Finalize: Finalizing twice will cause an error, @@ -98,11 +99,11 @@ def insert(self, idx, system): def __str__(self): return str(self._systems) - def extend_allowed_types(self, additional_types): - self.allowed_sys_types += additional_types + def extend_allowed_types(self, additional_types: list[Type[SystemType], ...]): + self.allowed_sys_types = tuple(list(self.allowed_sys_types)+additional_types) - def override_allowed_types(self, allowed_types): - self.allowed_sys_types = allowed_types + def override_allowed_types(self, allowed_types: list[Type[SystemType], ...]): + self.allowed_sys_types = tuple(allowed_types) def _get_sys_idx_if_valid(self, sys_to_be_added): from numpy import int_ as npint From 7dcf23895f52a1b74fc18e1124fb447d3a95e6ca Mon Sep 17 00:00:00 2001 From: Seung Hyun Kim Date: Thu, 2 May 2024 15:01:37 -0500 Subject: [PATCH 07/12] feat: allow single system integration --- elastica/timestepper/__init__.py | 29 ++++++++++++--------- elastica/timestepper/explicit_steppers.py | 13 +++++++++ elastica/timestepper/symplectic_steppers.py | 20 ++++++++++++++ 3 files changed, 50 insertions(+), 12 deletions(-) diff --git a/elastica/timestepper/__init__.py b/elastica/timestepper/__init__.py index d385edc28..2e60afa7d 100644 --- a/elastica/timestepper/__init__.py +++ b/elastica/timestepper/__init__.py @@ -11,14 +11,14 @@ from .symplectic_steppers import PositionVerlet, PEFRL from .explicit_steppers import RungeKutta4, EulerForward -from .tag import SymplecticStepperTag, ExplicitStepperTag +from .tag import SymplecticStepperTag, ExplicitStepperTag, allowed_stepper_tags from .protocol import StepperProtocol, SymplecticStepperProtocol # Deprecated: Remove in the future version # Many script still uses this method to control timestep. Keep it for backward compatibility def extend_stepper_interface( - Stepper: StepperProtocol, System: SystemType | SystemCollectionType + stepper: StepperProtocol, system_collection: SystemCollectionType ) -> Tuple[ Callable[ [StepperProtocol, SystemCollectionType, np.floating, np.floating], np.floating @@ -26,16 +26,18 @@ def extend_stepper_interface( SteppersOperatorsType, ]: # Check if system is a "collection" of smaller systems - assert is_system_a_collection(System) + assert is_system_a_collection(system_collection), "Only system-collection type can be used for timestepping. Use BaseSystemCollection." + if not hasattr(stepper, "Tag") or stepper.Tag not in allowed_stepper_tags: + raise NotImplementedError(f"{stepper} steppers is not supported. Only {allowed_stepper_tags} steppers are supported") - stepper_methods: SteppersOperatorsType = Stepper.step_methods() - do_step_method: Callable = Stepper.do_step # type: ignore[attr-defined] + stepper_methods: SteppersOperatorsType = stepper.steps_and_prefactors + do_step_method: Callable = stepper.do_step # type: ignore[attr-defined] return do_step_method, stepper_methods def integrate( - Stepper: StepperProtocol, - SystemCollection: SystemCollectionType, + stepper: StepperProtocol, + systems: SystemType | SystemCollectionType, final_time: float, n_steps: int = 1000, restart_time: float = 0.0, @@ -45,9 +47,9 @@ def integrate( Parameters ---------- - Stepper : StepperProtocol + stepper : StepperProtocol Stepper algorithm to use. - SystemCollection : SystemType + systems : SystemType | SystemCollectionType The elastica-system to simulate. final_time : float Total simulation time. The timestep is determined by final_time / n_steps. @@ -60,13 +62,16 @@ def integrate( """ assert final_time > 0.0, "Final time is negative!" assert n_steps > 0, "Number of integration steps is negative!" - assert is_system_a_collection(SystemCollection) dt = np.float_(float(final_time) / n_steps) time = np.float_(restart_time) - for i in tqdm(range(n_steps), disable=(not progress_bar)): - time = Stepper.step(SystemCollection, time, dt) + if is_system_a_collection(systems): + for i in tqdm(range(n_steps), disable=(not progress_bar)): + time = stepper.step(systems, time, dt) + else: + for i in tqdm(range(n_steps), disable=(not progress_bar)): + time = stepper.step_single_instance(systems, time, dt) print("Final time of simulation is : ", time) return time diff --git a/elastica/timestepper/explicit_steppers.py b/elastica/timestepper/explicit_steppers.py index 3064f00e1..5a336c9b1 100644 --- a/elastica/timestepper/explicit_steppers.py +++ b/elastica/timestepper/explicit_steppers.py @@ -6,6 +6,7 @@ from copy import copy from elastica.typing import ( + SystemType, SystemCollectionType, OperatorType, SteppersOperatorsType, @@ -150,6 +151,18 @@ def do_step( ) return time + def step_single_instance( + self: ExplicitStepperProtocol, + System: SystemType, + Memory: MemoryProtocol, + time: np.floating, + dt: np.floating, + ) -> np.floating: + for stage, update in self.steps_and_prefactors: + stage(System, Memory, time, dt) + time = update(System, Memory, time, dt) + return time + class EulerForward(ExplicitStepperMixin): """ diff --git a/elastica/timestepper/symplectic_steppers.py b/elastica/timestepper/symplectic_steppers.py index a09124594..c223a911b 100644 --- a/elastica/timestepper/symplectic_steppers.py +++ b/elastica/timestepper/symplectic_steppers.py @@ -5,6 +5,7 @@ from itertools import zip_longest from elastica.typing import ( + SystemType, SystemCollectionType, # StepOperatorType, # PrefactorOperatorType, @@ -138,6 +139,25 @@ def do_step( return time + def step_single_instance( + self: SymplecticStepperProtocol, + System: SystemType, + time: np.floating, + dt: np.floating, + ) -> np.floating: + + for (kin_prefactor, kin_step, dyn_step) in self.steps_and_prefactors[:-1]: + kin_step(System, time, dt) + time += kin_prefactor(dt) + System.update_internal_forces_and_torques(time) + dyn_step(System, time, dt) + + # Peel the last kinematic step and prefactor alone + last_kin_prefactor = self.steps_and_prefactors[-1][0] + last_kin_step = self.steps_and_prefactors[-1][1] + + last_kin_step(System, time, dt) + return time + last_kin_prefactor(dt) # type: ignore[no-any-return] class PositionVerlet(SymplecticStepperMixin): """ From b615cd42e3cd51722d90294440bfcd9551a98bf7 Mon Sep 17 00:00:00 2001 From: Seung Hyun Kim Date: Thu, 2 May 2024 15:14:03 -0500 Subject: [PATCH 08/12] test: fix broken tests due to changes in steppig method --- elastica/modules/base_system.py | 8 +- elastica/systems/analytical.py | 4 +- elastica/systems/protocol.py | 4 +- elastica/timestepper/__init__.py | 18 +- elastica/timestepper/explicit_steppers.py | 16 +- elastica/timestepper/protocol.py | 8 +- elastica/timestepper/symplectic_steppers.py | 33 +-- elastica/timestepper/tag.py | 2 + tests/test_math/test_timestepper.py | 251 +++++++++----------- 9 files changed, 164 insertions(+), 180 deletions(-) diff --git a/elastica/modules/base_system.py b/elastica/modules/base_system.py index 4c3d46528..8cbc918e0 100644 --- a/elastica/modules/base_system.py +++ b/elastica/modules/base_system.py @@ -58,7 +58,7 @@ def __init__(self): # We need to initialize our mixin classes super(BaseSystemCollection, self).__init__() # List of system types/bases that are allowed - self.allowed_sys_types:tuple[Type[SystemType], ...] = (RodBase, RigidBodyBase, SurfaceBase) + self.allowed_sys_types: tuple[Type, ...] = (RodBase, RigidBodyBase, SurfaceBase) # List of systems to be integrated self._systems = [] # Flag Finalize: Finalizing twice will cause an error, @@ -99,10 +99,10 @@ def insert(self, idx, system): def __str__(self): return str(self._systems) - def extend_allowed_types(self, additional_types: list[Type[SystemType], ...]): - self.allowed_sys_types = tuple(list(self.allowed_sys_types)+additional_types) + def extend_allowed_types(self, additional_types: list[Type, ...]): + self.allowed_sys_types += additional_types - def override_allowed_types(self, allowed_types: list[Type[SystemType], ...]): + def override_allowed_types(self, allowed_types: list[Type, ...]): self.allowed_sys_types = tuple(allowed_types) def _get_sys_idx_if_valid(self, sys_to_be_added): diff --git a/elastica/systems/analytical.py b/elastica/systems/analytical.py index 1b64a570d..b03a917aa 100644 --- a/elastica/systems/analytical.py +++ b/elastica/systems/analytical.py @@ -3,6 +3,7 @@ import numpy as np from elastica._rotations import _rotate from elastica.rod.data_structures import _RodSymplecticStepperMixin +from elastica.rod.rod_base import RodBase class BaseStatefulSystem: @@ -355,8 +356,9 @@ def make_simple_system_with_positions_directors( ) -class SimpleSystemWithPositionsDirectors(_RodSymplecticStepperMixin): +class SimpleSystemWithPositionsDirectors(_RodSymplecticStepperMixin, RodBase): def __init__(self, start_position, end_position, start_director): + self.ring_rod_flag = False # TODO: self.a = 0.5 self.b = 1 self.c = 2 diff --git a/elastica/systems/protocol.py b/elastica/systems/protocol.py index c2864f608..3d78f32e0 100644 --- a/elastica/systems/protocol.py +++ b/elastica/systems/protocol.py @@ -38,6 +38,8 @@ def external_forces(self) -> NDArray: ... @property def external_torques(self) -> NDArray: ... + def update_internal_forces_and_torques(self, time: np.floating) -> None: ... + class SymplecticSystemProtocol(SystemProtocol, Protocol): """ @@ -64,8 +66,6 @@ def dynamic_rates( self, time: np.floating, prefac: np.floating ) -> tuple[NDArray]: ... - def update_internal_forces_and_torques(self, time: np.floating) -> None: ... - class ExplicitSystemProtocol(SystemProtocol, Protocol): # TODO: Temporarily made to handle explicit stepper. diff --git a/elastica/timestepper/__init__.py b/elastica/timestepper/__init__.py index 2e60afa7d..49876dd43 100644 --- a/elastica/timestepper/__init__.py +++ b/elastica/timestepper/__init__.py @@ -1,6 +1,6 @@ __doc__ = """Timestepping utilities to be used with Rod and RigidBody classes""" -from typing import Tuple, List, Callable, Type, Any +from typing import Tuple, List, Callable, Type, Any, overload from elastica.typing import SystemType, SystemCollectionType, SteppersOperatorsType import numpy as np @@ -26,9 +26,13 @@ def extend_stepper_interface( SteppersOperatorsType, ]: # Check if system is a "collection" of smaller systems - assert is_system_a_collection(system_collection), "Only system-collection type can be used for timestepping. Use BaseSystemCollection." + assert is_system_a_collection( + system_collection + ), "Only system-collection type can be used for timestepping. Use BaseSystemCollection." if not hasattr(stepper, "Tag") or stepper.Tag not in allowed_stepper_tags: - raise NotImplementedError(f"{stepper} steppers is not supported. Only {allowed_stepper_tags} steppers are supported") + raise NotImplementedError( + f"{stepper} steppers is not supported. Only {allowed_stepper_tags} steppers are supported" + ) stepper_methods: SteppersOperatorsType = stepper.steps_and_prefactors do_step_method: Callable = stepper.do_step # type: ignore[attr-defined] @@ -42,7 +46,7 @@ def integrate( n_steps: int = 1000, restart_time: float = 0.0, progress_bar: bool = True, -) -> np.floating: +) -> float: """ Parameters @@ -68,10 +72,10 @@ def integrate( if is_system_a_collection(systems): for i in tqdm(range(n_steps), disable=(not progress_bar)): - time = stepper.step(systems, time, dt) + time = stepper.step(systems, time, dt) # type: ignore[arg-type] else: for i in tqdm(range(n_steps), disable=(not progress_bar)): - time = stepper.step_single_instance(systems, time, dt) + time = stepper.step_single_instance(systems, time, dt) # type: ignore[arg-type] print("Final time of simulation is : ", time) - return time + return float(time) diff --git a/elastica/timestepper/explicit_steppers.py b/elastica/timestepper/explicit_steppers.py index 5a336c9b1..00afbaf6b 100644 --- a/elastica/timestepper/explicit_steppers.py +++ b/elastica/timestepper/explicit_steppers.py @@ -133,22 +133,20 @@ def step( @staticmethod def do_step( TimeStepper: ExplicitStepperProtocol, - stages_and_updates: SteppersOperatorsType, + steps_and_prefactors: SteppersOperatorsType, SystemCollection: SystemCollectionType, MemoryCollection: Any, # TODO time: np.floating, dt: np.floating, ) -> np.floating: - for stage, update in stages_and_updates: + for stage, update in steps_and_prefactors: SystemCollection.synchronize(time) for system, memory in zip(SystemCollection[:-1], MemoryCollection[:-1]): - stage(TimeStepper, system, memory, time, dt) - _ = update(TimeStepper, system, memory, time, dt) + stage(system, memory, time, dt) + _ = update(system, memory, time, dt) - stage(TimeStepper, SystemCollection[-1], MemoryCollection[-1], time, dt) - time = update( - TimeStepper, SystemCollection[-1], MemoryCollection[-1], time, dt - ) + stage(SystemCollection[-1], MemoryCollection[-1], time, dt) + time = update(SystemCollection[-1], MemoryCollection[-1], time, dt) return time def step_single_instance( @@ -197,7 +195,7 @@ def _first_update( return time + dt -class RungeKutta4: +class RungeKutta4(ExplicitStepperMixin): """ Stateless runge-kutta4. coordinates operations only, memory needs to be externally managed and allocated. diff --git a/elastica/timestepper/protocol.py b/elastica/timestepper/protocol.py index f923e62eb..d27279269 100644 --- a/elastica/timestepper/protocol.py +++ b/elastica/timestepper/protocol.py @@ -17,21 +17,23 @@ class StepperProtocol(Protocol): """Protocol for all time-steppers""" Tag: StepperTags + steps_and_prefactors: SteppersOperatorsType def __init__(self) -> None: ... @property def n_stages(self) -> int: ... - @property - def steps_and_prefactors(self) -> SteppersOperatorsType: ... - def step_methods(self) -> SteppersOperatorsType: ... def step( self, SystemCollection: SystemCollectionType, time: np.floating, dt: np.floating ) -> np.floating: ... + def step_single_instance( + self, SystemCollection: SystemType, time: np.floating, dt: np.floating + ) -> np.floating: ... + class SymplecticStepperProtocol(StepperProtocol, Protocol): """symplectic stepper protocol.""" diff --git a/elastica/timestepper/symplectic_steppers.py b/elastica/timestepper/symplectic_steppers.py index c223a911b..8e38e279a 100644 --- a/elastica/timestepper/symplectic_steppers.py +++ b/elastica/timestepper/symplectic_steppers.py @@ -34,21 +34,18 @@ class SymplecticStepperMixin: def __init__(self: SymplecticStepperProtocol): - self.steps_and_prefactors: Final = self.step_methods() + self.steps_and_prefactors: Final[SteppersOperatorsType] = self.step_methods() def step_methods(self: SymplecticStepperProtocol) -> SteppersOperatorsType: # 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. + # be (2*n + 1) (for time-symmetry). _steps: list[OperatorType] = self.get_steps() - _steps = _steps + _steps[-2::-1] - # Prefac here is necessary because the linear-exponential integrator # needs only the prefactor and not the dt. _prefactors: list[OperatorType] = self.get_prefactors() - _prefactors = _prefactors + _prefactors[-2::-1] - assert len(_steps) == 2 * len(_prefactors) - 1 + assert int(np.ceil(len(_steps) / 2)) == len( + _prefactors + ), f"{len(_steps)=}, {len(_prefactors)=}" # Separate the kinematic and dynamic steps _kinematic_steps: list[OperatorType] = _steps[::2] @@ -100,9 +97,9 @@ def do_step( for kin_prefactor, kin_step, dyn_step in steps_and_prefactors[:-1]: for system in SystemCollection._memory_blocks: - kin_step(TimeStepper, system, time, dt) + kin_step(system, time, dt) - time += kin_prefactor(TimeStepper, dt) + time += kin_prefactor(dt) # Constrain only values SystemCollection.constrain_values(time) @@ -116,7 +113,7 @@ def do_step( SystemCollection.synchronize(time) for system in SystemCollection._memory_blocks: - dyn_step(TimeStepper, system, time, dt) + dyn_step(system, time, dt) # Constrain only rates SystemCollection.constrain_rates(time) @@ -126,8 +123,8 @@ def do_step( 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) + last_kin_step(system, time, dt) + time += last_kin_prefactor(dt) SystemCollection.constrain_values(time) # Call back function, will call the user defined call back functions and store data @@ -146,7 +143,7 @@ def step_single_instance( dt: np.floating, ) -> np.floating: - for (kin_prefactor, kin_step, dyn_step) in self.steps_and_prefactors[:-1]: + for kin_prefactor, kin_step, dyn_step in self.steps_and_prefactors[:-1]: kin_step(System, time, dt) time += kin_prefactor(dt) System.update_internal_forces_and_torques(time) @@ -159,6 +156,7 @@ def step_single_instance( last_kin_step(System, time, dt) return time + last_kin_prefactor(dt) # type: ignore[no-any-return] + class PositionVerlet(SymplecticStepperMixin): """ Position Verlet symplectic time stepper class, which @@ -171,11 +169,13 @@ def get_steps(self) -> list[OperatorType]: return [ self._first_kinematic_step, self._first_dynamic_step, + self._first_kinematic_step, ] def get_prefactors(self) -> list[OperatorType]: return [ self._first_prefactor, + self._first_prefactor, ] def _first_prefactor(self, dt: np.floating) -> np.floating: @@ -222,19 +222,22 @@ class PEFRL(SymplecticStepperMixin): xi_chi_dash_coeff: np.float64 = 1.0 - 2.0 * (ξ + χ) def get_steps(self) -> list[OperatorType]: - return [ + operators = [ self._first_kinematic_step, self._first_dynamic_step, self._second_kinematic_step, self._second_dynamic_step, self._third_kinematic_step, ] + return operators + operators[-2::-1] def get_prefactors(self) -> list[OperatorType]: return [ self._first_kinematic_prefactor, self._second_kinematic_prefactor, self._third_kinematic_prefactor, + self._second_kinematic_prefactor, + self._first_kinematic_prefactor, ] def _first_kinematic_prefactor(self, dt: np.floating) -> np.floating: diff --git a/elastica/timestepper/tag.py b/elastica/timestepper/tag.py index d23788750..8f0d77517 100644 --- a/elastica/timestepper/tag.py +++ b/elastica/timestepper/tag.py @@ -4,3 +4,5 @@ ExplicitStepperTag: Final = "ExplicitStepper" SymplecticStepperTag: Final = "SymplecticStepper" StepperTags: TypeAlias = Literal["SymplecticStepper", "ExplicitStepper"] + +allowed_stepper_tags: list[StepperTags] = [ExplicitStepperTag, SymplecticStepperTag] diff --git a/tests/test_math/test_timestepper.py b/tests/test_math/test_timestepper.py index 3868013f9..69c533e9c 100644 --- a/tests/test_math/test_timestepper.py +++ b/tests/test_math/test_timestepper.py @@ -37,22 +37,34 @@ class TestExtendStepperInterface: class MockSymplecticStepper(SymplecticStepperMixin): Tag = SymplecticStepperTag - def _first_prefactor(self): + def get_steps(self): + return [self._kinematic_step, self._dynamic_step, self._kinematic_step] + + def get_prefactors(self): + return [self._prefactor, self._prefactor] + + def _prefactor(self): pass - def _first_kinematic_step(self): + def _kinematic_step(self): pass - def _first_dynamic_step(self): + def _dynamic_step(self): pass class MockExplicitStepper(ExplicitStepperMixin): Tag = ExplicitStepperTag - def _first_stage(self): + def get_stages(self): + return [self._stage] + + def get_updates(self): + return [self._update] + + def _stage(self): pass - def _first_update(self): + def _update(self): pass # We cannot call a stepper on a system until both the stepper @@ -64,28 +76,33 @@ def _first_update(self): "stepper_module", [MockSymplecticStepper, MockExplicitStepper], ) - def test_symplectic_stepper_interface_for_collective_systems( - self, stepper_module - ): + def test_symplectic_stepper_interface_for_collective_systems(self, stepper_module): system = SymplecticUndampedHarmonicOscillatorCollectiveSystem() - stepper_cls = stepper_module.steps_and_prefactors - stepper = stepper_cls() + stepper = stepper_module() stepper_methods = None - assert stepper_methods is None - _, stepper_methods = extend_stepper_interface(stepper, system) - assert stepper_methods + assert stepper_methods == stepper.steps_and_prefactors class MockBadStepper: Tag = int() # an arbitrary tag that doesn't mean anything - @pytest.mark.parametrize("stepper_and_interface", [MockBadStepper]) - def test_symplectic_stepper_throws_for_bad_stepper(self, stepper_and_interface): + @pytest.mark.parametrize("stepper_module", [MockSymplecticStepper]) + def test_symplectic_stepper_throws_for_bad_stepper_for_simple_system( + self, stepper_module + ): system = ScalarExponentialDecaySystem() - stepper_cls = stepper_and_interface - stepper = stepper_cls() + stepper = stepper_module() + + with pytest.raises(AssertionError) as excinfo: + extend_stepper_interface(stepper, system) + assert "Only system-collection type can be used" in str(excinfo.value) + + @pytest.mark.parametrize("stepper_module", [MockBadStepper]) + def test_symplectic_stepper_throws_for_bad_stepper(self, stepper_module): + system = SymplecticUndampedHarmonicOscillatorCollectiveSystem() + stepper = stepper_module() with pytest.raises(NotImplementedError) as excinfo: extend_stepper_interface(stepper, system) @@ -112,127 +129,79 @@ def test_integrate_throws_an_assert_for_negative_total_steps(): SymplecticSteppers = [PositionVerlet, PEFRL] -""" -class TestExplicitSteppers: - @pytest.mark.parametrize("stepper", StatefulExplicitSteppers) - def test_against_scalar_exponential(self, stepper): - system = ScalarExponentialDecaySystem(-1, 1) - final_time = 1 - n_steps = 1000 - integrate(stepper(), system, final_time=final_time, n_steps=n_steps) - - assert_allclose( - system.state, - system.analytical_solution(final_time), - rtol=Tolerance.rtol() * 1e3, - atol=Tolerance.atol(), - ) - - @pytest.mark.parametrize("stepper", StatefulExplicitSteppers[:-1]) - def test_against_undamped_harmonic_oscillator(self, stepper): - system = UndampedSimpleHarmonicOscillatorSystem() - final_time = 4.0 * np.pi - n_steps = 2000 - integrate(stepper(), system, final_time=final_time, n_steps=n_steps) - - assert_allclose( - system.state, - system.analytical_solution(final_time), - rtol=Tolerance.rtol(), - atol=Tolerance.atol(), - ) - - @pytest.mark.parametrize("stepper", StatefulExplicitSteppers[:-1]) - def test_against_damped_harmonic_oscillator(self, stepper): - system = DampedSimpleHarmonicOscillatorSystem() - final_time = 4.0 * np.pi - n_steps = 2000 - integrate(stepper(), system, final_time=final_time, n_steps=n_steps) - - assert_allclose( - system.state, - system.analytical_solution(final_time), - rtol=Tolerance.rtol(), - atol=Tolerance.atol(), - ) - - def test_linear_exponential_integrator(self): - system = MultipleFrameRotationSystem(n_frames=128) - final_time = np.pi - n_steps = 1000 - integrate( - StatefulLinearExponentialIntegrator(), - system, - final_time=final_time, - n_steps=n_steps, - ) - - assert_allclose( - system.linearly_evolving_state, - system.analytical_solution(final_time), - atol=1e-4, - ) - - @pytest.mark.parametrize("explicit_stepper", StatefulExplicitSteppers[:-1]) - def test_explicit_against_analytical_system(self, explicit_stepper): - system = SecondOrderHybridSystem() - final_time = 1.0 - n_steps = 2000 - integrate(explicit_stepper(), system, final_time=final_time, n_steps=n_steps) - - assert_allclose( - system.final_solution(final_time), - system.analytical_solution(final_time), - rtol=Tolerance.rtol() * 1e2, - atol=Tolerance.atol(), - ) -""" - - -class TestSymplecticSteppers: - @pytest.mark.parametrize("stepper", SymplecticSteppers) - def test_symplectic_against_undamped_harmonic_oscillator(self, stepper): - system = SymplecticUndampedSimpleHarmonicOscillatorSystem( - omega=1.0 * np.pi, init_val=np.array([0.2, 0.8]) - ) - final_time = 4.0 * np.pi - n_steps = 2000 - time_stepper = stepper() - integrate(time_stepper, system, final_time=final_time, n_steps=n_steps) - - # Symplectic systems conserve energy to a certain extent - assert_allclose( - *system.compute_energy(final_time), - rtol=Tolerance.rtol() * 1e1, - atol=Tolerance.atol(), - ) - - # assert_allclose( - # system._state, - # system.analytical_solution(final_time), - # rtol=Tolerance.rtol(), - # atol=Tolerance.atol(), - # ) - - -""" - @pytest.mark.xfail - @pytest.mark.parametrize("symplectic_stepper", SymplecticSteppers) - def test_hybrid_symplectic_against_analytical_system(self, symplectic_stepper): - system = SecondOrderHybridSystem() - final_time = 1.0 - n_steps = 2000 - # stepper = SymplecticCosseratRodStepper(symplectic_stepper=symplectic_stepper()) - stepper = symplectic_stepper() - integrate(stepper, system, final_time=final_time, n_steps=n_steps) - - assert_allclose( - system.final_solution(final_time), - system.analytical_solution(final_time), - rtol=Tolerance.rtol() * 1e2, - atol=Tolerance.atol(), - ) -""" +# class TestExplicitSteppers: +# @pytest.mark.parametrize("stepper", StatefulExplicitSteppers) +# def test_against_scalar_exponential(self, stepper): +# system = ScalarExponentialDecaySystem(-1, 1) +# final_time = 1 +# n_steps = 1000 +# integrate(stepper(), system, final_time=final_time, n_steps=n_steps) +# +# assert_allclose( +# system.state, +# system.analytical_solution(final_time), +# rtol=Tolerance.rtol() * 1e3, +# atol=Tolerance.atol(), +# ) +# +# @pytest.mark.parametrize("stepper", StatefulExplicitSteppers[:-1]) +# def test_against_undamped_harmonic_oscillator(self, stepper): +# system = UndampedSimpleHarmonicOscillatorSystem() +# final_time = 4.0 * np.pi +# n_steps = 2000 +# integrate(stepper(), system, final_time=final_time, n_steps=n_steps) +# +# assert_allclose( +# system.state, +# system.analytical_solution(final_time), +# rtol=Tolerance.rtol(), +# atol=Tolerance.atol(), +# ) +# +# @pytest.mark.parametrize("stepper", StatefulExplicitSteppers[:-1]) +# def test_against_damped_harmonic_oscillator(self, stepper): +# system = DampedSimpleHarmonicOscillatorSystem() +# final_time = 4.0 * np.pi +# n_steps = 2000 +# integrate(stepper(), system, final_time=final_time, n_steps=n_steps) +# +# assert_allclose( +# system.state, +# system.analytical_solution(final_time), +# rtol=Tolerance.rtol(), +# atol=Tolerance.atol(), +# ) +# +# def test_linear_exponential_integrator(self): +# system = MultipleFrameRotationSystem(n_frames=128) +# final_time = np.pi +# n_steps = 1000 +# integrate( +# StatefulLinearExponentialIntegrator(), +# system, +# final_time=final_time, +# n_steps=n_steps, +# ) +# +# assert_allclose( +# system.linearly_evolving_state, +# system.analytical_solution(final_time), +# atol=1e-4, +# ) +# +# @pytest.mark.parametrize("explicit_stepper", StatefulExplicitSteppers[:-1]) +# def test_explicit_against_analytical_system(self, explicit_stepper): +# system = SecondOrderHybridSystem() +# final_time = 1.0 +# n_steps = 2000 +# integrate(explicit_stepper(), system, final_time=final_time, n_steps=n_steps) +# +# assert_allclose( +# system.final_solution(final_time), +# system.analytical_solution(final_time), +# rtol=Tolerance.rtol() * 1e2, +# atol=Tolerance.atol(), +# ) class TestSteppersAgainstCollectiveSystems: @@ -350,9 +319,13 @@ def test_symplectics_against_ellipse_motion(self, symplectic_stepper): ) final_time = 1.0 n_steps = 1000 + dt = final_time / n_steps + stepper = symplectic_stepper() - integrate(stepper, rod_like_system, final_time=final_time, n_steps=n_steps) + time = 0.0 + for _ in range(n_steps): + time = stepper.step_single_instance(rod_like_system, time, dt) assert_allclose( rod_like_system.position_collection, From 0531ef87aec8a066e30783d5580f981faf56eaf2 Mon Sep 17 00:00:00 2001 From: Seung Hyun Kim Date: Fri, 3 May 2024 17:54:27 -0500 Subject: [PATCH 09/12] remove: timestepper tag --- elastica/timestepper/__init__.py | 12 +++++------- elastica/timestepper/explicit_steppers.py | 5 ----- elastica/timestepper/protocol.py | 2 -- elastica/timestepper/symplectic_steppers.py | 5 ----- elastica/timestepper/tag.py | 8 -------- tests/test_math/test_timestepper.py | 7 ++----- 6 files changed, 7 insertions(+), 32 deletions(-) delete mode 100644 elastica/timestepper/tag.py diff --git a/elastica/timestepper/__init__.py b/elastica/timestepper/__init__.py index 49876dd43..5b6111d09 100644 --- a/elastica/timestepper/__init__.py +++ b/elastica/timestepper/__init__.py @@ -11,7 +11,6 @@ from .symplectic_steppers import PositionVerlet, PEFRL from .explicit_steppers import RungeKutta4, EulerForward -from .tag import SymplecticStepperTag, ExplicitStepperTag, allowed_stepper_tags from .protocol import StepperProtocol, SymplecticStepperProtocol @@ -29,13 +28,12 @@ def extend_stepper_interface( assert is_system_a_collection( system_collection ), "Only system-collection type can be used for timestepping. Use BaseSystemCollection." - if not hasattr(stepper, "Tag") or stepper.Tag not in allowed_stepper_tags: - raise NotImplementedError( - f"{stepper} steppers is not supported. Only {allowed_stepper_tags} steppers are supported" - ) - stepper_methods: SteppersOperatorsType = stepper.steps_and_prefactors - do_step_method: Callable = stepper.do_step # type: ignore[attr-defined] + try: + stepper_methods: SteppersOperatorsType = stepper.steps_and_prefactors + do_step_method: Callable = stepper.do_step # type: ignore[attr-defined] + except AttributeError as e: + raise NotImplementedError(f"{stepper} stepper is not supported.") from e return do_step_method, stepper_methods diff --git a/elastica/timestepper/explicit_steppers.py b/elastica/timestepper/explicit_steppers.py index 00afbaf6b..56140e111 100644 --- a/elastica/timestepper/explicit_steppers.py +++ b/elastica/timestepper/explicit_steppers.py @@ -13,7 +13,6 @@ StateType, ) from elastica.systems.protocol import ExplicitSystemProtocol -from .tag import StepperTags, ExplicitStepperTag from .protocol import ExplicitStepperProtocol, MemoryProtocol @@ -167,8 +166,6 @@ class EulerForward(ExplicitStepperMixin): Classical Euler Forward stepper. Stateless, coordinates operations only. """ - Tag: StepperTags = ExplicitStepperTag - def get_stages(self) -> list[OperatorType]: return [self._first_stage] @@ -201,8 +198,6 @@ class RungeKutta4(ExplicitStepperMixin): to be externally managed and allocated. """ - Tag: StepperTags = ExplicitStepperTag - def get_stages(self) -> list[OperatorType]: return [ self._first_stage, diff --git a/elastica/timestepper/protocol.py b/elastica/timestepper/protocol.py index d27279269..818d04b8b 100644 --- a/elastica/timestepper/protocol.py +++ b/elastica/timestepper/protocol.py @@ -8,7 +8,6 @@ OperatorType, SystemCollectionType, ) -from .tag import StepperTags import numpy as np @@ -16,7 +15,6 @@ class StepperProtocol(Protocol): """Protocol for all time-steppers""" - Tag: StepperTags steps_and_prefactors: SteppersOperatorsType def __init__(self) -> None: ... diff --git a/elastica/timestepper/symplectic_steppers.py b/elastica/timestepper/symplectic_steppers.py index 8e38e279a..de2d761f1 100644 --- a/elastica/timestepper/symplectic_steppers.py +++ b/elastica/timestepper/symplectic_steppers.py @@ -21,7 +21,6 @@ ) from elastica.systems.protocol import SymplecticSystemProtocol from .protocol import SymplecticStepperProtocol -from .tag import StepperTags, SymplecticStepperTag """ Developer Note @@ -163,8 +162,6 @@ class PositionVerlet(SymplecticStepperMixin): includes methods for second-order position Verlet. """ - Tag: StepperTags = SymplecticStepperTag - def get_steps(self) -> list[OperatorType]: return [ self._first_kinematic_step, @@ -210,8 +207,6 @@ class PEFRL(SymplecticStepperMixin): http://arxiv.org/abs/cond-mat/0110585 """ - Tag: StepperTags = SymplecticStepperTag - # xi and chi are confusing, but be careful! ξ: np.float64 = np.float64(0.1786178958448091e0) # ξ λ: np.float64 = -np.float64(0.2123418310626054e0) # λ diff --git a/elastica/timestepper/tag.py b/elastica/timestepper/tag.py deleted file mode 100644 index 8f0d77517..000000000 --- a/elastica/timestepper/tag.py +++ /dev/null @@ -1,8 +0,0 @@ -from typing import Literal, TypeAlias, Final - - -ExplicitStepperTag: Final = "ExplicitStepper" -SymplecticStepperTag: Final = "SymplecticStepper" -StepperTags: TypeAlias = Literal["SymplecticStepper", "ExplicitStepper"] - -allowed_stepper_tags: list[StepperTags] = [ExplicitStepperTag, SymplecticStepperTag] diff --git a/tests/test_math/test_timestepper.py b/tests/test_math/test_timestepper.py index 69c533e9c..11dbfa8d3 100644 --- a/tests/test_math/test_timestepper.py +++ b/tests/test_math/test_timestepper.py @@ -25,7 +25,6 @@ PEFRL, SymplecticStepperMixin, ) -from elastica.timestepper.tag import SymplecticStepperTag, ExplicitStepperTag from elastica.utils import Tolerance @@ -35,7 +34,6 @@ class TestExtendStepperInterface: """TODO add documentation""" class MockSymplecticStepper(SymplecticStepperMixin): - Tag = SymplecticStepperTag def get_steps(self): return [self._kinematic_step, self._dynamic_step, self._kinematic_step] @@ -53,7 +51,6 @@ def _dynamic_step(self): pass class MockExplicitStepper(ExplicitStepperMixin): - Tag = ExplicitStepperTag def get_stages(self): return [self._stage] @@ -86,7 +83,7 @@ def test_symplectic_stepper_interface_for_collective_systems(self, stepper_modul assert stepper_methods == stepper.steps_and_prefactors class MockBadStepper: - Tag = int() # an arbitrary tag that doesn't mean anything + pass @pytest.mark.parametrize("stepper_module", [MockSymplecticStepper]) def test_symplectic_stepper_throws_for_bad_stepper_for_simple_system( @@ -106,7 +103,7 @@ def test_symplectic_stepper_throws_for_bad_stepper(self, stepper_module): with pytest.raises(NotImplementedError) as excinfo: extend_stepper_interface(stepper, system) - assert "steppers are supported" in str(excinfo.value) + assert "stepper is not supported" in str(excinfo.value) def test_integrate_throws_an_assert_for_negative_final_time(): From 425e14a023a8bc393cf0f4b7c418b22a050dc212 Mon Sep 17 00:00:00 2001 From: Seung Hyun Kim Date: Fri, 3 May 2024 17:57:11 -0500 Subject: [PATCH 10/12] update tutorial with new timestepper syntax --- examples/Binder/1_Timoshenko_Beam.ipynb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/examples/Binder/1_Timoshenko_Beam.ipynb b/examples/Binder/1_Timoshenko_Beam.ipynb index a8b81fdfa..63efe00db 100644 --- a/examples/Binder/1_Timoshenko_Beam.ipynb +++ b/examples/Binder/1_Timoshenko_Beam.ipynb @@ -627,16 +627,14 @@ "\n", "\n", "def run_and_update_plot(simulator, dt, start_time, stop_time, ax):\n", - " from elastica.timestepper import extend_stepper_interface\n", " from elastica.timestepper.symplectic_steppers import PositionVerlet\n", "\n", " timestepper = PositionVerlet()\n", - " do_step, stages_and_updates = extend_stepper_interface(timestepper, simulator)\n", "\n", " n_steps = int((stop_time - start_time) / dt)\n", " time = start_time\n", " for i in range(n_steps):\n", - " time = do_step(timestepper, stages_and_updates, simulator, time, dt)\n", + " time = timestepper.step(simulator, time, dt)\n", " plot_timoshenko_dynamic(shearable_rod_new, unshearable_rod_new, end_force, time, ax)\n", " return time\n", "\n", From 52a77da035088b689fbaadeacd317cc459e26473 Mon Sep 17 00:00:00 2001 From: Seung Hyun Kim Date: Fri, 3 May 2024 23:09:32 -0500 Subject: [PATCH 11/12] feat: add testing for single system integrate overload --- elastica/timestepper/__init__.py | 28 ++++++++++++++++++++++------ tests/test_math/test_timestepper.py | 27 ++++++++++++++++----------- 2 files changed, 38 insertions(+), 17 deletions(-) diff --git a/elastica/timestepper/__init__.py b/elastica/timestepper/__init__.py index 5b6111d09..34575dfd6 100644 --- a/elastica/timestepper/__init__.py +++ b/elastica/timestepper/__init__.py @@ -10,7 +10,6 @@ from .symplectic_steppers import PositionVerlet, PEFRL from .explicit_steppers import RungeKutta4, EulerForward - from .protocol import StepperProtocol, SymplecticStepperProtocol @@ -24,11 +23,6 @@ def extend_stepper_interface( ], SteppersOperatorsType, ]: - # Check if system is a "collection" of smaller systems - assert is_system_a_collection( - system_collection - ), "Only system-collection type can be used for timestepping. Use BaseSystemCollection." - try: stepper_methods: SteppersOperatorsType = stepper.steps_and_prefactors do_step_method: Callable = stepper.do_step # type: ignore[attr-defined] @@ -37,6 +31,28 @@ def extend_stepper_interface( return do_step_method, stepper_methods +@overload +def integrate( + stepper: StepperProtocol, + systems: SystemType, + final_time: float, + n_steps: int, + restart_time: float, + progress_bar: bool, +) -> float: ... + + +@overload +def integrate( + stepper: StepperProtocol, + systems: SystemCollectionType, + final_time: float, + n_steps: int, + restart_time: float, + progress_bar: bool, +) -> float: ... + + def integrate( stepper: StepperProtocol, systems: SystemType | SystemCollectionType, diff --git a/tests/test_math/test_timestepper.py b/tests/test_math/test_timestepper.py index 11dbfa8d3..bf4b2fa2c 100644 --- a/tests/test_math/test_timestepper.py +++ b/tests/test_math/test_timestepper.py @@ -69,6 +69,22 @@ def _update(self): # So before "seeing" the system, the stepper should not have # the interface (interface_cls). It should however have the interface # after "seeing" the system, via extend_stepper_interface + @pytest.mark.parametrize( + "stepper_module", + [ + MockSymplecticStepper, + MockExplicitStepper, + ], + ) + def test_symplectic_stepper_interface_for_simple_systems(self, stepper_module): + system = ScalarExponentialDecaySystem() + stepper = stepper_module() + + stepper_methods = None + _, stepper_methods = extend_stepper_interface(stepper, system) + + assert stepper_methods + @pytest.mark.parametrize( "stepper_module", [MockSymplecticStepper, MockExplicitStepper], @@ -85,17 +101,6 @@ def test_symplectic_stepper_interface_for_collective_systems(self, stepper_modul class MockBadStepper: pass - @pytest.mark.parametrize("stepper_module", [MockSymplecticStepper]) - def test_symplectic_stepper_throws_for_bad_stepper_for_simple_system( - self, stepper_module - ): - system = ScalarExponentialDecaySystem() - stepper = stepper_module() - - with pytest.raises(AssertionError) as excinfo: - extend_stepper_interface(stepper, system) - assert "Only system-collection type can be used" in str(excinfo.value) - @pytest.mark.parametrize("stepper_module", [MockBadStepper]) def test_symplectic_stepper_throws_for_bad_stepper(self, stepper_module): system = SymplecticUndampedHarmonicOscillatorCollectiveSystem() From 42bfd091903dcc0ea26aa5af6ff2d7c6bb230599 Mon Sep 17 00:00:00 2001 From: Seung Hyun Kim Date: Sat, 4 May 2024 16:52:47 -0500 Subject: [PATCH 12/12] autoflake8 -> autoflake --- Makefile | 24 ++++---- elastica/timestepper/explicit_steppers.py | 2 +- elastica/timestepper/protocol.py | 2 +- elastica/timestepper/symplectic_steppers.py | 2 +- poetry.lock | 66 ++++++++++++--------- pyproject.toml | 11 +++- 6 files changed, 62 insertions(+), 45 deletions(-) diff --git a/Makefile b/Makefile index abb6fd70f..89b4b1799 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ #* Variables PYTHON := python3 PYTHONPATH := `pwd` -AUTOFLAKE8_ARGS := -r --exclude '__init__.py' --keep-pass-after-docstring +AUTOFLAKE_ARGS := -r #* Poetry .PHONY: poetry-download poetry-download: @@ -47,19 +47,19 @@ flake8: poetry run flake8 --version poetry run flake8 elastica tests -.PHONY: autoflake8-check -autoflake8-check: - poetry run autoflake8 --version - poetry run autoflake8 $(AUTOFLAKE8_ARGS) elastica tests examples - poetry run autoflake8 --check $(AUTOFLAKE8_ARGS) elastica tests examples +.PHONY: autoflake-check +autoflake-check: + poetry run autoflake --version + poetry run autoflake $(AUTOFLAKE_ARGS) elastica tests examples + poetry run autoflake --check $(AUTOFLAKE_ARGS) elastica tests examples -.PHONY: autoflake8-format -autoflake8-format: - poetry run autoflake8 --version - poetry run autoflake8 --in-place $(AUTOFLAKE8_ARGS) elastica tests examples +.PHONY: autoflake-format +autoflake-format: + poetry run autoflake --version + poetry run autoflake --in-place $(AUTOFLAKE_ARGS) elastica tests examples .PHONY: format-codestyle -format-codestyle: black flake8 +format-codestyle: black autoflake-format .PHONY: mypy mypy: @@ -78,7 +78,7 @@ test_coverage_xml: NUMBA_DISABLE_JIT=1 poetry run pytest --cov=elastica --cov-report=xml .PHONY: check-codestyle -check-codestyle: black-check flake8 autoflake8-check +check-codestyle: black-check flake8 autoflake-check .PHONY: formatting formatting: format-codestyle diff --git a/elastica/timestepper/explicit_steppers.py b/elastica/timestepper/explicit_steppers.py index 56140e111..aeb4cab03 100644 --- a/elastica/timestepper/explicit_steppers.py +++ b/elastica/timestepper/explicit_steppers.py @@ -1,6 +1,6 @@ __doc__ = """Explicit timesteppers and concepts""" -from typing import Type, Any, Final +from typing import Any import numpy as np from copy import copy diff --git a/elastica/timestepper/protocol.py b/elastica/timestepper/protocol.py index 818d04b8b..6eff40dd9 100644 --- a/elastica/timestepper/protocol.py +++ b/elastica/timestepper/protocol.py @@ -1,6 +1,6 @@ __doc__ = "Time stepper interface" -from typing import Protocol, Callable, Literal, ClassVar, Type +from typing import Protocol from elastica.typing import ( SystemType, diff --git a/elastica/timestepper/symplectic_steppers.py b/elastica/timestepper/symplectic_steppers.py index de2d761f1..84a87ce20 100644 --- a/elastica/timestepper/symplectic_steppers.py +++ b/elastica/timestepper/symplectic_steppers.py @@ -1,6 +1,6 @@ __doc__ = """Symplectic time steppers and concepts for integrating the kinematic and dynamic equations of rod-like objects. """ -from typing import Callable, Any, Final +from typing import Any, Final from itertools import zip_longest diff --git a/poetry.lock b/poetry.lock index 7883be3f5..282f244d9 100644 --- a/poetry.lock +++ b/poetry.lock @@ -26,18 +26,19 @@ files = [ ] [[package]] -name = "autoflake8" -version = "0.4.1" -description = "Tool to automatically fix some issues reported by flake8 (forked from autoflake)." +name = "autoflake" +version = "2.3.1" +description = "Removes unused imports and unused variables" optional = false -python-versions = ">=3.7,<4.0" +python-versions = ">=3.8" files = [ - {file = "autoflake8-0.4.1-py3-none-any.whl", hash = "sha256:fdf663b627993ac38e5b55b7d742c388fb2a4f34798a052f43eecc5e8d629e9d"}, - {file = "autoflake8-0.4.1.tar.gz", hash = "sha256:c17da499bd2b71ba02fb11fe53ff1ad83d7dae6efb0f115fd1344f467797c679"}, + {file = "autoflake-2.3.1-py3-none-any.whl", hash = "sha256:3ae7495db9084b7b32818b4140e6dc4fc280b712fb414f5b8fe57b0a8e85a840"}, + {file = "autoflake-2.3.1.tar.gz", hash = "sha256:c98b75dc5b0a86459c4f01a1d32ac7eb4338ec4317a4469515ff1e687ecd909e"}, ] [package.dependencies] -pyflakes = ">=2.3.0" +pyflakes = ">=3.0.0" +tomli = {version = ">=2.0.1", markers = "python_version < \"3.11\""} [[package]] name = "babel" @@ -496,19 +497,19 @@ typing = ["typing-extensions (>=4.8)"] [[package]] name = "flake8" -version = "3.9.2" +version = "7.0.0" description = "the modular source code checker: pep8 pyflakes and co" optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" +python-versions = ">=3.8.1" files = [ - {file = "flake8-3.9.2-py2.py3-none-any.whl", hash = "sha256:bf8fd333346d844f616e8d47905ef3a3384edae6b4e9beb0c5101e25e3110907"}, - {file = "flake8-3.9.2.tar.gz", hash = "sha256:07528381786f2a6237b061f6e96610a4167b226cb926e2aa2b6b1d78057c576b"}, + {file = "flake8-7.0.0-py2.py3-none-any.whl", hash = "sha256:a6dfbb75e03252917f2473ea9653f7cd799c3064e54d4c8140044c5c065f53c3"}, + {file = "flake8-7.0.0.tar.gz", hash = "sha256:33f96621059e65eec474169085dc92bf26e7b2d47366b70be2f67ab80dc25132"}, ] [package.dependencies] -mccabe = ">=0.6.0,<0.7.0" -pycodestyle = ">=2.7.0,<2.8.0" -pyflakes = ">=2.3.0,<2.4.0" +mccabe = ">=0.7.0,<0.8.0" +pycodestyle = ">=2.11.0,<2.12.0" +pyflakes = ">=3.2.0,<3.3.0" [[package]] name = "fonttools" @@ -928,13 +929,13 @@ python-dateutil = ">=2.7" [[package]] name = "mccabe" -version = "0.6.1" +version = "0.7.0" description = "McCabe checker, plugin for flake8" optional = false -python-versions = "*" +python-versions = ">=3.6" files = [ - {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, - {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, + {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, + {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, ] [[package]] @@ -1351,13 +1352,13 @@ files = [ [[package]] name = "pycodestyle" -version = "2.7.0" +version = "2.11.1" description = "Python style guide checker" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=3.8" files = [ - {file = "pycodestyle-2.7.0-py2.py3-none-any.whl", hash = "sha256:514f76d918fcc0b55c6680472f0a37970994e07bbb80725808c17089be302068"}, - {file = "pycodestyle-2.7.0.tar.gz", hash = "sha256:c389c1d06bf7904078ca03399a4816f974a1d590090fecea0c63ec26ebaf1cef"}, + {file = "pycodestyle-2.11.1-py2.py3-none-any.whl", hash = "sha256:44fe31000b2d866f2e41841b18528a505fbd7fef9017b04eff4e2648a0fadc67"}, + {file = "pycodestyle-2.11.1.tar.gz", hash = "sha256:41ba0e7afc9752dfb53ced5489e89f8186be00e599e712660695b7a75ff2663f"}, ] [[package]] @@ -1389,13 +1390,13 @@ test = ["pytest", "pytest-cov", "pytest-regressions"] [[package]] name = "pyflakes" -version = "2.3.1" +version = "3.2.0" description = "passive checker of Python programs" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=3.8" files = [ - {file = "pyflakes-2.3.1-py2.py3-none-any.whl", hash = "sha256:7893783d01b8a89811dd72d7dfd4d84ff098e5eed95cfa8905b22bbffe52efc3"}, - {file = "pyflakes-2.3.1.tar.gz", hash = "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db"}, + {file = "pyflakes-3.2.0-py2.py3-none-any.whl", hash = "sha256:84b5be138a2dfbb40689ca07e2152deb896a65c3a3e24c251c5c62489568074a"}, + {file = "pyflakes-3.2.0.tar.gz", hash = "sha256:1c61603ff154621fb2a9172037d84dca3500def8c8b630657d1701f026f8af3f"}, ] [[package]] @@ -1552,6 +1553,7 @@ files = [ {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, + {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, @@ -1559,8 +1561,16 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, + {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, + {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, + {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, + {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, @@ -1577,6 +1587,7 @@ files = [ {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, + {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, @@ -1584,6 +1595,7 @@ files = [ {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, + {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, @@ -2034,4 +2046,4 @@ examples = ["cma"] [metadata] lock-version = "2.0" python-versions = ">=3.10,<3.12" -content-hash = "d772fc54f4a1b5816fcb4c6fbda968ac724b5884433f8cf604abf723830176fe" +content-hash = "07105447f85d22c61c0766e22959a4f3428f9cfa20e7499aae4017fa993fdca3" diff --git a/pyproject.toml b/pyproject.toml index d8988a3c5..09a13f1ec 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -54,6 +54,7 @@ docutils = {version = "^0.18", optional = true} cma = {version = "^3.2.2", optional = true} mypy = "^1.10.0" mypy-extensions = "^1.0.0" +flake8 = "^7.0.0" [tool.poetry.dev-dependencies] black = "24.3.0" @@ -62,10 +63,9 @@ coverage = "^6.3.3" pre-commit = "^2.19.0" pytest-html = "^3.1.1" pytest-cov = "^3.0.0" -flake8 = "^3.8" codecov = "2.1.13" click = "8.0.0" -autoflake8 = "^0.4" +autoflake = "^2.3.1" [tool.poetry.extras] docs = [ @@ -102,10 +102,15 @@ exclude = ''' )/ ''' +[tool.autoflake] +ignore-init-module-imports = true +ignore-pass-statements = true +ignore-pass-after-docstring = true + [tool.pytest.ini_options] # https://docs.pytest.org/en/6.2.x/customize.html#pyproject-toml # Directories that are not visited by pytest collector: -norecursedirs =["hooks", "*.egg", ".eggs", "dist", "build", "docs", ".tox", ".git", "__pycache__"] +norecursedirs = ["hooks", "*.egg", ".eggs", "dist", "build", "docs", ".tox", ".git", "__pycache__"] [tool.mypy] # https://mypy.readthedocs.io/en/latest/config_file.html#using-a-pyproject-toml-file