__doc__ = """
Forcing
-------
Provides the forcing interface to apply forces and torques to rod-like objects
(external point force, muscle torques, etc).
"""
from elastica.interaction import AnisotropicFrictionalPlane
[docs]class Forcing:
"""
The Forcing class is a wrapper for applying boundary conditions that
consist of applied external forces. To apply forcing on rod-like objects,
the simulator class must be derived from the Forcing class.
Attributes
----------
_ext_forces_torques: list
List of forcing class defined for rod-like objects.
"""
def __init__(self):
self._ext_forces_torques = []
super(Forcing, self).__init__()
self._feature_group_synchronize.append(self._call_ext_forces_torques)
self._feature_group_finalize.append(self._finalize_forcing)
[docs] def add_forcing_to(self, system):
"""
This method applies external forces and torques on the relevant
user-defined system or rod-like object. You must input the system
or rod-like object that you want to apply external forces and torques on.
Parameters
----------
system: object
System is a rod-like object.
Returns
-------
"""
sys_idx = self._get_sys_idx_if_valid(system)
# Create _Constraint object, cache it and return to user
_ext_force_torque = _ExtForceTorque(sys_idx)
self._ext_forces_torques.append(_ext_force_torque)
return _ext_force_torque
def _finalize_forcing(self):
# From stored _ExtForceTorque objects, and instantiate a Force
# inplace : https://stackoverflow.com/a/1208792
# dev : the first index stores the rod index to apply the boundary condition
# to. Technically we can use another array but it its one more book-keeping
# step. Being lazy, I put them both in the same array
self._ext_forces_torques[:] = [
(ext_force_torque.id(), ext_force_torque())
for ext_force_torque in self._ext_forces_torques
]
# Sort from lowest id to highest id for potentially better memory access
# _ext_forces_torques contains list of tuples. First element of tuple is
# rod number and following elements are the type of boundary condition such as
# [(0, NoForces, GravityForces), (1, UniformTorques), ... ]
# Thus using lambda we iterate over the list of tuples and use rod number (x[0])
# to sort _ext_forces_torques.
self._ext_forces_torques.sort(key=lambda x: x[0])
# Find if there are any friction plane forcing, if add them to the end of the list,
# since friction planes uses external forces.
friction_plane_index = []
for idx, ext_force_torque in enumerate(self._ext_forces_torques):
if isinstance(ext_force_torque[1], AnisotropicFrictionalPlane):
friction_plane_index.append(idx)
# Move to the friction forces to the end of the external force and torques list.
for index in friction_plane_index:
self._ext_forces_torques.append(self._ext_forces_torques.pop(index))
def _call_ext_forces_torques(self, time, *args, **kwargs):
for sys_id, ext_force_torque in self._ext_forces_torques:
ext_force_torque.apply_forces(self._systems[sys_id], time, *args, **kwargs)
ext_force_torque.apply_torques(self._systems[sys_id], time, *args, **kwargs)
# TODO Apply torque, see if necessary
class _ExtForceTorque:
"""
Forcing wrapper private class
Attributes
----------
_sys_idx: int
_forcing_cls: list
*args
Variable length argument list.
**kwargs
Arbitrary keyword arguments.
"""
def __init__(self, sys_idx: int):
"""
Parameters
----------
sys_idx: int
"""
self._sys_idx = sys_idx
self._forcing_cls = None
self._args = ()
self._kwargs = {}
def using(self, forcing_cls, *args, **kwargs):
"""
This method is a wrapper to set which forcing class is used to apply forcing
to user defined rod-like objects.
Parameters
----------
forcing_cls: object
User defined forcing class.
*args
Variable length argument list
**kwargs
Arbitrary keyword arguments.
Returns
-------
"""
from elastica.external_forces import NoForces
assert issubclass(
forcing_cls, NoForces
), "{} is not a valid forcing. Did you forget to derive from NoForces?".format(
forcing_cls
)
self._forcing_cls = forcing_cls
self._args = args
self._kwargs = kwargs
return self
def id(self):
return self._sys_idx
def __call__(self, *args, **kwargs):
"""Constructs a constraint after checks
Parameters
----------
*args
Variable length argument list.
**kwargs
Arbitrary keyword arguments.
Returns
-------
"""
if not self._forcing_cls:
raise RuntimeError(
"No forcing provided to act on rod id {0}"
"but a force was registered. Did you forget to call"
"the `using` method".format(self.id())
)
try:
return self._forcing_cls(*self._args, **self._kwargs)
except (TypeError, IndexError):
raise TypeError(
r"Unable to construct forcing class.\n"
r"Did you provide all necessary force properties?"
)