forest_navigating_uav/src/fastsim_forest_nav/fastsim_forest_nav/dynamics/controls.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
"""Provide low-level control helpers for acceleration-limited commands."""

from __future__ import annotations

import numpy as np


def accel_limit_velocity(
    desired: float,
    current: float,
    accel_max: float,
    dt: float,
    decel_max: float | None = None,
) -> tuple[float, bool]:
    """Apply acceleration and deceleration limits to a target velocity."""
    if accel_max <= 0.0:
        return float(desired), False

    effective_decel = (
        float(decel_max) if decel_max is not None and float(decel_max) > 0.0 else float(accel_max)
    )
    accel_step = float(accel_max) * float(dt)
    decel_step = float(effective_decel) * float(dt)

    delta = float(desired) - float(current)
    if delta >= 0.0:
        clipped = delta > accel_step
        applied = float(current) + float(np.clip(delta, 0.0, accel_step))
    else:
        clipped = abs(delta) > decel_step
        applied = float(current) + float(np.clip(delta, -decel_step, 0.0))

    return applied, clipped


def map_normalized_accel(action_component: float, accel_max: float, decel_max: float) -> float:
    """Map a normalized action component in [-1, 1] to signed acceleration."""
    u = float(np.clip(action_component, -1.0, 1.0))
    pos_limit = float(max(accel_max, 0.0))
    neg_limit = float(decel_max) if float(decel_max) > 0.0 else pos_limit
    if u >= 0.0:
        return u * pos_limit
    return u * neg_limit


def approach_speed_cap(gap: float, decel_max: float, dt: float) -> float:
    """Compute the maximum speed that can still stop within the given gap."""
    g = float(max(gap, 0.0))
    a = float(max(decel_max, 1e-6))
    d = float(max(dt, 1e-6))
    return float(max(0.0, -a * d + np.sqrt((a * d) ** 2 + 2.0 * a * g)))