예제 #1
0
 def compute_muscle_torques(
     time,
     my_spline,
     s,
     angular_frequency,
     wave_number,
     phase_shift,
     ramp_up_time,
     direction,
     director_collection,
     external_torques,
 ):
     # Ramp up the muscle torque
     factor = min(1.0, time / ramp_up_time)
     # From the node 1 to node nelem-1
     # Magnitude of the torque. Am = beta(s) * sin(2pi*t/T + 2pi*s/lambda + phi)
     # There is an inconsistency with paper and Elastica cpp implementation. In paper sign in
     # front of wave number is positive, in Elastica cpp it is negative.
     torque_mag = (
         factor
         * my_spline
         * np.sin(angular_frequency * time - wave_number * s + phase_shift)
     )
     # Head and tail of the snake is opposite compared to elastica cpp. We need to iterate torque_mag
     # from last to first element.
     torque = _batch_product_i_k_to_ik(direction, torque_mag[::-1])
     inplace_addition(
         external_torques[..., 1:],
         _batch_matvec(director_collection, torque)[..., 1:],
     )
     inplace_substraction(
         external_torques[..., :-1],
         _batch_matvec(director_collection[..., :-1], torque[..., 1:]),
     )
예제 #2
0
    def apply_torques(self, system, time: np.float = 0.0):

        # Ramp up the muscle torque
        factor = min(1.0, time / self.ramp_up_time)
        # From the node 1 to node nelem-1
        # s is the position of nodes on the rod, we go from node=1 to node=nelem-1, because there is no
        # torques applied by first and last node on elements. Reason is that we cannot apply torque in an
        # infinitesimal segment at the beginning and end of rod, because there is no additional element
        # (at element=-1 or element=n_elem+1) to provide internal torques to cancel out an external
        # torque. This coupled with the requirement that the sum of all muscle torques has
        # to be zero results in this condition.
        s = np.cumsum(system.rest_lengths)
        # Magnitude of the torque. Am = beta(s) * sin(2pi*t/T + 2pi*s/lambda + phi)
        # There is an inconsistency with paper and Elastica cpp implementation. In paper sign in
        # front of wave number is positive, in Elastica cpp it is negative.
        torque_mag = (factor * self.my_spline(s) *
                      np.sin(self.angular_frequency * time -
                             self.wave_number * s + self.phase_shift))
        # Head and tail of the snake is opposite compared to elastica cpp. We need to iterate torque_mag
        # from last to first element.
        torque = np.einsum("j,ij->ij", torque_mag[::-1], self.direction)
        # TODO: Find a way without doing tow batch_matvec product
        system.external_torques[..., 1:] += _batch_matvec(
            system.director_collection, torque)[..., 1:]
        system.external_torques[..., :-1] -= _batch_matvec(
            system.director_collection[..., :-1], torque[..., 1:])
예제 #3
0
 def _apply_torques(index_one, torque, rod_one_director_collection,
                    rod_one_external_torques):
     start_idx = index_one[0]
     end_idx = index_one[-1]
     rod_one_external_torques[..., start_idx] += 0.5 * _batch_matvec(
         rod_one_director_collection[..., start_idx:start_idx + 1],
         torque).reshape(3)
     rod_one_external_torques[..., end_idx] -= 0.5 * _batch_matvec(
         rod_one_director_collection[..., end_idx:end_idx + 1],
         torque).reshape(3)
예제 #4
0
def _compute_internal_bending_twist_stresses_from_model(
    director_collection,
    rest_voronoi_lengths,
    internal_couple,
    bend_matrix,
    kappa,
    rest_kappa,
):
    """
    Linear force functional
    Operates on
    B : (3,3,n) tensor and curvature kappa (3,n)

    Returns
    -------

    """
    _compute_bending_twist_strains(director_collection, rest_voronoi_lengths,
                                   kappa)  # concept : needs to compute kappa

    blocksize = kappa.shape[1]
    temp = np.empty((3, blocksize))
    for i in range(3):
        for k in range(blocksize):
            temp[i, k] = kappa[i, k] - rest_kappa[i, k]

    internal_couple[:] = _batch_matvec(bend_matrix, temp)
