Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New contact module, Surface base class and tests #281

Merged
merged 33 commits into from
Aug 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
c2f4c27
build(deps): bump requests from 2.30.0 to 2.31.0
dependabot[bot] May 23, 2023
d149646
testing default indices was incorrect, the connect.id() should be sli…
Ali-7800 Jul 18, 2023
fc4b2a3
contact module, surface base class, and tests
Ali-7800 Jul 18, 2023
b1e9c30
fixed repeated imports
Ali-7800 Jul 18, 2023
e9163e7
removed _generate_contact_function(), each class will have one apply_…
Ali-7800 Jul 18, 2023
93441ed
Merge pull request #259 from GazzolaLab/dependabot/pip/requests-2.31.0
armantekinalp Jul 20, 2023
4addb32
Merge branch 'update-0.3.2' into 113_dev_contact
Ali-7800 Jul 21, 2023
fd11d83
Merge branch 'update-0.3.2' into 113_dev_contact
bhosale2 Jul 24, 2023
e524c52
added contact order check and tests, and test_connections call on sys…
Ali-7800 Jul 28, 2023
34b51e2
sync with update-0.3.2
Ali-7800 Jul 28, 2023
3902be2
Merge branch 'GazzolaLab:master' into 113_dev_contact
Ali-7800 Jul 28, 2023
540bc90
removed pass from mock_init
Ali-7800 Jul 28, 2023
d967819
Merge branch '113_dev_contact' of github.com:Ali-7800/PyElastica into…
Ali-7800 Jul 28, 2023
e83a918
Merge branch 'update-0.3.2' into 113_dev_contact
bhosale2 Jul 31, 2023
2fd9ea2
doc changes, error message changes, class name changes, and removed _…
Ali-7800 Jul 31, 2023
7644639
Merge branch '113_dev_contact' of github.com:Ali-7800/PyElastica into…
Ali-7800 Jul 31, 2023
bbe3514
removed _len_ from MockRigidBody
Ali-7800 Jul 31, 2023
4a2aee5
changed link to establish contact between in doc
Ali-7800 Jul 31, 2023
1cd5e57
Update elastica/contact_forces.py
Ali-7800 Jul 31, 2023
1e9f495
changed add_contact_to to detect_contact_between, added typ hints to …
Ali-7800 Aug 2, 2023
bb97e87
Merge branch '113_dev_contact' of github.com:Ali-7800/PyElastica into…
Ali-7800 Aug 2, 2023
b697dd3
Update tests/test_modules/test_connections.py
Ali-7800 Aug 3, 2023
ba41e14
refactor _order_check to _check_order
Ali-7800 Aug 5, 2023
11d0130
added SurfaceBase to allowed_sys_types
Ali-7800 Aug 5, 2023
0dd7462
changed error message tests to test whole error message instead of pa…
Ali-7800 Aug 5, 2023
e5156d9
changed scwc and scwcai to system_collection_with_connections and sys…
Ali-7800 Aug 5, 2023
c108ced
removed unused imports
Ali-7800 Aug 5, 2023
d1625b4
Update elastica/contact_forces.py
Ali-7800 Aug 6, 2023
40339fc
removed empty returns in doc
Ali-7800 Aug 6, 2023
25e4fbf
Update elastica/contact_forces.py
Ali-7800 Aug 7, 2023
cadbae9
Update elastica/modules/contact.py
Ali-7800 Aug 7, 2023
360a61c
Update elastica/modules/contact.py
Ali-7800 Aug 7, 2023
44942a7
added type hints, remove unused time argument, updated docstring
Ali-7800 Aug 7, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion elastica/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@
HingeJoint,
SelfContact,
)
from elastica.contact_forces import (
NoContact,
)
from elastica.callback_functions import CallBackBaseClass, ExportCallBack, MyCallBack
from elastica.dissipation import (
DamperBase,
Expand All @@ -54,6 +57,8 @@
from elastica.modules.constraints import Constraints
from elastica.modules.forcing import Forcing
from elastica.modules.damping import Damping
from elastica.modules.contact import Contact

from elastica.transformations import inv_skew_symmetrize
from elastica.transformations import rotate
from elastica._calculus import (
Expand All @@ -66,7 +71,7 @@
)
from elastica._linalg import levi_civita_tensor
from elastica.utils import isqrt
from elastica.typing import RodType, SystemType
from elastica.typing import RodType, SystemType, AllowedContactType
from elastica.timestepper import (
integrate,
PositionVerlet,
Expand Down
68 changes: 68 additions & 0 deletions elastica/contact_forces.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
__doc__ = """ Numba implementation module containing contact between rods and rigid bodies and other rods rigid bodies or surfaces."""

from elastica.typing import SystemType, AllowedContactType
from elastica.rod import RodBase


class NoContact:
"""
This is the base class for contact applied between rod-like objects and allowed contact objects.

Notes
-----
Every new contact class must be derived
from NoContact class.

"""

def __init__(self):
"""
NoContact class does not need any input parameters.
"""

def _check_order(
self,
system_one: SystemType,
system_two: AllowedContactType,
) -> None:
"""
This checks the contact order between a SystemType object and an AllowedContactType object, the order should follow: Rod, Rigid body, Surface.
In NoContact class, this just checks if system_two is a rod then system_one must be a rod.


Parameters
----------
system_one
SystemType
system_two
AllowedContactType
"""
if issubclass(system_two.__class__, RodBase):
if not issubclass(system_one.__class__, RodBase):
raise TypeError(
"Systems provided to the contact class have incorrect order. \n"
" First system is {0} and second system is {1} . \n"
" If the first system is a rod, the second system can be a rod, rigid body or surface. \n"
" If the first system is a rigid body, the second system can be a rigid body or surface.".format(
system_one.__class__, system_two.__class__
)
)

def apply_contact(
self,
system_one: SystemType,
system_two: AllowedContactType,
) -> None:
"""
Apply contact forces and torques between SystemType object and AllowedContactType object.

In NoContact class, this routine simply passes.

Parameters
----------
system_one : SystemType
Rod or rigid-body object
system_two : AllowedContactType
Rod, rigid-body, or surface object
"""
pass
1 change: 1 addition & 0 deletions elastica/modules/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@
from .forcing import Forcing
from .callbacks import CallBacks
from .damping import Damping
from .contact import Contact
3 changes: 2 additions & 1 deletion elastica/modules/base_system.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

from elastica.rod import RodBase
from elastica.rigidbody import RigidBodyBase
from elastica.surface import SurfaceBase
from elastica.modules.memory_block import construct_memory_block_structures
from elastica._synchronize_periodic_boundary import _ConstrainPeriodicBoundaries

Expand Down Expand Up @@ -54,7 +55,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)
self.allowed_sys_types = (RodBase, RigidBodyBase, SurfaceBase)
# List of systems to be integrated
self._systems = []
# Flag Finalize: Finalizing twice will cause an error,
Expand Down
175 changes: 175 additions & 0 deletions elastica/modules/contact.py
Ali-7800 marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
__doc__ = """
Contact
-------

Provides the contact interface to apply contact forces between objects
(rods, rigid bodies, surfaces).
"""

from elastica.typing import SystemType, AllowedContactType


class Contact:
"""
The Contact class is a module for applying contact between rod-like objects . To apply contact between rod-like objects,
the simulator class must be derived from the Contact class.

Attributes
----------
_contacts: list
List of contact classes defined for rod-like objects.
"""

def __init__(self):
self._contacts = []
super(Contact, self).__init__()
self._feature_group_synchronize.append(self._call_contacts)
self._feature_group_finalize.append(self._finalize_contact)

def detect_contact_between(
self, first_system: SystemType, second_system: AllowedContactType
):
"""
This method adds contact detection between two objects using the selected contact class.
You need to input the two objects that are to be connected.

Parameters
----------
first_system : SystemType
Rod or rigid body object
second_system : AllowedContactType
Rod, rigid body or surface object

Returns
-------

"""
sys_idx = [None] * 2
for i_sys, sys in enumerate((first_system, second_system)):
sys_idx[i_sys] = self._get_sys_idx_if_valid(sys)

# Create _Contact object, cache it and return to user
_contact = _Contact(*sys_idx)
self._contacts.append(_contact)

return _contact

def _finalize_contact(self) -> None:

# dev : the first indices stores the
# (first_rod_idx, second_rod_idx)
# to apply the contacts 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._contacts[:] = [(*contact.id(), contact()) for contact in self._contacts]

# check contact order
for (
first_sys_idx,
second_sys_idx,
contact,
) in self._contacts:
contact._check_order(
self._systems[first_sys_idx],
self._systems[second_sys_idx],
)

def _call_contacts(self, *args, **kwargs):
for (
first_sys_idx,
second_sys_idx,
contact,
) in self._contacts:
contact.apply_contact(
self._systems[first_sys_idx],
self._systems[second_sys_idx],
*args,
**kwargs,
)


class _Contact:
"""
Contact module private class

Attributes
----------
_first_sys_idx: int
_second_sys_idx: int
_contact_cls: list
*args
Variable length argument list.
**kwargs
Arbitrary keyword arguments.
"""

def __init__(
self,
first_sys_idx: int,
second_sys_idx: int,
) -> None:
"""

Parameters
----------
first_sys_idx
second_sys_idx
"""
self.first_sys_idx = first_sys_idx
self.second_sys_idx = second_sys_idx
self._contact_cls = None
self._args = ()
self._kwargs = {}

def using(self, contact_cls: object, *args, **kwargs):
"""
This method is a module to set which contact class is used to apply contact
between user defined rod-like objects.

Parameters
----------
contact_cls: object
User defined contact class.
*args
Variable length argument list
**kwargs
Arbitrary keyword arguments.

Returns
-------

"""
from elastica.contact_forces import NoContact

assert issubclass(
contact_cls, NoContact
), "{} is not a valid contact class. Did you forget to derive from NoContact?".format(
contact_cls
)
self._contact_cls = contact_cls
self._args = args
self._kwargs = kwargs
return self

def id(self):
return (
self.first_sys_idx,
self.second_sys_idx,
)

def __call__(self, *args, **kwargs):
if not self._contact_cls:
raise RuntimeError(
"No contacts provided to to establish contact between rod-like object id {0}"
" and {1}, but a Contact"
"was intended as per code. Did you forget to"
"call the `using` method?".format(*self.id())
)

try:
return self._contact_cls(*self._args, **self._kwargs)
except (TypeError, IndexError):
raise TypeError(
r"Unable to construct contact class.\n"
r"Did you provide all necessary contact properties?"
)
2 changes: 2 additions & 0 deletions elastica/surface/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
__doc__ = """Surface classes"""
from elastica.surface.surface_base import SurfaceBase
18 changes: 18 additions & 0 deletions elastica/surface/surface_base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
__doc__ = """Base class for surfaces"""


class SurfaceBase:
"""
Base class for all surfaces.

Notes
-----
All new surface classes must be derived from this SurfaceBase class.

"""

def __init__(self):
"""
SurfaceBase does not take any arguments.
"""
pass
2 changes: 2 additions & 0 deletions elastica/typing.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
from elastica.rod import RodBase
from elastica.rigidbody import RigidBodyBase
from elastica.surface import SurfaceBase

from typing import Type, Union

RodType = Type[RodBase]
SystemType = Union[RodType, Type[RigidBodyBase]]
AllowedContactType = Union[SystemType, Type[SurfaceBase]]
6 changes: 3 additions & 3 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 10 additions & 2 deletions tests/test_modules/test_base_system.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,16 @@ def test_extend_allowed_types(self, load_collection):

from elastica.rod import RodBase
from elastica.rigidbody import RigidBodyBase

assert bsc.allowed_sys_types == (RodBase, RigidBodyBase, int, float, str)
from elastica.surface import SurfaceBase

assert bsc.allowed_sys_types == (
RodBase,
RigidBodyBase,
SurfaceBase,
int,
float,
str,
)

def test_extend_correctness(self, load_collection):
"""
Expand Down
Loading
Loading