Beispiel #1
0
    def __init__(
        self,
        position_start,
        position_end,
        director_start,
        director_end,
        twisting_time,
        slack,
        number_of_rotations,
    ):
        """

        Parameters
        ----------

        position_start : numpy.ndarray
            2D (dim, 1) array containing data with 'float' type.
            Initial position of first node.
        position_end : numpy.ndarray
            2D (dim, 1) array containing data with 'float' type.
            Initial position of last node.
        director_start : numpy.ndarray
            3D (dim, dim, blocksize) array containing data with 'float' type.
            Initial director of first element.
        director_end : numpy.ndarray
            3D (dim, dim, blocksize) array containing data with 'float' type.
            Initial director of last element.
        twisting_time : float
            Time to complete twist.
        slack : float
            Slack applied to rod.
        number_of_rotations : float
            Number of rotations applied to rod.
        """
        FreeRod.__init__(self)
        self.twisting_time = twisting_time

        angel_vel_scalar = (2.0 * number_of_rotations * np.pi /
                            self.twisting_time) / 2.0
        shrink_vel_scalar = slack / (self.twisting_time * 2.0)

        direction = (position_end - position_start
                     ) / np.linalg.norm(position_end - position_start)

        self.final_start_position = position_start + slack / 2.0 * direction
        self.final_end_position = position_end - slack / 2.0 * direction

        self.ang_vel = angel_vel_scalar * direction
        self.shrink_vel = shrink_vel_scalar * direction

        theta = number_of_rotations * np.pi

        self.final_start_directors = (_get_rotation_matrix(
            theta, direction.reshape(3, 1)).reshape(3, 3) @ director_start
                                      )  # rotation_matrix wants vectors 3,1
        self.final_end_directors = (_get_rotation_matrix(
            -theta, direction.reshape(3, 1)).reshape(3, 3) @ director_end
                                    )  # rotation_matrix wants vectors 3,1
def test_get_rotation_matrix_correctness_across_blocksizes(blocksize):
    dim = 3
    dt = np.random.random_sample()
    vector_collection = np.random.randn(dim).reshape(-1, 1)
    # No need for copying the vector collection here, as we now create
    # new arrays inside
    correct_rot_mat_collection = _get_rotation_matrix(dt, vector_collection)
    correct_rot_mat_collection = np.tile(correct_rot_mat_collection, blocksize)

    # Construct
    test_vector_collection = np.tile(vector_collection, blocksize)
    test_rot_mat_collection = _get_rotation_matrix(dt, test_vector_collection)

    assert test_rot_mat_collection.shape == (3, 3, blocksize)
    assert_allclose(test_rot_mat_collection, correct_rot_mat_collection)
def test_get_rotation_matrix_correctness_against_canned_example():
    """
    Computer test code at page 5-7 from

    "Rotation, Reflection, and Frame Changes
    Orthogonal tensors in computational engineering mechanics"
    by Rebecca Brennen, 2018, IOP

    Returns
    -------

    """
    vector_collection = np.array([1, 3.2, 7])
    vector_collection /= np.linalg.norm(vector_collection)
    vector_collection = vector_collection.reshape(-1, 1)
    theta = np.deg2rad(76.0)
    test_rot_mat = _get_rotation_matrix(theta, vector_collection)
    # Previous correct matrix, which did not have a transpose
    """
    correct_rot_mat = np.array(
        [
            [0.254506, -0.834834, 0.488138],
            [0.915374, 0.370785, 0.156873],
            [-0.311957, 0.406903, 0.858552],
        ]
    ).reshape(3, 3, 1)
    """
    # Transpose for similar reasons mentioned before
    correct_rot_mat = np.array([
        [0.254506, -0.834834, 0.488138],
        [0.915374, 0.370785, 0.156873],
        [-0.311957, 0.406903, 0.858552],
    ]).T.reshape(3, 3, 1)

    assert_allclose(test_rot_mat, correct_rot_mat, atol=1e-6)