예제 #5
0
    def update_accelerations(self, time):
        """
        This method computes translational and angular acceleration and reset external forces and torques to zero.

        Parameters
        ----------
        time: float
            Simulation time.

        Returns
        -------

        """
        np.copyto(
            self.acceleration_collection,
            (self.internal_forces + self.external_forces) / self.mass,
        )
        np.copyto(
            self.alpha_collection,
            _batch_matvec(
                self.inv_mass_second_moment_of_inertia,
                (self.internal_torques + self.external_torques),
            )
            * self.dilatation,
        )

        # Reset forces and torques
        self.external_forces *= 0.0
        self.external_torques *= 0.0
예제 #6
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())
예제 #7
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())
예제 #8
0
def _compute_shear_stretch_strains(
    position_collection,
    volume,
    lengths,
    tangents,
    radius,
    rest_lengths,
    rest_voronoi_lengths,
    dilatation,
    voronoi_dilatation,
    director_collection,
    sigma,
):
    # Quick trick : Instead of evaliation Q(et-d^3), use property that Q*d3 = (0,0,1), a constant
    _compute_all_dilatations(
        position_collection,
        volume,
        lengths,
        tangents,
        radius,
        dilatation,
        rest_lengths,
        rest_voronoi_lengths,
        voronoi_dilatation,
    )

    z_vector = np.array([0.0, 0.0, 1.0]).reshape(3, -1)
    sigma[:] = dilatation * _batch_matvec(director_collection,
                                          tangents) - z_vector
예제 #9
0
    def update_accelerations(self, time):
        """TODO Do we need to make the collection members abstract?

        Parameters
        ----------
        time

        Returns
        -------

        """
        np.copyto(
            self.acceleration_collection,
            (self.internal_forces + self.external_forces) / self.mass,
        )
        np.copyto(
            self.alpha_collection,
            _batch_matvec(
                self.inv_mass_second_moment_of_inertia,
                (self.internal_torques + self.external_torques),
            ),
        )

        # Reset forces and torques
        self.external_forces *= 0.0
        self.external_torques *= 0.0
예제 #10
0
 def apply_torques(self, system, time: np.float = 0.0):
     n_elems = system.n_elems
     torque_on_one_element = (
         _batch_product_i_k_to_ik(self.torque, np.ones((n_elems))) / n_elems
     )
     system.external_torques += _batch_matvec(
         system.director_collection, torque_on_one_element
     )
예제 #11
0
    def test_update_acceleration(self, n_elem):
        """
        In this test case, we initialize a straight rod.
        We set correct parameters for rod mass, dilatation, mass moment of inertia
        and call the function update_accelerations and compare the angular and
        translational acceleration with the correct values.
        This test case tests,
            update_accelerations
            _update_accelerations

        """

        initial, test_rod = constructor(n_elem, nu=0.0)
        mass = test_rod.mass

        external_forces = np.zeros(3 * (n_elem + 1)).reshape(3, n_elem + 1)
        external_torques = np.zeros(3 * n_elem).reshape(3, n_elem)

        for i in range(0, n_elem):
            external_torques[..., i] = np.random.rand(3)

        for i in range(0, n_elem + 1):
            external_forces[..., i] = np.random.rand(3)

        test_rod.external_forces[:] = external_forces
        test_rod.external_torques[:] = external_torques

        # No dilatation in the rods
        dilatations = np.ones(n_elem)

        # Set mass moment of inertia matrix to identity matrix for convenience.
        # Inverse of identity = identity
        inv_mass_moment_of_inertia = np.repeat(np.identity(3)[:, :,
                                                              np.newaxis],
                                               n_elem,
                                               axis=2)
        test_rod.inv_mass_second_moment_of_inertia[:] = inv_mass_moment_of_inertia

        # Compute acceleration
        test_rod.update_accelerations(time=0)

        correct_acceleration = external_forces / mass
        assert_allclose(
            test_rod.acceleration_collection,
            correct_acceleration,
            atol=Tolerance.atol(),
        )

        correct_angular_acceleration = (
            _batch_matvec(inv_mass_moment_of_inertia, external_torques) *
            dilatations)

        assert_allclose(
            test_rod.alpha_collection,
            correct_angular_acceleration,
            atol=Tolerance.atol(),
        )
