Exemple #1
0
    def compute_shear_energy(self):

        sigma_diff = self.sigma - self.rest_sigma
        shear_internal_torques = _batch_matvec(self.shear_matrix, sigma_diff)

        return (0.5 * (_batch_dot(sigma_diff, shear_internal_torques) *
                       self.rest_lengths).sum())
Exemple #2
0
    def compute_bending_energy(self):

        kappa_diff = self.kappa - self.rest_kappa
        bending_internal_torques = _batch_matvec(self.bend_matrix, kappa_diff)

        return (0.5 * (_batch_dot(kappa_diff, bending_internal_torques) *
                       self.rest_voronoi_lengths).sum())
Exemple #3
0
def test_batch_dot(blocksize):
    input_first_vector_collection = np.random.randn(3, blocksize)
    input_second_vector_collection = np.random.randn(3, blocksize)

    test_vector_collection = _batch_dot(input_first_vector_collection,
                                        input_second_vector_collection)

    correct_vector_collection = np.einsum("ij,ij->j",
                                          input_first_vector_collection,
                                          input_second_vector_collection)

    assert_allclose(test_vector_collection, correct_vector_collection)
Exemple #4
0
def _compute_dilatation_rate(position_collection, velocity_collection, lengths,
                             rest_lengths, dilatation_rate):
    """

    Returns
    -------

    """
    # TODO Use the vector formula rather than separating it out
    # self.lengths = l_i = |r^{i+1} - r^{i}|
    r_dot_v = _batch_dot(position_collection, velocity_collection)
    r_plus_one_dot_v = _batch_dot(position_collection[..., 1:],
                                  velocity_collection[..., :-1])
    r_dot_v_plus_one = _batch_dot(position_collection[..., :-1],
                                  velocity_collection[..., 1:])

    blocksize = lengths.shape[0]

    for k in range(blocksize):
        dilatation_rate[k] = ((r_dot_v[k] + r_dot_v[k + 1] -
                               r_dot_v_plus_one[k] - r_plus_one_dot_v[k]) /
                              lengths[k] / rest_lengths[k])