Beispiel #4
0
    def __iadd__(self, scaled_deriv_array):
        """ overloaded += operator

        The add for directors is customized to reflect Rodrigues' rotation
        formula.

        Parameters
        ----------
        scaled_deriv_array : numpy.ndarray containing dt * (v, ω),
        as retured from _DynamicState's `kinematic_rates` method

        Returns
        -------
        self : _KinematicState instance with inplace modified data

        Note
        -------
        Takes a numpy.ndarray and not a _KinematicState object (as one expects).
        This is done for efficiency reasons, see _DynamicState's `kinematic_rates`
        method
        """
        # x += v*dt
        self.position_collection += scaled_deriv_array[..., :self.n_nodes]
        # TODO Avoid code repeat
        # Devs : see `_State.__iadd__` for reasons why we do matmul here
        # print(_get_rotation_matrix(1.0, scaled_deriv_array[..., self.n_nodes:]))
        np.einsum(
            "ijk,jlk->ilk",
            _get_rotation_matrix(1.0, scaled_deriv_array[..., self.n_nodes:]),
            self.director_collection.copy(),
            out=self.director_collection,
        )
        return self
Beispiel #5
0
def overload_operator_kinematic_numba(
    n_nodes,
    prefac,
    position_collection,
    director_collection,
    velocity_collection,
    omega_collection,
):
    """overloaded += operator

    The add for directors is customized to reflect Rodrigues' rotation
    formula.
    Parameters
    ----------
    scaled_deriv_array : np.ndarray containing dt * (v, ω),
    as retured from _DynamicState's `kinematic_rates` method
    Returns
    -------
    self : _KinematicState instance with inplace modified data
    Caveats
    -------
    Takes a np.ndarray and not a _KinematicState object (as one expects).
    This is done for efficiency reasons, see _DynamicState's `kinematic_rates`
    method
    """
    # x += v*dt
    for i in range(3):
        for k in range(n_nodes):
            position_collection[i, k] += prefac * velocity_collection[i, k]
    rotation_matrix = _get_rotation_matrix(1.0, prefac * omega_collection)
    director_collection[:] = _batch_matmul(rotation_matrix, director_collection)

    return
Beispiel #6
0
    def __iadd__(self, scaled_deriv_array):
        """overloaded += operator

        The add for directors is customized to reflect Rodrigues' rotation
        formula.

        Parameters
        ----------
        scaled_deriv_array : np.ndarray containing dt * (v, ω),
        as retured from _DynamicState's `kinematic_rates` method

        Returns
        -------
        self : _KinematicState instance with inplace modified data

        Caveats
        -------
        Takes a np.ndarray and not a _KinematicState object (as one expects).
        This is done for efficiency reasons, see _DynamicState's `kinematic_rates`
        method
        """
        velocity_collection = scaled_deriv_array[0]
        omega_collection = scaled_deriv_array[1]
        # x += v*dt
        self.position_collection += velocity_collection
        # Devs : see `_State.__iadd__` for reasons why we do matmul here
        np.einsum(
            "ijk,jlk->ilk",
            _get_rotation_matrix(1.0, omega_collection),
            self.director_collection.copy(),
            out=self.director_collection,
        )
        return self
Beispiel #7
0
    def __init__(
        self,
        position_start,
        position_end,
        director_start,
        director_end,
        twisting_time,
        time_twis_start,
        number_of_rotations,
    ):
        FreeRod.__init__(self)
        self.twisting_time = twisting_time
        self.time_twis_start = time_twis_start

        theta = 2.0 * number_of_rotations * np.pi

        angel_vel_scalar = theta / self.twisting_time

        direction = -(position_end - position_start) / np.linalg.norm(
            position_end - position_start)

        axis_of_rotation_in_material_frame = director_end @ direction
        axis_of_rotation_in_material_frame /= np.linalg.norm(
            axis_of_rotation_in_material_frame)

        self.final_end_directors = (_get_rotation_matrix(
            -theta, axis_of_rotation_in_material_frame.reshape(3, 1)).reshape(
                3, 3) @ director_end)  # rotation_matrix wants vectors 3,1

        self.ang_vel = angel_vel_scalar * axis_of_rotation_in_material_frame

        self.position_start = position_start
        self.director_start = director_start
def test_get_rotation_matrix_gives_unit_determinant():
    dim = 3
    blocksize = 16
    dt = np.random.random_sample()
    test_rot_mat_collection = _get_rotation_matrix(
        dt, np.random.randn(dim, blocksize))

    test_det_collection = np.linalg.det(test_rot_mat_collection.T)
    correct_det_collection = 1.0 + 0.0 * test_det_collection

    assert_allclose(correct_det_collection, test_det_collection)