예제 #12
0
    def apply_torques(self, system, time: np.float = 0.0):

        # Ramp up the muscle torque
        factor = min(1.0, time / self.ramp_up_time)
        # From the node 1 to node nelem-1
        # Magnitude of the torque. Am = beta(s) * sin(2pi*t/T + 2pi*s/lambda + phi)
        # There is an inconsistency with paper and Elastica cpp implementation. In paper sign in
        # front of wave number is positive, in Elastica cpp it is negative.
        torque_mag = (factor * self.my_spline *
                      np.sin(self.angular_frequency * time -
                             self.wave_number * self.s + self.phase_shift))
        # Head and tail of the snake is opposite compared to elastica cpp. We need to iterate torque_mag
        # from last to first element.
        torque = np.einsum("j,ij->ij", torque_mag[::-1], self.direction)
        # TODO: Find a way without doing tow batch_matvec product
        system.external_torques[..., 1:] += _batch_matvec(
            system.director_collection, torque)[..., 1:]
        system.external_torques[..., :-1] -= _batch_matvec(
            system.director_collection[..., :-1], torque[..., 1:])
예제 #13
0
    def compute_rotational_energy(self):
        """
        This method computes the total rotational energy of the rod.

        Returns
        -------

        """
        J_omega_upon_e = (
            _batch_matvec(self.mass_second_moment_of_inertia, self.omega_collection)
            / self.dilatation
        )
        return 0.5 * np.einsum("ik,ik->k", self.omega_collection, J_omega_upon_e).sum()
예제 #14
0
    def _compute_shear_stretch_strains(self):
        """
        This method computes shear and stretch strains of the elements.

        Returns
        -------

        """
        # Quick trick : Instead of evaliation Q(et-d^3), use property that Q*d3 = (0,0,1), a constant
        self._compute_all_dilatations()
        self.sigma = (
            self.dilatation * _batch_matvec(self.director_collection, self.tangents)
            - _get_z_vector()
        )
예제 #15
0
def test_batch_matvec(blocksize):
    input_matrix_collection = np.random.randn(3, 3, blocksize)
    input_vector_collection = np.random.randn(3, blocksize)

    test_vector_collection = _batch_matvec(input_matrix_collection,
                                           input_vector_collection)

    correct_vector_collection = [
        np.dot(input_matrix_collection[..., i],
               input_vector_collection[..., i]) for i in range(blocksize)
    ]
    correct_vector_collection = np.array(correct_vector_collection).T

    assert_allclose(test_vector_collection, correct_vector_collection)
예제 #16
0
def _compute_internal_shear_stretch_stresses_from_model(
    position_collection,
    volume,
    lengths,
    tangents,
    radius,
    rest_lengths,
    rest_voronoi_lengths,
    dilatation,
    voronoi_dilatation,
    director_collection,
    sigma,
    rest_sigma,
    shear_matrix,
    internal_stress,
):
    """
    Linear force functional
    Operates on
    S : (3,3,n) tensor and sigma (3,n)

    Returns
    -------

    """
    _compute_shear_stretch_strains(
        position_collection,
        volume,
        lengths,
        tangents,
        radius,
        rest_lengths,
        rest_voronoi_lengths,
        dilatation,
        voronoi_dilatation,
        director_collection,
        sigma,
    )
    internal_stress[:] = _batch_matvec(shear_matrix, sigma - rest_sigma)
