Rename multi-segment functions to two-concept names; polish docstrings
Naming pass: rename functions whose third+ segment is redundant or implementation-detail, sticking to the codebase's preferred ``noun_verb`` / ``verb_noun`` two-concept idiom. Renames are atomic across definitions, callers, and tests. is_penned_position → is_penned modulate_speed_near_sheep → modulate_speed mecanum_kinematics_step → mecanum_step policy_forward_mean → forward_mean Two-concept patterns like ``velocity_to_wheels`` / ``detections_from_scan`` / ``make_strombom_predictor`` are left alone — they're idiomatic converters / factories that read as a single concept, and the longer form aids grep-ability. Docstring polish: * ``herding/config.py`` header drops the "previously lived as a module-level literal" historical framing — we ship as a single thing, so the refactor anecdote no longer earns its keep. The usage examples now mention both ``HERDING_WEBOTS`` and ``HERDING_MEC_WEBOTS`` presets. 126 pytest cases still pass. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
+16
-18
@@ -1,32 +1,30 @@
|
||||
"""Central configuration dataclasses for the herding simulation.
|
||||
|
||||
Every tunable constant that previously lived as a module-level literal in
|
||||
perception/lidar_sim.py, perception/lidar_perception.py,
|
||||
perception/sheep_tracker.py, world/geometry.py, or training/herding_env.py
|
||||
is now represented here as a field with its original default value.
|
||||
Every tunable parameter lives here as a frozen dataclass field — LiDAR
|
||||
spec, cluster detection thresholds, tracker gates, robot kinematics,
|
||||
and domain-randomisation knobs — composed into :class:`HerdingConfig`.
|
||||
|
||||
Usage — use the module defaults unchanged::
|
||||
Usage — accept the defaults::
|
||||
|
||||
env = HerdingEnv() # same behaviour as before
|
||||
env = HerdingEnv()
|
||||
|
||||
Override a subset of parameters::
|
||||
Override a subset::
|
||||
|
||||
from herding.config import HerdingConfig, TrackerConfig
|
||||
cfg = HerdingConfig(tracker=TrackerConfig(forget_steps=60))
|
||||
env = HerdingEnv(herding_cfg=cfg)
|
||||
|
||||
Use a named preset for Webots-matched training::
|
||||
Use a named preset::
|
||||
|
||||
from herding.config import HERDING_WEBOTS
|
||||
env = HerdingEnv(herding_cfg=HERDING_WEBOTS)
|
||||
env = HerdingEnv(herding_cfg=HERDING_WEBOTS) # 140° FOV
|
||||
env = HerdingEnv(herding_cfg=HERDING_MEC_WEBOTS) # + mecanum slip
|
||||
|
||||
Design notes
|
||||
------------
|
||||
* All dataclasses are frozen — instances are immutable after construction.
|
||||
* This module must not import from other ``herding.*`` packages to avoid
|
||||
import cycles. Field-geometry constants (pen coordinates, field size)
|
||||
stay in ``herding.world.geometry`` because they depend on the world
|
||||
variant selected at runtime via ``HERDING_WORLD``.
|
||||
* All dataclasses are frozen so instances are immutable after construction.
|
||||
* This module must not import from other ``herding.*`` packages —
|
||||
field-geometry constants live in ``herding.world.geometry`` because
|
||||
they depend on the world variant selected at runtime via
|
||||
``HERDING_WORLD``, which would create an import cycle here.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -257,7 +255,7 @@ class RobotConfig:
|
||||
|
||||
``1.0`` (default) = perfect mecanum kinematics. ``0.4`` matches the
|
||||
Webots roller-hinge mecanum proto calibration (62% slip on strafe,
|
||||
11% on forward). Used by ``mecanum_kinematics_step`` only — has no
|
||||
11% on forward). Used by ``mecanum_step`` only — has no
|
||||
effect on differential drive.
|
||||
"""
|
||||
|
||||
@@ -266,7 +264,7 @@ class RobotConfig:
|
||||
|
||||
``0.0`` (default) = no bleed. ``-0.28`` matches the Webots proto's
|
||||
consistent backward push under strafe commands. Used by
|
||||
``mecanum_kinematics_step`` only.
|
||||
``mecanum_step`` only.
|
||||
"""
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
|
||||
@@ -12,7 +12,7 @@ exploration behaviours:
|
||||
beyond the 12 m LiDAR range).
|
||||
|
||||
When the tracker has detections the base teacher's action is used,
|
||||
post-processed by ``modulate_speed_near_sheep`` so the dog doesn't
|
||||
post-processed by ``modulate_speed`` so the dog doesn't
|
||||
charge the flock.
|
||||
"""
|
||||
|
||||
@@ -20,7 +20,7 @@ from __future__ import annotations
|
||||
|
||||
import math
|
||||
|
||||
from herding.control.modulation import modulate_speed_near_sheep
|
||||
from herding.control.modulation import modulate_speed
|
||||
|
||||
|
||||
INITIAL_SCAN_STEPS = 80 # ≈1.3 s — covers one full rotation
|
||||
@@ -117,6 +117,6 @@ class ActiveScanTeacher:
|
||||
else:
|
||||
vx, vy, mode = result
|
||||
omega = 0.0
|
||||
vx, vy = modulate_speed_near_sheep(vx, vy, dog_xy, sheep_positions)
|
||||
vx, vy = modulate_speed(vx, vy, dog_xy, sheep_positions)
|
||||
self.last_action = (vx, vy)
|
||||
return vx, vy, omega, mode
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""Shared action post-processing.
|
||||
|
||||
Every dog mode routes its action through ``modulate_speed_near_sheep``
|
||||
Every dog mode routes its action through ``modulate_speed``
|
||||
so the magnitude is reduced near sheep — direction (intent) is
|
||||
preserved.
|
||||
"""
|
||||
@@ -14,7 +14,7 @@ SLOW_NEAR_SHEEP = 2.5 # m — distance below which action norm is scaled down
|
||||
MIN_SPEED = 0.30 # action norm at zero distance
|
||||
|
||||
|
||||
def modulate_speed_near_sheep(
|
||||
def modulate_speed(
|
||||
vx: float, vy: float,
|
||||
dog_xy: tuple[float, float],
|
||||
sheep_positions,
|
||||
|
||||
@@ -15,7 +15,7 @@ Three-stage greedy nearest-neighbour data association:
|
||||
extrapolated for up to ``PREDICT_STEPS`` frames, then falls back to
|
||||
last-seen static memory until ``FORGET_STEPS`` deletes it.
|
||||
3. **Pen latching**. A track whose estimated position crosses the gate
|
||||
plane south of ``is_penned_position`` is marked penned, excluded
|
||||
plane south of ``is_penned`` is marked penned, excluded
|
||||
from ``get_positions``, and kept indefinitely.
|
||||
|
||||
Output of :meth:`SheepTracker.get_positions` is ``{name: (x, y)}`` —
|
||||
@@ -31,7 +31,7 @@ from typing import TYPE_CHECKING
|
||||
if TYPE_CHECKING:
|
||||
from herding.config import TrackerConfig
|
||||
|
||||
from herding.world.geometry import MAX_SHEEP, in_pen, is_penned_position
|
||||
from herding.world.geometry import MAX_SHEEP, in_pen, is_penned
|
||||
|
||||
|
||||
GATE_M = 2.5 # m — primary NN gate (recently observed tracks)
|
||||
@@ -356,10 +356,10 @@ class SheepTracker:
|
||||
at y ≈ -15) from being permanently latched as penned tracks.
|
||||
"""
|
||||
from herding.world.geometry import GATE_Y
|
||||
# Apply depth threshold to both in_pen and is_penned_position so
|
||||
# Apply depth threshold to both in_pen and is_penned so
|
||||
# that any position in the gate column must clear GATE_Y - depth.
|
||||
threshold = GATE_Y - self._pen_latch_depth
|
||||
return (in_pen(x, y) or is_penned_position(x, y)) and y <= threshold
|
||||
return (in_pen(x, y) or is_penned(x, y)) and y <= threshold
|
||||
|
||||
def get_positions(self, min_freshness: int | None = None) -> dict[str, tuple[float, float]]:
|
||||
"""Promoted (non-candidate, non-penned) tracks as ``{name: (x, y)}``.
|
||||
|
||||
@@ -3,7 +3,7 @@ controllers.
|
||||
|
||||
First-order rigid-body model — no slip, wheel-accel limits, or contact
|
||||
forces by default. Pass ``slip_std`` and an ``rng`` to
|
||||
:func:`kinematics_step` / :func:`mecanum_kinematics_step` to add
|
||||
:func:`kinematics_step` / :func:`mecanum_step` to add
|
||||
per-wheel Gaussian speed noise for domain randomisation.
|
||||
"""
|
||||
|
||||
@@ -80,7 +80,7 @@ def heading_speed_to_wheels(heading, speed_motor, h, max_wheel_omega,
|
||||
# Mecanum (4-wheel omnidirectional) kinematics
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def mecanum_kinematics_step(x, y, h, w_fl, w_fr, w_rl, w_rr,
|
||||
def mecanum_step(x, y, h, w_fl, w_fr, w_rl, w_rr,
|
||||
wheel_radius, lx, ly, dt,
|
||||
slip_std: float = 0.0,
|
||||
rng: Optional[np.random.Generator] = None,
|
||||
|
||||
@@ -171,7 +171,7 @@ def in_gate_corridor(x: float, y: float, margin: float = 0.0) -> bool:
|
||||
and PEN_Y[0] - margin <= y <= GATE_Y + margin)
|
||||
|
||||
|
||||
def is_penned_position(x: float, y: float, latch_margin: float = 0.2) -> bool:
|
||||
def is_penned(x: float, y: float, latch_margin: float = 0.2) -> bool:
|
||||
"""True iff (x, y) is in the gate column and south of the gate line."""
|
||||
return (GATE_X[0] - latch_margin <= x <= GATE_X[1] + latch_margin
|
||||
and y <= GATE_Y)
|
||||
|
||||
Reference in New Issue
Block a user