def allocate(n_elements,
             start,
             direction,
             normal,
             base_length,
             base_radius,
             density,
             nu,
             youngs_modulus,
             poisson_ratio,
             alpha_c=4.0 / 3.0,
             *args,
             **kwargs):

    # sanity checks here
    assert n_elements > 1
    assert base_length > Tolerance.atol()
    assert np.sqrt(np.dot(normal, normal)) > Tolerance.atol()
    assert np.sqrt(np.dot(direction, direction)) > Tolerance.atol()

    # Set the position array
    position = np.zeros((MaxDimension.value(), n_elements + 1))
    # check if position is in kwargs, if it is use user defined position otherwise generate position
    if kwargs.__contains__("position"):
        position_temp = np.array(kwargs["position"])

        # Check the shape of the input position
        assert position_temp.shape == (MaxDimension.value(), n_elements + 1), (
            "Given position  shape is not correct, it should be " +
            str(position.shape) + " but instead " + str(position_temp.shape))
        # Check if the start position of the rod and first entry of position array are the same
        assert_allclose(
            position_temp[..., 0],
            start,
            atol=Tolerance.atol(),
            err_msg=str("First entry of position" + " (" +
                        str(position_temp[..., 0]) + " ) "
                        " is different than start " + " (" + str(start) +
                        " ) "),
        )
        position = position_temp.copy()

    else:
        end = start + direction * base_length
        for i in range(0, 3):
            position[i, ...] = np.linspace(start[i], end[i], n_elements + 1)

    # Compute rest lengths and tangents
    position_diff = position[..., 1:] - position[..., :-1]
    rest_lengths = _batch_norm(position_diff)
    tangents = position_diff / rest_lengths
    normal /= np.linalg.norm(normal)

    # Set the directors matrix
    directors = np.zeros(
        (MaxDimension.value(), MaxDimension.value(), n_elements))
    # check if directors is in kwargs, if it use user defined directors otherwise generate directors
    if kwargs.__contains__("directors"):
        directors_temp = np.array(kwargs["directors"])

        # Check the shape of input directors
        assert directors_temp.shape == (
            MaxDimension.value(),
            MaxDimension.value(),
            n_elements,
        ), (" Given directors shape is not correct, it should be " +
            str(directors.shape) + " but instead " + str(directors_temp.shape))

        # Check if d1, d2, d3 are unit vectors
        d1 = directors_temp[0, ...]
        d2 = directors_temp[1, ...]
        d3 = directors_temp[2, ...]
        assert_allclose(
            _batch_norm(d1),
            np.ones((n_elements)),
            atol=Tolerance.atol(),
            err_msg=(
                " d1 vector of input director matrix is not unit vector "),
        )
        assert_allclose(
            _batch_norm(d2),
            np.ones((n_elements)),
            atol=Tolerance.atol(),
            err_msg=(
                " d2 vector of input director matrix is not unit vector "),
        )
        assert_allclose(
            _batch_norm(d3),
            np.ones((n_elements)),
            atol=Tolerance.atol(),
            err_msg=(
                " d3 vector of input director matrix is not unit vector "),
        )

        # Check if d3xd1 = d2
        assert_allclose(
            _batch_cross(d3, d1),
            d2,
            atol=Tolerance.atol(),
            err_msg=(" d3 x d1 != d2 of input director matrix"),
        )

        # Check if computed tangents from position is the same with d3
        assert_allclose(
            tangents,
            d3,
            atol=Tolerance.atol(),
            err_msg=
            " Tangent vector computed using node positions is different than d3 vector of input directors",
        )

        directors[:] = directors_temp[:]

    else:
        # Construct directors using tangents and normal
        normal_collection = np.repeat(normal[:, np.newaxis],
                                      n_elements,
                                      axis=1)
        # Check if rod normal and rod tangent are perpendicular to each other otherwise
        # directors will be wrong!!
        assert_allclose(
            _batch_dot(normal_collection, tangents),
            0,
            atol=Tolerance.atol(),
            err_msg=(
                " Rod normal and tangent are not perpendicular to each other!"
            ),
        )
        directors[0, ...] = normal_collection
        directors[1, ...] = _batch_cross(tangents, normal_collection)
        directors[2, ...] = tangents

    # Set radius array
    radius = np.zeros((n_elements))
    # Check if the user input radius is valid
    radius_temp = np.array(base_radius)
    assert radius_temp.ndim < 2, ("Input radius shape is not correct " +
                                  str(radius_temp.shape) + " It should be " +
                                  str(radius.shape) +
                                  " or  single floating number ")
    radius[:] = radius_temp
    # Check if the elements of radius are greater than tolerance
    for k in range(n_elements):
        assert radius[k] > Tolerance.atol(), (
            " Radius has to be greater than 0" + " Check you radius input!")

    # Set density array
    density_array = np.zeros((n_elements))
    # Check if the user input density is valid
    density_temp = np.array(density)
    assert density_temp.ndim < 2, ("Input density shape is not correct " +
                                   str(density_temp.shape) + " It should be " +
                                   str(density_array.shape) +
                                   " or  single floating number ")
    density_array[:] = density_temp
    # Check if the elements of density are greater than tolerance
    for k in range(n_elements):
        assert density_array[k] > Tolerance.atol(), (
            " Density has to be greater than 0" + " Check you density input!")

    # Second moment of inertia
    A0 = np.pi * radius * radius
    I0_1 = A0 * A0 / (4.0 * np.pi)
    I0_2 = I0_1
    I0_3 = 2.0 * I0_2
    I0 = np.array([I0_1, I0_2, I0_3]).transpose()
    # Mass second moment of inertia for disk cross-section
    mass_second_moment_of_inertia = np.zeros(
        (MaxDimension.value(), MaxDimension.value(), n_elements), np.float64)

    mass_second_moment_of_inertia_temp = np.einsum("ij,i->ij", I0,
                                                   density * rest_lengths)

    for i in range(n_elements):
        np.fill_diagonal(
            mass_second_moment_of_inertia[..., i],
            mass_second_moment_of_inertia_temp[i, :],
        )
    # sanity check of mass second moment of inertia
    for k in range(n_elements):
        for i in range(0, MaxDimension.value()):
            assert mass_second_moment_of_inertia[i, i, k] > Tolerance.atol()

    # Inverse of second moment of inertia
    inv_mass_second_moment_of_inertia = np.zeros(
        (MaxDimension.value(), MaxDimension.value(), n_elements))
    for i in range(n_elements):
        # Check rank of mass moment of inertia matrix to see if it is invertible
        assert (np.linalg.matrix_rank(
            mass_second_moment_of_inertia[..., i]) == MaxDimension.value())
        inv_mass_second_moment_of_inertia[..., i] = np.linalg.inv(
            mass_second_moment_of_inertia[..., i])

    # Shear/Stretch matrix
    shear_modulus = youngs_modulus / (poisson_ratio + 1.0)
    shear_matrix = np.zeros(
        (MaxDimension.value(), MaxDimension.value(), n_elements), np.float64)
    for i in range(n_elements):
        np.fill_diagonal(
            shear_matrix[..., i],
            [
                alpha_c * shear_modulus * A0[i],
                alpha_c * shear_modulus * A0[i],
                youngs_modulus * A0[i],
            ],
        )

    # Bend/Twist matrix
    bend_matrix = np.zeros(
        (MaxDimension.value(), MaxDimension.value(), n_elements), np.float64)
    for i in range(n_elements):
        np.fill_diagonal(
            bend_matrix[..., i],
            [
                youngs_modulus * I0_1[i],
                youngs_modulus * I0_2[i],
                shear_modulus * I0_3[i],
            ],
        )
    for k in range(n_elements):
        for i in range(0, MaxDimension.value()):
            assert bend_matrix[i, i, k] > Tolerance.atol()
    # Compute bend matrix in Voronoi Domain
    bend_matrix = (bend_matrix[..., 1:] * rest_lengths[1:] +
                   bend_matrix[..., :-1] * rest_lengths[0:-1]) / (
                       rest_lengths[1:] + rest_lengths[:-1])

    # Compute volume of elements
    volume = np.pi * radius**2 * rest_lengths

    # Compute mass of elements
    mass = np.zeros(n_elements + 1)
    mass[:-1] += 0.5 * density * volume
    mass[1:] += 0.5 * density * volume

    # Set dissipation constant or nu array
    dissipation_constant_for_forces = np.zeros((n_elements))
    # Check if the user input nu is valid
    nu_temp = np.array(nu)
    assert nu_temp.ndim < 2, (
        "Input dissipation constant(nu) for forces shape is not correct " +
        str(nu_temp.shape) + " It should be " +
        str(dissipation_constant_for_forces.shape) +
        " or  single floating number ")
    dissipation_constant_for_forces[:] = nu
    # Check if the elements of dissipation constant greater than tolerance
    for k in range(n_elements):
        assert dissipation_constant_for_forces[k] >= 0.0, (
            " Dissipation constant has to be equal or greater than 0 " +
            " Check your dissipation constant(nu) input!")

    dissipation_constant_for_torques = np.zeros((n_elements))
    if kwargs.__contains__("nu_for_torques"):
        temp_nu_for_torques = np.array(kwargs["nu_for_torques"])
        assert temp_nu_for_torques.ndim < 2, (
            "Input dissipation constant(nu) for torques shape is not correct "
            + str(temp_nu_for_torques.shape) + " It should be " +
            str(dissipation_constant_for_torques.shape) +
            " or  single floating number ")
        dissipation_constant_for_torques[:] = temp_nu_for_torques

    else:
        dissipation_constant_for_torques[:] = dissipation_constant_for_forces

    # Generate rest sigma and rest kappa, use user input if defined
    # set rest strains and curvature to be  zero at start
    # if found in kwargs modify (say for curved rod)
    rest_sigma = np.zeros((MaxDimension.value(), n_elements))
    if kwargs.__contains__("rest_sigma"):
        temp_rest_sigma = np.array(kwargs["rest_sigma"])
        assert temp_rest_sigma.shape == rest_sigma.shape, (
            "Input rest sigma shape is not correct " +
            str(temp_rest_sigma.shape) + " It should be " +
            str(rest_sigma.shape))
        rest_sigma[:] = temp_rest_sigma

    rest_kappa = np.zeros((MaxDimension.value(), n_elements - 1))
    if kwargs.__contains__("rest_kappa"):
        temp_rest_kappa = np.array(kwargs["rest_kappa"])
        assert temp_rest_kappa.shape == rest_kappa.shape, (
            "Input rest kappa shape is not correct " +
            str(temp_rest_kappa.shape) + " It should be " +
            str(rest_kappa.shape))
        rest_kappa[:] = temp_rest_kappa

    # Compute rest voronoi length
    rest_voronoi_lengths = 0.5 * (rest_lengths[1:] + rest_lengths[:-1])

    # Allocate arrays for Cosserat Rod equations
    velocities = np.zeros((MaxDimension.value(), n_elements + 1))
    omegas = np.zeros((MaxDimension.value(), n_elements))
    accelerations = 0.0 * velocities
    angular_accelerations = 0.0 * omegas
    _vector_states = np.hstack(
        (position, velocities, omegas, accelerations, angular_accelerations))
    _matrix_states = directors.copy()

    internal_forces = 0.0 * accelerations
    internal_torques = 0.0 * angular_accelerations

    external_forces = 0.0 * accelerations
    external_torques = 0.0 * angular_accelerations

    lengths = np.zeros((n_elements))
    tangents = np.zeros((3, n_elements))

    dilatation = np.zeros((n_elements))
    voronoi_dilatation = np.zeros((n_elements - 1))
    dilatation_rate = np.zeros((n_elements))

    sigma = np.zeros((3, n_elements))
    kappa = np.zeros((3, n_elements - 1))

    internal_stress = np.zeros((3, n_elements))
    internal_couple = np.zeros((3, n_elements - 1))

    damping_forces = np.zeros((3, n_elements + 1))
    damping_torques = np.zeros((3, n_elements))

    return (
        n_elements,
        _vector_states,
        _matrix_states,
        radius,
        mass_second_moment_of_inertia,
        inv_mass_second_moment_of_inertia,
        shear_matrix,
        bend_matrix,
        density,
        volume,
        mass,
        dissipation_constant_for_forces,
        dissipation_constant_for_torques,
        internal_forces,
        internal_torques,
        external_forces,
        external_torques,
        lengths,
        rest_lengths,
        tangents,
        dilatation,
        dilatation_rate,
        voronoi_dilatation,
        rest_voronoi_lengths,
        sigma,
        kappa,
        rest_sigma,
        rest_kappa,
        internal_stress,
        internal_couple,
        damping_forces,
        damping_torques,
    )