def test_block_structure_kinematic_update(n_rods):
    """
    This function is testing validity __iadd__ operation of kinematic_states.

    Parameters
    ----------
    n_rods

    Returns
    -------

    """

    world_rods = [
        MockRod(np.random.randint(10, 30 + 1)) for _ in range(n_rods)
    ]
    block_structure = BlockStructureWithSymplecticStepper(world_rods)

    position = block_structure.position_collection.copy()
    velocity = block_structure.velocity_collection.copy()

    directors = block_structure.director_collection.copy()
    omega = block_structure.omega_collection.copy()

    prefac = np.random.randn()

    correct_position = position + prefac * velocity
    correct_director = np.zeros(directors.shape)
    np.einsum(
        "ijk,jlk->ilk",
        _get_rotation_matrix(1.0, prefac * omega),
        directors.copy(),
        out=correct_director,
    )

    # block_structure.kinematic_states += block_structure.kinematic_rates(0, prefac)

    overload_operator_kinematic_numba(
        block_structure.n_nodes,
        prefac,
        block_structure.position_collection,
        block_structure.director_collection,
        block_structure.velocity_collection,
        block_structure.omega_collection,
    )

    assert_allclose(correct_position,
                    block_structure.position_collection,
                    atol=Tolerance.atol())
    assert_allclose(correct_director,
                    block_structure.director_collection,
                    atol=Tolerance.atol())
Beispiel #10
0
def test_get_rotation_matrix_correct_rotation_about_y(ycomp, dt):
    vector_collection = np.array([0.0, ycomp, 0.0]).reshape(-1, 1)
    test_rot_mat = _get_rotation_matrix(dt, vector_collection)
    test_theta = ycomp * dt
    # Transpose for similar reasons mentioned before
    correct_rot_mat = np.array([
        [np.cos(test_theta), 0.0, -np.sin(test_theta)],
        [0.0, 1.0, 0.0],
        [np.sin(test_theta), 0.0, np.cos(test_theta)],
    ]).reshape(3, 3, 1)

    assert test_rot_mat.shape == (3, 3, 1)
    assert_allclose(test_rot_mat, correct_rot_mat, atol=Tolerance.atol())
Beispiel #11
0
def test_get_rotation_matrix_gives_orthonormal_matrices():
    dim = 3
    blocksize = 16
    dt = np.random.random_sample()
    rot_mat = _get_rotation_matrix(dt, np.random.randn(dim, blocksize))

    r_rt = np.einsum("ijk,ljk->ilk", rot_mat, rot_mat)
    rt_r = np.einsum("jik,jlk->ilk", rot_mat, rot_mat)

    test_mat = np.array([np.eye(dim) for _ in range(blocksize)]).T
    # We can't get there fully, but 1e-15 suffices in precision
    assert_allclose(r_rt, test_mat, atol=Tolerance.atol())
    assert_allclose(rt_r, test_mat, atol=Tolerance.atol())
Beispiel #12
0
def test_get_rotation_matrix_correctness_in_three_dimensions():
    # A rotation of 120 degrees about x=y=z gives
    # the permutation matrix P
    # {\begin{bmatrix}0&0&1\\1&0&0\\0&1&0\end{bmatrix}}
    # Basically x becomes y, y becomes z, z becomes x
    # For our case then (with directors aligned on the rows,
    # rather than columns)
    # we have
    # {\begin{bmatrix}0&1&0\\0&0&1\\1&0&0\end{bmatrix}}
    vector_collection = np.array([1.0, 1.0, 1.0]) / np.sqrt(3.0)
    vector_collection = vector_collection.reshape(-1, 1)
    theta = np.deg2rad(120.0)
    test_rot_mat = _get_rotation_matrix(theta, vector_collection)
    # previous correct matrix
    # correct_rot_mat = np.roll(np.eye(3), -1, axis=1).reshape(3, 3, 1)
    correct_rot_mat = np.roll(np.eye(3), 1, axis=1).reshape(3, 3, 1)

    assert_allclose(test_rot_mat, correct_rot_mat, atol=Tolerance.atol())
