|
3 | 3 | from elastica.typing import RodType, SystemType, AllowedContactType
|
4 | 4 | from elastica.rod import RodBase
|
5 | 5 | from elastica.rigidbody import Cylinder
|
| 6 | +from elastica.surface import MeshSurface |
6 | 7 | from elastica.contact_utils import (
|
7 | 8 | _dot_product,
|
8 | 9 | _norm,
|
9 | 10 | _find_min_dist,
|
10 | 11 | _prune_using_aabbs_rod_cylinder,
|
11 | 12 | _prune_using_aabbs_rod_rod,
|
| 13 | + find_contact_faces_idx, |
12 | 14 | )
|
13 |
| - |
14 |
| -from elastica._linalg import _batch_product_k_ik_to_ik |
| 15 | +from elastica.interaction import node_to_element_velocity, elements_to_nodes_inplace |
| 16 | +from elastica._linalg import _batch_product_k_ik_to_ik, _batch_dot, _batch_norm |
15 | 17 | import numba
|
16 | 18 | import numpy as np
|
17 | 19 |
|
@@ -423,6 +425,122 @@ def _calculate_contact_forces_self_rod(
|
423 | 425 | external_forces_rod[..., j + 1] += net_contact_force
|
424 | 426 |
|
425 | 427 |
|
| 428 | +@numba.njit(cache=True) |
| 429 | +def _calculate_contact_forces_rod_mesh_surface( |
| 430 | + faces_normals: np.ndarray, |
| 431 | + faces_centers: np.ndarray, |
| 432 | + element_position: np.ndarray, |
| 433 | + position_idx_array: np.ndarray, |
| 434 | + face_idx_array, |
| 435 | + surface_tol: float, |
| 436 | + k: float, |
| 437 | + nu: float, |
| 438 | + radius: np.array, |
| 439 | + mass: np.array, |
| 440 | + velocity_collection: np.ndarray, |
| 441 | + external_forces: np.ndarray, |
| 442 | +) -> tuple: |
| 443 | + """ |
| 444 | + This function computes the plane force response on the element, in the |
| 445 | + case of contact. Contact model given in Eqn 4.8 Gazzola et. al. RSoS 2018 paper |
| 446 | + is used. |
| 447 | +
|
| 448 | + Parameters |
| 449 | + ---------- |
| 450 | + faces_normals: np.ndarray |
| 451 | + mesh cell's normal vectors |
| 452 | + faces_centers: np.ndarray |
| 453 | + mesh cell's center points |
| 454 | + element_position: np.ndarray |
| 455 | + rod element's center points |
| 456 | + position_idx_array: np.ndarray |
| 457 | + rod element's index array |
| 458 | + face_idx_array: np.ndarray |
| 459 | + mesh cell's index array |
| 460 | + surface_tol: float |
| 461 | + Penetration tolerance between the surface and the rod-like object |
| 462 | + k: float |
| 463 | + Contact spring constant |
| 464 | + nu: float |
| 465 | + Contact damping constant |
| 466 | + radius: np.array |
| 467 | + rod element's radius |
| 468 | + mass: np.array |
| 469 | + rod element's mass |
| 470 | + velocity_collection: np.ndarray |
| 471 | + rod element's velocity |
| 472 | + external_forces: np.ndarray |
| 473 | + rod element's external forces |
| 474 | +
|
| 475 | + Returns |
| 476 | + ------- |
| 477 | + magnitude of the plane response |
| 478 | + """ |
| 479 | + |
| 480 | + # Damping force response due to velocity towards the plane |
| 481 | + element_velocity = node_to_element_velocity( |
| 482 | + mass=mass, node_velocity_collection=velocity_collection |
| 483 | + ) |
| 484 | + |
| 485 | + if len(face_idx_array) > 0: |
| 486 | + element_position_contacts = element_position[:, position_idx_array] |
| 487 | + contact_face_centers = faces_centers[:, face_idx_array] |
| 488 | + normals_on_elements = faces_normals[:, face_idx_array] |
| 489 | + radius_contacts = radius[position_idx_array] |
| 490 | + element_velocity_contacts = element_velocity[:, position_idx_array] |
| 491 | + |
| 492 | + else: |
| 493 | + element_position_contacts = element_position |
| 494 | + contact_face_centers = np.zeros_like(element_position) |
| 495 | + normals_on_elements = np.zeros_like(element_position) |
| 496 | + radius_contacts = radius |
| 497 | + element_velocity_contacts = element_velocity |
| 498 | + |
| 499 | + # Elastic force response due to penetration |
| 500 | + |
| 501 | + distance_from_plane = _batch_dot( |
| 502 | + normals_on_elements, (element_position_contacts - contact_face_centers) |
| 503 | + ) |
| 504 | + plane_penetration = ( |
| 505 | + -np.abs(np.minimum(distance_from_plane - radius_contacts, 0.0)) ** 1.5 |
| 506 | + ) |
| 507 | + elastic_force = -k * _batch_product_k_ik_to_ik( |
| 508 | + plane_penetration, normals_on_elements |
| 509 | + ) |
| 510 | + |
| 511 | + normal_component_of_element_velocity = _batch_dot( |
| 512 | + normals_on_elements, element_velocity_contacts |
| 513 | + ) |
| 514 | + damping_force = -nu * _batch_product_k_ik_to_ik( |
| 515 | + normal_component_of_element_velocity, normals_on_elements |
| 516 | + ) |
| 517 | + |
| 518 | + # Compute total plane response force |
| 519 | + plane_response_force_contacts = elastic_force + damping_force |
| 520 | + |
| 521 | + # Check if the rod elements are in contact with plane. |
| 522 | + no_contact_point_idx = np.where( |
| 523 | + (distance_from_plane - radius_contacts) > surface_tol |
| 524 | + )[0] |
| 525 | + # If rod element does not have any contact with plane, plane cannot apply response |
| 526 | + # force on the element. Thus lets set plane response force to 0.0 for the no contact points. |
| 527 | + plane_response_force_contacts[..., no_contact_point_idx] = 0.0 |
| 528 | + |
| 529 | + plane_response_forces = np.zeros_like(external_forces) |
| 530 | + for i in range(len(position_idx_array)): |
| 531 | + plane_response_forces[ |
| 532 | + :, position_idx_array[i] |
| 533 | + ] += plane_response_force_contacts[:, i] |
| 534 | + |
| 535 | + # Update the external forces |
| 536 | + elements_to_nodes_inplace(plane_response_forces, external_forces) |
| 537 | + return ( |
| 538 | + _batch_norm(plane_response_force_contacts), |
| 539 | + no_contact_point_idx, |
| 540 | + normals_on_elements, |
| 541 | + ) |
| 542 | + |
| 543 | + |
426 | 544 | class RodRodContact(NoContact):
|
427 | 545 | """
|
428 | 546 | This class is for applying contact forces between rod-rod.
|
@@ -725,3 +843,148 @@ def apply_contact(
|
725 | 843 | self.k,
|
726 | 844 | self.nu,
|
727 | 845 | )
|
| 846 | + |
| 847 | + |
| 848 | +class RodMeshSurfaceContactWithGridMethod(NoContact): |
| 849 | + """ |
| 850 | + This class is for applying contact forces between rod-mesh_surface. |
| 851 | + First system is always rod and second system is always mesh_surface. |
| 852 | +
|
| 853 | + Examples |
| 854 | + -------- |
| 855 | + How to define contact between rod and mesh_surface. |
| 856 | +
|
| 857 | + >>> simulator.detect_contact_between(rod, mesh_surface).using( |
| 858 | + ... RodMeshSurfaceContactWithGridMethod, |
| 859 | + ... k=1e4, |
| 860 | + ... nu=10, |
| 861 | + ... surface_tol=1e-2, |
| 862 | + ... ) |
| 863 | + """ |
| 864 | + |
| 865 | + def __init__( |
| 866 | + self, k: float, nu: float, faces_grid: dict, grid_size: float, surface_tol=1e-4 |
| 867 | + ): |
| 868 | + """ |
| 869 | +
|
| 870 | + Parameters |
| 871 | + ---------- |
| 872 | + k: float |
| 873 | + Stiffness coefficient between the plane and the rod-like object. |
| 874 | + nu: float |
| 875 | + Dissipation coefficient between the plane and the rod-like object. |
| 876 | + faces_grid: dict |
| 877 | + Dictionary containing the grid information of the mesh surface. |
| 878 | + grid_size: float |
| 879 | + Grid size of the mesh surface. |
| 880 | + surface_tol: float |
| 881 | + Penetration tolerance between the surface and the rod-like object. |
| 882 | +
|
| 883 | + """ |
| 884 | + super(RodMeshSurfaceContactWithGridMethod, self).__init__() |
| 885 | + # n_faces = faces.shape[-1] |
| 886 | + self.k = k |
| 887 | + self.nu = nu |
| 888 | + self.faces_grid = faces_grid |
| 889 | + self.grid_size = grid_size |
| 890 | + self.surface_tol = surface_tol |
| 891 | + |
| 892 | + def _check_systems_validity( |
| 893 | + self, |
| 894 | + system_one: SystemType, |
| 895 | + system_two: AllowedContactType, |
| 896 | + faces_grid: dict, |
| 897 | + grid_size: float, |
| 898 | + ) -> None: |
| 899 | + """ |
| 900 | + This checks the contact order and type of a SystemType object and an AllowedContactType object. |
| 901 | + For the RodMeshSurfaceContact class first_system should be a rod and second_system should be a mesh_surface; |
| 902 | + morever, the imported grid's attributes should match imported rod-mesh_surface(in contact) grid's attributes. |
| 903 | +
|
| 904 | + Parameters |
| 905 | + ---------- |
| 906 | + system_one |
| 907 | + SystemType |
| 908 | + system_two |
| 909 | + AllowedContactType |
| 910 | + """ |
| 911 | + if not issubclass(system_one.__class__, RodBase) or not issubclass( |
| 912 | + system_two.__class__, MeshSurface |
| 913 | + ): |
| 914 | + raise TypeError( |
| 915 | + "Systems provided to the contact class have incorrect order/type. \n" |
| 916 | + " First system is {0} and second system is {1}. \n" |
| 917 | + " First system should be a rod, second should be a mesh surface".format( |
| 918 | + system_one.__class__, system_two.__class__ |
| 919 | + ) |
| 920 | + ) |
| 921 | + |
| 922 | + elif not faces_grid["grid_size"] == grid_size: |
| 923 | + raise TypeError( |
| 924 | + "Imported grid size does not match with the current rod-mesh_surface grid size. " |
| 925 | + ) |
| 926 | + |
| 927 | + elif not faces_grid["model_path"] == system_two.model_path: |
| 928 | + raise TypeError( |
| 929 | + "Imported grid's model path does not match with the current mesh_surface model path. " |
| 930 | + ) |
| 931 | + |
| 932 | + elif not faces_grid["surface_reorient"] == system_two.mesh_orientation: |
| 933 | + raise TypeError( |
| 934 | + "Imported grid's surface orientation does not match with the current mesh_surface rientation. " |
| 935 | + ) |
| 936 | + |
| 937 | + def apply_contact( |
| 938 | + self, system_one: RodType, system_two: AllowedContactType |
| 939 | + ) -> tuple: |
| 940 | + """ |
| 941 | + In the case of contact with the plane, this function computes the plane reaction force on the element. |
| 942 | +
|
| 943 | + Parameters |
| 944 | + ---------- |
| 945 | + system_one: object |
| 946 | + Rod-like object. |
| 947 | + system_two: Surface |
| 948 | + Mesh surface. |
| 949 | +
|
| 950 | + Returns |
| 951 | + ------- |
| 952 | + plane_response_force_mag : numpy.ndarray |
| 953 | + 1D (blocksize) array containing data with 'float' type. |
| 954 | + Magnitude of plane response force acting on rod-like object. |
| 955 | + no_contact_point_idx : numpy.ndarray |
| 956 | + 1D (blocksize) array containing data with 'int' type. |
| 957 | + Index of rod-like object elements that are not in contact with the plane. |
| 958 | + """ |
| 959 | + |
| 960 | + self.mesh_surface_faces = system_two.faces |
| 961 | + self.mesh_surface_x_min = np.min(self.mesh_surface_faces[0, :, :]) |
| 962 | + self.mesh_surface_y_min = np.min(self.mesh_surface_faces[1, :, :]) |
| 963 | + self.mesh_surface_face_normals = system_two.face_normals |
| 964 | + self.mesh_surface_face_centers = system_two.face_centers |
| 965 | + ( |
| 966 | + self.position_idx_array, |
| 967 | + self.face_idx_array, |
| 968 | + self.element_position, |
| 969 | + ) = find_contact_faces_idx( |
| 970 | + self.faces_grid, |
| 971 | + self.mesh_surface_x_min, |
| 972 | + self.mesh_surface_y_min, |
| 973 | + self.grid_size, |
| 974 | + system_one.position_collection, |
| 975 | + ) |
| 976 | + |
| 977 | + return _calculate_contact_forces_rod_mesh_surface( |
| 978 | + self.mesh_surface_face_normals, |
| 979 | + self.mesh_surface_face_centers, |
| 980 | + self.element_position, |
| 981 | + self.position_idx_array, |
| 982 | + self.face_idx_array, |
| 983 | + self.surface_tol, |
| 984 | + self.k, |
| 985 | + self.nu, |
| 986 | + system_one.radius, |
| 987 | + system_one.mass, |
| 988 | + system_one.velocity_collection, |
| 989 | + system_one.external_forces, |
| 990 | + ) |
0 commit comments