Exemple #6
0
def anisotropic_friction(
    plane_origin,
    plane_normal,
    surface_tol,
    slip_velocity_tol,
    k,
    nu,
    kinetic_mu_forward,
    kinetic_mu_backward,
    kinetic_mu_sideways,
    static_mu_forward,
    static_mu_backward,
    static_mu_sideways,
    radius,
    tangents,
    position_collection,
    director_collection,
    velocity_collection,
    omega_collection,
    internal_forces,
    external_forces,
    internal_torques,
    external_torques,
):
    plane_response_force_mag, no_contact_point_idx = apply_normal_force_numba(
        plane_origin,
        plane_normal,
        surface_tol,
        k,
        nu,
        radius,
        position_collection,
        velocity_collection,
        internal_forces,
        external_forces,
    )

    # First compute component of rod tangent in plane. Because friction forces acts in plane not out of plane. Thus
    # axial direction has to be in plane, it cannot be out of plane. We are projecting rod element tangent vector in
    # to the plane. So friction forces can only be in plane forces and not out of plane.
    tangent_along_normal_direction = _batch_product_i_ik_to_k(
        plane_normal, tangents)
    tangent_perpendicular_to_normal_direction = tangents - _batch_product_i_k_to_ik(
        plane_normal, tangent_along_normal_direction)
    tangent_perpendicular_to_normal_direction_mag = _batch_norm(
        tangent_perpendicular_to_normal_direction)
    # Normalize tangent_perpendicular_to_normal_direction. This is axial direction for plane. Here we are adding
    # small tolerance (1e-10) for normalization, in order to prevent division by 0.
    axial_direction = _batch_product_k_ik_to_ik(
        1 / (tangent_perpendicular_to_normal_direction_mag + 1e-14),
        tangent_perpendicular_to_normal_direction,
    )
    element_velocity = node_to_element_pos_or_vel(velocity_collection)
    # first apply axial kinetic friction
    velocity_mag_along_axial_direction = _batch_dot(element_velocity,
                                                    axial_direction)
    velocity_along_axial_direction = _batch_product_k_ik_to_ik(
        velocity_mag_along_axial_direction, axial_direction)

    # Friction forces depends on the direction of velocity, in other words sign
    # of the velocity vector.
    velocity_sign_along_axial_direction = np.sign(
        velocity_mag_along_axial_direction)
    # Check top for sign convention
    kinetic_mu = 0.5 * (kinetic_mu_forward *
                        (1 + velocity_sign_along_axial_direction) +
                        kinetic_mu_backward *
                        (1 - velocity_sign_along_axial_direction))
    # Call slip function to check if elements slipping or not
    slip_function_along_axial_direction = find_slipping_elements(
        velocity_along_axial_direction, slip_velocity_tol)

    # Now rolling kinetic friction
    rolling_direction = _batch_vec_oneD_vec_cross(axial_direction,
                                                  plane_normal)
    torque_arm = _batch_product_i_k_to_ik(-plane_normal, radius)
    velocity_along_rolling_direction = _batch_dot(element_velocity,
                                                  rolling_direction)
    directors_transpose = _batch_matrix_transpose(director_collection)
    # w_rot = Q.T @ omega @ Q @ r
    rotation_velocity = _batch_matvec(
        directors_transpose,
        _batch_cross(omega_collection,
                     _batch_matvec(director_collection, torque_arm)),
    )
    rotation_velocity_along_rolling_direction = _batch_dot(
        rotation_velocity, rolling_direction)
    slip_velocity_mag_along_rolling_direction = (
        velocity_along_rolling_direction +
        rotation_velocity_along_rolling_direction)
    slip_velocity_along_rolling_direction = _batch_product_k_ik_to_ik(
        slip_velocity_mag_along_rolling_direction, rolling_direction)
    slip_function_along_rolling_direction = find_slipping_elements(
        slip_velocity_along_rolling_direction, slip_velocity_tol)
    # Compute unitized total slip velocity vector. We will use this to distribute the weight of the rod in axial
    # and rolling directions.
    unitized_total_velocity = (slip_velocity_along_rolling_direction +
                               velocity_along_axial_direction)
    unitized_total_velocity /= _batch_norm(unitized_total_velocity + 1e-14)
    # Apply kinetic friction in axial direction.
    kinetic_friction_force_along_axial_direction = -(
        (1.0 - slip_function_along_axial_direction) * kinetic_mu *
        plane_response_force_mag *
        _batch_dot(unitized_total_velocity, axial_direction) * axial_direction)
    # If rod element does not have any contact with plane, plane cannot apply friction
    # force on the element. Thus lets set kinetic friction force to 0.0 for the no contact points.
    kinetic_friction_force_along_axial_direction[...,
                                                 no_contact_point_idx] = 0.0
    elements_to_nodes_inplace(kinetic_friction_force_along_axial_direction,
                              external_forces)
    # Apply kinetic friction in rolling direction.
    kinetic_friction_force_along_rolling_direction = -(
        (1.0 - slip_function_along_rolling_direction) *
        kinetic_mu_sideways * plane_response_force_mag * _batch_dot(
            unitized_total_velocity, rolling_direction) * rolling_direction)
    # If rod element does not have any contact with plane, plane cannot apply friction
    # force on the element. Thus lets set kinetic friction force to 0.0 for the no contact points.
    kinetic_friction_force_along_rolling_direction[...,
                                                   no_contact_point_idx] = 0.0
    elements_to_nodes_inplace(kinetic_friction_force_along_rolling_direction,
                              external_forces)
    # torque = Q @ r @ Fr
    external_torques += _batch_matvec(
        director_collection,
        _batch_cross(torque_arm,
                     kinetic_friction_force_along_rolling_direction),
    )

    # now axial static friction
    nodal_total_forces = _batch_vector_sum(internal_forces, external_forces)
    element_total_forces = nodes_to_elements(nodal_total_forces)
    force_component_along_axial_direction = _batch_dot(element_total_forces,
                                                       axial_direction)
    force_component_sign_along_axial_direction = np.sign(
        force_component_along_axial_direction)
    # check top for sign convention
    static_mu = 0.5 * (static_mu_forward *
                       (1 + force_component_sign_along_axial_direction) +
                       static_mu_backward *
                       (1 - force_component_sign_along_axial_direction))
    max_friction_force = (slip_function_along_axial_direction * static_mu *
                          plane_response_force_mag)
    # friction = min(mu N, pushing force)
    static_friction_force_along_axial_direction = -(
        np.minimum(np.fabs(force_component_along_axial_direction),
                   max_friction_force) *
        force_component_sign_along_axial_direction * axial_direction)
    # If rod element does not have any contact with plane, plane cannot apply friction
    # force on the element. Thus lets set static friction force to 0.0 for the no contact points.
    static_friction_force_along_axial_direction[...,
                                                no_contact_point_idx] = 0.0
    elements_to_nodes_inplace(static_friction_force_along_axial_direction,
                              external_forces)

    # now rolling static friction
    # there is some normal, tangent and rolling directions inconsitency from Elastica
    total_torques = _batch_matvec(directors_transpose,
                                  (internal_torques + external_torques))
    # Elastica has opposite defs of tangents in interaction.h and rod.cpp
    total_torques_along_axial_direction = _batch_dot(total_torques,
                                                     axial_direction)
    force_component_along_rolling_direction = _batch_dot(
        element_total_forces, rolling_direction)
    noslip_force = -(
        (radius * force_component_along_rolling_direction -
         2.0 * total_torques_along_axial_direction) / 3.0 / radius)
    max_friction_force = (slip_function_along_rolling_direction *
                          static_mu_sideways * plane_response_force_mag)
    noslip_force_sign = np.sign(noslip_force)
    static_friction_force_along_rolling_direction = (
        np.minimum(np.fabs(noslip_force), max_friction_force) *
        noslip_force_sign * rolling_direction)
    # If rod element does not have any contact with plane, plane cannot apply friction
    # force on the element. Thus lets set plane static friction force to 0.0 for the no contact points.
    static_friction_force_along_rolling_direction[...,
                                                  no_contact_point_idx] = 0.0
    elements_to_nodes_inplace(static_friction_force_along_rolling_direction,
                              external_forces)
    external_torques += _batch_matvec(
        director_collection,
        _batch_cross(torque_arm,
                     static_friction_force_along_rolling_direction),
    )