예제 #17
0
    def test_get_functions(self, n_elem):
        """
            In this test case, we initialize a straight rod. First
            we set velocity and angular velocity to random values,
            and we recover back initialized values using `get_velocity`
            and `get_angular_velocity` functions. Then, we set random
            external forces and torques. We compute translational
            accelerations and angular accelerations based on external
            forces and torques. Finally we use `get_acceleration` and
            `get_angular_acceleration` functions and compare returned
            translational acceleration and angular acceleration with
            analytically computed ones.
            This test case tests,
                get_velocity
                get_angular_velocity
                get_acceleration
                get_angular_acceleration
            Note that, viscous dissipation set to 0,
            since we don't want any contribution from
            damping torque.
        """

        initial, test_rod = constructor(n_elem, nu=0.0)
        mass = test_rod.mass

        velocity = np.zeros(3 * (n_elem + 1)).reshape(3, n_elem + 1)
        external_forces = np.zeros(3 * (n_elem + 1)).reshape(3, n_elem + 1)
        omega = np.zeros(3 * n_elem).reshape(3, n_elem)
        external_torques = np.zeros(3 * n_elem).reshape(3, n_elem)

        for i in range(0, n_elem):
            omega[..., i] = np.random.rand(3)
            external_torques[..., i] = np.random.rand(3)

        for i in range(0, n_elem + 1):
            velocity[..., i] = np.random.rand(3)
            external_forces[..., i] = np.random.rand(3)

        test_rod.velocity_collection = velocity
        test_rod.omega_collection = omega

        assert_allclose(test_rod.get_velocity(),
                        velocity,
                        atol=Tolerance.atol())
        assert_allclose(test_rod.get_angular_velocity(),
                        omega,
                        atol=Tolerance.atol())

        test_rod.external_forces = external_forces
        test_rod.external_torques = external_torques

        correct_acceleration = external_forces / mass
        assert_allclose(test_rod.get_acceleration(),
                        correct_acceleration,
                        atol=Tolerance.atol())

        # No dilatation in the rods
        dilatations = np.ones(n_elem)

        # Set angular velocity zero, so that we dont have any
        # contribution from lagrangian transport and unsteady dilatation.

        test_rod.omega_collection[:] = 0.0
        # Set mass moment of inertia matrix to identity matrix for convenience.
        # Inverse of identity = identity
        inv_mass_moment_of_inertia = np.repeat(np.identity(3)[:, :,
                                                              np.newaxis],
                                               n_elem,
                                               axis=2)
        test_rod.inv_mass_second_moment_of_inertia = inv_mass_moment_of_inertia

        correct_angular_acceleration = (
            _batch_matvec(inv_mass_moment_of_inertia, external_torques) *
            dilatations)

        assert_allclose(
            test_rod.get_angular_acceleration(),
            correct_angular_acceleration,
            atol=Tolerance.atol(),
        )
예제 #18
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),
    )
예제 #19
0
 def compute_rotational_energy(self):
     J_omega_upon_e = (_batch_matvec(self.mass_second_moment_of_inertia,
                                     self.omega_collection) /
                       self.dilatation)
     return 0.5 * np.einsum("ik,ik->k", self.omega_collection,
                            J_omega_upon_e).sum()
예제 #20
0
    def _compute_internal_torques(self):
        """
        This method computes internal torques acting on elements.

        Returns
        -------

        """
        # Compute \tau_l and cache it using internal_couple
        # Be careful about usage though
        self._compute_internal_bending_twist_stresses_from_model()
        # Compute dilatation rate when needed, dilatation itself is done before
        # in internal_stresses
        self._compute_dilatation_rate()

        voronoi_dilatation_inv_cube_cached = 1.0 / self.voronoi_dilatation ** 3
        # Delta(\tau_L / \Epsilon^3)
        bend_twist_couple_2D = difference_kernel(
            self.internal_couple * voronoi_dilatation_inv_cube_cached
        )
        # \mathcal{A}[ (\kappa x \tau_L ) * \hat{D} / \Epsilon^3 ]
        bend_twist_couple_3D = quadrature_kernel(
            _batch_cross(self.kappa, self.internal_couple)
            * self.rest_voronoi_lengths
            * voronoi_dilatation_inv_cube_cached
        )
        # (Qt x n_L) * \hat{l}
        shear_stretch_couple = (
            _batch_cross(
                _batch_matvec(self.director_collection, self.tangents),
                self.internal_stress,
            )
            * self.rest_lengths
        )

        # I apply common sub expression elimination here, as J w / e is used in both the lagrangian and dilatation
        # terms
        # TODO : the _batch_matvec kernel needs to depend on the representation of J, and should be coded as such
        J_omega_upon_e = (
            _batch_matvec(self.mass_second_moment_of_inertia, self.omega_collection)
            / self.dilatation
        )

        # (J \omega_L / e) x \omega_L
        # Warning : Do not do micro-optimization here : you can ignore dividing by dilatation as we later multiply by it
        # but this causes confusion and violates SRP
        lagrangian_transport = _batch_cross(J_omega_upon_e, self.omega_collection)

        # Note : in the computation of dilatation_rate, there is an optimization opportunity as dilatation rate has
        # a dilatation-like term in the numerator, which we cancel here
        # (J \omega_L / e^2) . (de/dt)
        unsteady_dilatation = J_omega_upon_e * self.dilatation_rate / self.dilatation

        return (
            bend_twist_couple_2D
            + bend_twist_couple_3D
            + shear_stretch_couple
            + lagrangian_transport
            + unsteady_dilatation
            - self._compute_damping_torques()
        )