Beispiel #13
0
def test_get_rotation_matrix_correct_rotation_about_z(zcomp, dt):
    vector_collection = np.array([0.0, 0.0, zcomp]).reshape(-1, 1)
    test_rot_mat = _get_rotation_matrix(dt, vector_collection)
    test_theta = zcomp * dt
    # Notice that the correct_rot_mat seems to be a transpose
    # ie if you take a vector v, and do Rv, it seems to do a
    # rotation be -test_theta.
    # The catch in this case is that our directors (d_1, d_2, d_3)
    # are all aligned row-wise rather than columnwise. Thus we need
    # to multiply them by a R.transpose, which is equivalent to
    # multiplying by a R(-theta).
    correct_rot_mat = np.array([
        [np.cos(test_theta), np.sin(test_theta), 0.0],
        [-np.sin(test_theta), np.cos(test_theta), 0.0],
        [0.0, 0.0, 1.0],
    ]).reshape(3, 3, 1)

    assert test_rot_mat.shape == (3, 3, 1)
    assert_allclose(test_rot_mat, correct_rot_mat, atol=Tolerance.atol())
Beispiel #14
0
 def get_linear_state_transition_operator(self, time, dt):
     return _get_rotation_matrix(dt, self.omega)
Beispiel #15
0
 def analytical_solution(self, time):
     # http://scipp.ucsc.edu/~haber/ph5B/sho09.pdf
     # return _batch_matmul(self._state, _get_rotation_matrix(time, self.omega))
     return np.einsum("ijk,ljk->ilk", self.initial_value,
                      _get_rotation_matrix(time, self.omega))
Beispiel #16
0
    def __iadd__(self, scaled_deriv_array):
        """ overloaded += operator

        The add for directors is customized to reflect Rodrigues' rotation
        formula.

        Parameters
        ----------
        scaled_deriv_array : numpy.ndarray containing dt * (v, ω, dv/dt, dω/dt)
        ,as returned from _DerivativeState's __mul__ method

        Returns
        -------
        self : _State with inplace modified data

        """
        # x += v*dt
        self.position_collection += scaled_deriv_array[..., :self.n_nodes]
        # TODO : Verify the math in this note
        r"""
        Developer Note
        --------------
        Here the overloaded `+=` operator is exploited to perform
        matrix multiplication for the directors, which is counter-
        intutive at first. While this provides a stable interface
        to interact the rod states with the timesteppers and the
        rest of the world, the reasons behind including it here also has
        a depper mathematical significance.

        Firstly, position lies in the vector space corresponding to R^{3}
        and update is done this space (with the + and * operators defined
        as usual), hence the `+=` operator (or `__iadd__`) is reflected
        as `+=` operator in the position update (line 163 above).

        For directors rather, which lie in a restricteed R^{3} \otimes
        R^{3} tensorial space, the space with Q^T.Q = Q.Q^T = I, the +
        operator can be thought of as an equivalent `*=` update for a
        'exponential' multiplication with a rotation matrix (e^{At}).
        . This does not correspond to the position update. However, if
        we view this in a logarithmic space the `*=` becomse the '+='
        operator once again! After performing this `+=` operation, we
        bring it back into its original space using the exponential
        operator. So we are still indirectly doing the '+='
        update.

        To avoid all this hassle with the operators and spaces, we simply define
        '+=' or '__iadd__' in the case of directors as an equivalent
        '*=' (matrix multiply) with the RHS below.
        """
        # TODO Q *= exp(w*dt) , whats' the formua again?
        # TODO the scale factor 1.0 does not seem to be necessary, although
        # we perform more work in the present framework (muliply dt to entire vector, then take
        # norm) rather than vector norm then multiple by dt (1/3 operation costs)
        # TODO optimize (somehow) extra copy away : if we don't make a copy
        # its even more slower, maybe due to aliasing effects
        np.einsum(
            "ijk,jlk->ilk",
            _get_rotation_matrix(
                1.0, scaled_deriv_array[...,
                                        self.n_nodes:self.n_kinematic_rates]),
            self.director_collection.copy(),
            out=self.director_collection,
        )
        # (v,ω) += (dv/dt, dω/dt)*dt
        self.kinematic_rate_collection += scaled_deriv_array[
            ..., self.n_kinematic_rates:]
        return self