Exemple #7
0
def anisotropic_friction_numba_rigid_body(
    plane_origin,
    plane_normal,
    surface_tol,
    slip_velocity_tol,
    k,
    nu,
    kinetic_mu_forward,
    kinetic_mu_backward,
    kinetic_mu_sideways,
    static_mu_forward,
    static_mu_backward,
    static_mu_sideways,
    length,
    position_collection,
    director_collection,
    velocity_collection,
    omega_collection,
    external_forces,
    external_torques,
):
    # calculate axial and rolling directions
    # plane_response_force_mag, no_contact_point_idx = self.apply_normal_force(system)
    (
        plane_response_force_mag,
        no_contact_point_idx,
    ) = apply_normal_force_numba_rigid_body(
        plane_origin,
        plane_normal,
        surface_tol,
        k,
        nu,
        length,
        position_collection,
        velocity_collection,
        external_forces,
    )
    # FIXME: In future change the below part we should be able to compute the normal
    axial_direction = director_collection[
        0]  # rigid_body_normal  # system.tangents
    element_velocity = velocity_collection

    # first apply axial kinetic friction
    velocity_mag_along_axial_direction = _batch_dot(element_velocity,
                                                    axial_direction)
    velocity_along_axial_direction = _batch_product_k_ik_to_ik(
        velocity_mag_along_axial_direction, axial_direction)
    # Friction forces depends on the direction of velocity, in other words sign
    # of the velocity vector.
    velocity_sign_along_axial_direction = np.sign(
        velocity_mag_along_axial_direction)
    # Check top for sign convention
    kinetic_mu = 0.5 * (kinetic_mu_forward *
                        (1 + velocity_sign_along_axial_direction) +
                        kinetic_mu_backward *
                        (1 - velocity_sign_along_axial_direction))
    # Call slip function to check if elements slipping or not
    slip_function_along_axial_direction = find_slipping_elements(
        velocity_along_axial_direction, slip_velocity_tol)
    kinetic_friction_force_along_axial_direction = -(
        (1.0 - slip_function_along_axial_direction) * kinetic_mu *
        plane_response_force_mag * velocity_sign_along_axial_direction *
        axial_direction)

    binormal_direction = director_collection[1]  # rigid_body_binormal
    velocity_mag_along_binormal_direction = _batch_dot(element_velocity,
                                                       binormal_direction)
    velocity_along_binormal_direction = _batch_product_k_ik_to_ik(
        velocity_mag_along_binormal_direction, binormal_direction)
    # Friction forces depends on the direction of velocity, in other words sign
    # of the velocity vector.
    velocity_sign_along_binormal_direction = np.sign(
        velocity_mag_along_binormal_direction)
    # Check top for sign convention
    kinetic_mu = 0.5 * (kinetic_mu_forward *
                        (1 + velocity_sign_along_binormal_direction) +
                        kinetic_mu_backward *
                        (1 - velocity_sign_along_binormal_direction))
    # Call slip function to check if elements slipping or not
    slip_function_along_binormal_direction = find_slipping_elements(
        velocity_along_binormal_direction, slip_velocity_tol)
    kinetic_friction_force_along_binormal_direction = -(
        (1.0 - slip_function_along_binormal_direction) * kinetic_mu *
        plane_response_force_mag * velocity_mag_along_binormal_direction *
        binormal_direction)

    # If rod element does not have any contact with plane, plane cannot apply friction
    # force on the element. Thus lets set kinetic friction force to 0.0 for the no contact points.
    kinetic_friction_force_along_axial_direction[...,
                                                 no_contact_point_idx] = 0.0
    kinetic_friction_force_along_binormal_direction[...,
                                                    no_contact_point_idx] = 0.0
    external_forces += (kinetic_friction_force_along_axial_direction +
                        kinetic_friction_force_along_binormal_direction)