예제 #21
0
def _compute_internal_torques(
    position_collection,
    velocity_collection,
    tangents,
    lengths,
    rest_lengths,
    director_collection,
    rest_voronoi_lengths,
    bend_matrix,
    rest_kappa,
    kappa,
    voronoi_dilatation,
    mass_second_moment_of_inertia,
    omega_collection,
    internal_stress,
    internal_couple,
    dilatation,
    dilatation_rate,
    dissipation_constant_for_torques,
    damping_torques,
    internal_torques,
    ghost_voronoi_idx,
):
    # Compute \tau_l and cache it using internal_couple
    # Be careful about usage though
    _compute_internal_bending_twist_stresses_from_model(
        director_collection,
        rest_voronoi_lengths,
        internal_couple,
        bend_matrix,
        kappa,
        rest_kappa,
    )
    # Compute dilatation rate when needed, dilatation itself is done before
    # in internal_stresses
    _compute_dilatation_rate(position_collection, velocity_collection, lengths,
                             rest_lengths, dilatation_rate)

    # FIXME: change memory overload instead for the below calls!
    voronoi_dilatation_inv_cube_cached = 1.0 / voronoi_dilatation**3
    # Delta(\tau_L / \Epsilon^3)
    bend_twist_couple_2D = difference_kernel_for_block_structure(
        internal_couple * voronoi_dilatation_inv_cube_cached,
        ghost_voronoi_idx)
    # \mathcal{A}[ (\kappa x \tau_L ) * \hat{D} / \Epsilon^3 ]
    bend_twist_couple_3D = quadrature_kernel_for_block_structure(
        _batch_cross(kappa, internal_couple) * rest_voronoi_lengths *
        voronoi_dilatation_inv_cube_cached,
        ghost_voronoi_idx,
    )
    # (Qt x n_L) * \hat{l}
    shear_stretch_couple = (_batch_cross(
        _batch_matvec(director_collection, tangents), internal_stress) *
                            rest_lengths)

    # I apply common sub expression elimination here, as J w / e is used in both the lagrangian and dilatation
    # terms
    # TODO : the _batch_matvec kernel needs to depend on the representation of J, and should be coded as such
    J_omega_upon_e = (
        _batch_matvec(mass_second_moment_of_inertia, omega_collection) /
        dilatation)

    # (J \omega_L / e) x \omega_L
    # Warning : Do not do micro-optimization here : you can ignore dividing by dilatation as we later multiply by it
    # but this causes confusion and violates SRP
    lagrangian_transport = _batch_cross(J_omega_upon_e, omega_collection)

    # Note : in the computation of dilatation_rate, there is an optimization opportunity as dilatation rate has
    # a dilatation-like term in the numerator, which we cancel here
    # (J \omega_L / e^2) . (de/dt)
    unsteady_dilatation = J_omega_upon_e * dilatation_rate / dilatation

    _compute_damping_torques(damping_torques, omega_collection,
                             dissipation_constant_for_torques, lengths)

    blocksize = internal_torques.shape[1]
    for i in range(3):
        for k in range(blocksize):
            internal_torques[i, k] = (bend_twist_couple_2D[i, k] +
                                      bend_twist_couple_3D[i, k] +
                                      shear_stretch_couple[i, k] +
                                      lagrangian_transport[i, k] +
                                      unsteady_dilatation[i, k] -
                                      damping_torques[i, k])
예제 #22
0
 def apply_torques(self, system, time: np.float = 0.0):
     torque_on_one_element = self.torque / system.n_elems
     system.external_torques += _batch_matvec(system.director_collection,
                                              torque_on_one_element)