Search code examples
pythonvector

derotation algorithm in 2d space


I have an array of coordinates in x and y, relative to the movement of an object in a constantly rotating circular environment (10 rpm). How can I disentangle between the movement of the object from that of the environment?

I tried polar coordinates, speed and movement vectors, and I get results, but I'd like to know if someone knows the right way to go. Mainly, I derotate every point according to the distance from the center of rotation, converting to polar coordinates, creating an array of derotated positions. Then I calculate the movement vector between each original_points[idx] and derotated_points[idx+1]. Then, I take the first original point as the starting point for a new array, and to that I add the first vector, storing the new location. Which will be the starting point for adding the second vector, and so on...

Seems reasonable but I'd like to know if there are other methods.


Solution

  • This is a really nice area and a good question about matrix transformations. If you wish to know about this, do read this course: https://graphics.stanford.edu/courses/cs248-01/

    Now, Let's start by creating some sample data:

    import numpy as np
    import matplotlib.pyplot as plt
    import pandas as pd
    
    rpm = 10  
    omega = rpm * 2 * np.pi / 60  
    time = np.linspace(0, 60, 60)  
    speed = 0.1  
    
    x_linear = speed * time
    y_linear = np.zeros_like(time)
    
    x_rotated = x_linear * np.cos(omega * time) - y_linear * np.sin(omega * time)
    y_rotated = x_linear * np.sin(omega * time) + y_linear * np.cos(omega * time)
    
    df = pd.DataFrame({
        'time': time,
        'x': x_rotated,
        'y': y_rotated
    })
    
    
    print(df)
    

    Here, I assume that rpm = 10, the angular velocity, in rad(s, is given as omega = rpm * 2 * np.pi / 60 and that the speed of the obejct is speed = 0.1 units/sec.

    This gives you

             time         x             y
    0    0.000000  0.000000  0.000000e+00
    1    1.016949  0.049276  8.895896e-02
    2    2.033898 -0.107882  1.724206e-01
    3    3.050847 -0.304652 -1.623727e-02
    4    4.067797 -0.177888 -3.658219e-01
    5    5.084746  0.292265 -4.160862e-01
    6    6.101695  0.606713  6.485704e-02
    7    7.118644  0.276790  6.558492e-01
    8    8.135593 -0.502393  6.399064e-01
    9    9.152542 -0.903602 -1.455835e-01
    10  10.169492 -0.344989 -9.566443e-01
    11  11.186441  0.736640 -8.418588e-01
    12  12.203390  1.192763  2.579585e-01
    13  13.220339  0.381661  1.265744e+00
    ...
    56  56.949153 -5.686844  3.030958e-01
    57  57.966102 -3.074643 -4.913986e+00
    58  58.983051  2.858029 -5.159620e+00
    59  60.000000  6.000000 -5.732833e-14
    
    

    Now, to derotate (transform the above matrix); you need to define the following function (for your reference, Gilbert Strand's Linear Algebra and Its Applications is a perfect book.

    def derotate_coordinates(df, omega):
        df['x_derotated'] = df['x']*np.cos(-omega*df['time']) - df['y']*np.sin(-omega*df['time'])
        df['y_derotated'] = df['x']*np.sin(-omega*df['time']) + df['y']*np.cos(-omega*df['time'])
        return df
    

    which applied

    df_derotated = derotate_coordinates(df.copy(), omega)
    
    print(df_derotated)
    

    will give you

             time         x             y  x_derotated   y_derotated
    0    0.000000  0.000000  0.000000e+00     0.000000  0.000000e+00
    1    1.016949  0.049276  8.895896e-02     0.101695  0.000000e+00
    2    2.033898 -0.107882  1.724206e-01     0.203390 -1.387779e-17
    3    3.050847 -0.304652 -1.623727e-02     0.305085  0.000000e+00
    4    4.067797 -0.177888 -3.658219e-01     0.406780  0.000000e+00
    5    5.084746  0.292265 -4.160862e-01     0.508475  0.000000e+00
    6    6.101695  0.606713  6.485704e-02     0.610169  0.000000e+00
    7    7.118644  0.276790  6.558492e-01     0.711864  0.000000e+00
    8    8.135593 -0.502393  6.399064e-01     0.813559  0.000000e+00
    9    9.152542 -0.903602 -1.455835e-01     0.915254  2.775558e-17
    10  10.169492 -0.344989 -9.566443e-01     1.016949 -5.551115e-17
    11  11.186441  0.736640 -8.418588e-01     1.118644  0.000000e+00
    12  12.203390  1.192763  2.579585e-01     1.220339  0.000000e+00
    13  13.220339  0.381661  1.265744e+00     1.322034 -5.551115e-17
    ...
    56  56.949153 -5.686844  3.030958e-01     5.694915  0.000000e+00
    57  57.966102 -3.074643 -4.913986e+00     5.796610  0.000000e+00
    58  58.983051  2.858029 -5.159620e+00     5.898305 -4.440892e-16
    59  60.000000  6.000000 -5.732833e-14     6.000000  0.000000e+00
    

    If you want to visualize this

    plt.figure(figsize=(12, 6))
    
    plt.subplot(1, 2, 1)
    plt.plot(df['x'], df['y'], 'ro-')
    plt.title('Path in Rotating Frame')
    plt.xlabel('X')
    plt.ylabel('Y')
    plt.axis('equal')
    
    plt.subplot(1, 2, 2)
    plt.plot(df_derotated['x_derotated'], df_derotated['y_derotated'], 'bo-')
    plt.title('Path After Derotation')
    plt.xlabel('X')
    plt.ylabel('Y')
    plt.axis('equal')
    
    plt.show()
    

    which gives

    enter image description here

    Update: In 3D, just because this is a fascinating topic

    In this case, let's define a rotating spiral and work in polar coordinates:

    import numpy as np
    import matplotlib.pyplot as plt
    from mpl_toolkits.mplot3d import Axes3D
    
    t = np.linspace(0, 4 * np.pi, 100)
    x = t * np.sin(t)
    y = t * np.cos(t)
    z = t
    
    original_points = np.vstack([x, y, z])
    
    def rotation_matrix_y(theta):
        cos_theta, sin_theta = np.cos(theta), np.sin(theta)
        return np.array([
            [cos_theta, 0, sin_theta],
            [0, 1, 0],
            [-sin_theta, 0, cos_theta]
        ])
    
    def rotation_matrix_z(theta):
        cos_theta, sin_theta = np.cos(theta), np.sin(theta)
        return np.array([
            [cos_theta, -sin_theta, 0],
            [sin_theta, cos_theta, 0],
            [0, 0, 1]
        ])
    
    theta_z = np.radians(45)  
    theta_y = np.radians(30)  
    rot_matrix_z = rotation_matrix_z(theta_z)
    rot_matrix_y = rotation_matrix_y(theta_y)
    
    combined_rot_matrix = rot_matrix_y @ rot_matrix_z
    rotated_points = combined_rot_matrix @ original_points
    
    
    inverse_combined_rot_matrix = np.transpose(combined_rot_matrix)
    
    derotated_points = inverse_combined_rot_matrix @ rotated_points
    
    fig = plt.figure(figsize=(18, 6))
    ax1 = fig.add_subplot(131, projection='3d')
    ax1.plot(*original_points, 'r')
    ax1.set_title('Original Spiral')
    ax1.set_xlim([-20, 20])
    ax1.set_ylim([-20, 20])
    ax1.set_zlim([0, 40])
    
    ax2 = fig.add_subplot(132, projection='3d')
    ax2.plot(*rotated_points, 'b')
    ax2.set_title('Rotated Spiral')
    ax2.set_xlim([-20, 20])
    ax2.set_ylim([-20, 20])
    ax2.set_zlim([0, 40])
    
    ax3 = fig.add_subplot(133, projection='3d')
    ax3.plot(*derotated_points, 'g')
    ax3.set_title('Derotated Spiral')
    ax3.set_xlim([-20, 20])
    ax3.set_ylim([-20, 20])
    ax3.set_zlim([0, 40])
    
    plt.show()
    
    

    which gives you

    enter image description here