| """Geometric action policies for clean-image classical baselines.""" |
|
|
| from __future__ import annotations |
|
|
| import numpy as np |
|
|
|
|
| def goal_action( |
| pose: np.ndarray, |
| goal: np.ndarray, |
| action_dim: int, |
| forward_gain: float, |
| turn_gain: float, |
| drift: np.ndarray | None = None, |
| drift_gain: float = 0.0, |
| ) -> np.ndarray: |
| delta = np.asarray(goal, dtype=np.float32) - pose[:2] |
| if drift is not None: |
| delta = delta - float(drift_gain) * np.asarray(drift, dtype=np.float32) |
| theta = float(np.arctan2(pose[3], pose[2])) |
| target = float(np.arctan2(delta[1], delta[0])) |
| err = float(np.arctan2(np.sin(target - theta), np.cos(target - theta))) |
| forward = float(forward_gain * max(0.0, np.cos(err))) |
| turn = float(-turn_gain * np.sin(err)) |
| if action_dim == 2: |
| return np.array([forward + turn, forward - turn], dtype=np.float32).clip(-1.0, 1.0) |
| desired = delta / max(float(np.linalg.norm(delta)), 1e-6) |
| rot_world_to_body = np.array( |
| [[np.cos(theta), np.sin(theta)], [-np.sin(theta), np.cos(theta)]], |
| dtype=np.float32, |
| ) |
| desired_body = rot_world_to_body @ desired |
| phis = np.array([0.0, 2.0 * np.pi / 3.0, 4.0 * np.pi / 3.0], dtype=np.float32) |
| dirs = np.stack([-np.sin(phis), np.cos(phis)], axis=0) |
| translation = np.linalg.lstsq(dirs, desired_body, rcond=None)[0].astype(np.float32) |
| action = forward_gain * translation + turn * np.ones(3, dtype=np.float32) |
| return action.clip(-1.0, 1.0) |
|
|