robosim.physics.motion

motion.py - Implements the motion dynamics. We support from simple 'level 1' (velocity only) to higher level ones. All the functions here receive the current Physics state and motion input (level-dependent) and return velocities (v, w)

 1"""
 2motion.py - Implements the motion dynamics. We support from simple 'level 1' (velocity only) to higher level ones.
 3All the functions here receive the current Physics state and motion input (level-dependent) and return velocities (v, w)
 4"""
 5import random
 6import numpy as np
 7from microecs import Field
 8from robosim.utils import renormalize_rotation_matrix, btrexp, bskewa
 9from robosim.constants import EPS
10
11Arr = np.ndarray | Field
12
13def integrate_velocity_into_pose(pose: Arr, velocity: Arr, dt: float) -> np.ndarray:
14    """integrates pose[t+1] = pose[t] + v[t] * dt with v=(v, w) twist. Uses rodrigues formula (batched)."""
15    pose = pose.numpy() if isinstance(pose, Field) else pose                 # (N, 4, 4)
16    velocity = velocity.numpy() if isinstance(velocity, Field) else velocity # (N, 6) [m/s]
17    res = np.float32(pose) @ btrexp(bskewa(velocity * dt)).astype("float32") # (N, 4, 4) [m]
18    if random.random() >= 0.99: # renormalize every now and then to ensure rotation matrix is still reliable
19        for i in range(len(pose)):
20            res[i, 0:3, 0:3] = renormalize_rotation_matrix(res[i, 0:3, 0:3])
21    return res
22
23def motion_level1(motion_input: Arr, max_velocities: Arr) -> Arr:
24    """level 1 physics: velocity is the motion input. Returns it after clamping"""
25    velocity = np.clip(motion_input, -1, 1) * max_velocities              # [m/s]
26    return velocity
27
28def motion_level2(motion_input: Arr, dt: float, current_velocity: Arr,
29                  max_accelerations: Arr, drag_coefficient: Arr) -> tuple[Arr, Arr]:
30    """
31    level 2 physics: integrates acceleration (motion input) + drag (ct) into the current velocity (A = (a[t] - k * v[t])
32    - v[t+1] = v[t] + dt * (a[t] - k * v[t])
33    """
34    clamped_acceleration = np.clip(motion_input, -1, 1) * max_accelerations
35    drag = current_velocity * drag_coefficient                            # [m/s**2]; (N, 6) * (N, )
36    acceleration = clamped_acceleration - drag                            # [m/s**2]; (N, 6) - (N, 6)
37    velocity = current_velocity + dt * acceleration                       # [m/s]
38    # clamp off super small velocities so they don't go "forever" in the tests
39    velocity = np.where(np.abs(velocity) < EPS, 0, velocity)              # [m/s]
40    return velocity, acceleration
Arr = numpy.ndarray | microecs.query_result.Field
def integrate_velocity_into_pose( pose: numpy.ndarray | microecs.query_result.Field, velocity: numpy.ndarray | microecs.query_result.Field, dt: float) -> numpy.ndarray:
14def integrate_velocity_into_pose(pose: Arr, velocity: Arr, dt: float) -> np.ndarray:
15    """integrates pose[t+1] = pose[t] + v[t] * dt with v=(v, w) twist. Uses rodrigues formula (batched)."""
16    pose = pose.numpy() if isinstance(pose, Field) else pose                 # (N, 4, 4)
17    velocity = velocity.numpy() if isinstance(velocity, Field) else velocity # (N, 6) [m/s]
18    res = np.float32(pose) @ btrexp(bskewa(velocity * dt)).astype("float32") # (N, 4, 4) [m]
19    if random.random() >= 0.99: # renormalize every now and then to ensure rotation matrix is still reliable
20        for i in range(len(pose)):
21            res[i, 0:3, 0:3] = renormalize_rotation_matrix(res[i, 0:3, 0:3])
22    return res

integrates pose[t+1] = pose[t] + v[t] * dt with v=(v, w) twist. Uses rodrigues formula (batched).

def motion_level1( motion_input: numpy.ndarray | microecs.query_result.Field, max_velocities: numpy.ndarray | microecs.query_result.Field) -> numpy.ndarray | microecs.query_result.Field:
24def motion_level1(motion_input: Arr, max_velocities: Arr) -> Arr:
25    """level 1 physics: velocity is the motion input. Returns it after clamping"""
26    velocity = np.clip(motion_input, -1, 1) * max_velocities              # [m/s]
27    return velocity

level 1 physics: velocity is the motion input. Returns it after clamping

def motion_level2( motion_input: numpy.ndarray | microecs.query_result.Field, dt: float, current_velocity: numpy.ndarray | microecs.query_result.Field, max_accelerations: numpy.ndarray | microecs.query_result.Field, drag_coefficient: numpy.ndarray | microecs.query_result.Field) -> tuple[numpy.ndarray | microecs.query_result.Field, numpy.ndarray | microecs.query_result.Field]:
29def motion_level2(motion_input: Arr, dt: float, current_velocity: Arr,
30                  max_accelerations: Arr, drag_coefficient: Arr) -> tuple[Arr, Arr]:
31    """
32    level 2 physics: integrates acceleration (motion input) + drag (ct) into the current velocity (A = (a[t] - k * v[t])
33    - v[t+1] = v[t] + dt * (a[t] - k * v[t])
34    """
35    clamped_acceleration = np.clip(motion_input, -1, 1) * max_accelerations
36    drag = current_velocity * drag_coefficient                            # [m/s**2]; (N, 6) * (N, )
37    acceleration = clamped_acceleration - drag                            # [m/s**2]; (N, 6) - (N, 6)
38    velocity = current_velocity + dt * acceleration                       # [m/s]
39    # clamp off super small velocities so they don't go "forever" in the tests
40    velocity = np.where(np.abs(velocity) < EPS, 0, velocity)              # [m/s]
41    return velocity, acceleration

level 2 physics: integrates acceleration (motion input) + drag (ct) into the current velocity (A = (a[t] - k * v[t])

  • v[t+1] = v[t] + dt * (a[t] - k * v[t])