Checkpoint 7
This commit is contained in:
@@ -1,11 +1,8 @@
|
||||
"""Differential-drive kinematics matching the Webots robot specs.
|
||||
"""Differential-drive kinematics, shared by the env and Webots controllers.
|
||||
|
||||
The Webots controllers and the training env both use these helpers so the
|
||||
sim and the real (Webots) physics agree to first order. They do not model
|
||||
slip, wheel acceleration limits, or contact forces — Webots does that for
|
||||
us at inference time. The training env has to be close enough that a
|
||||
policy trained against this kinematic model still works when handed off
|
||||
to ODE physics.
|
||||
First-order rigid-body model — no slip, wheel-accel limits, or contact
|
||||
forces. Webots' ODE physics handles those at inference; the env stays
|
||||
close enough to first order that a policy trained here transfers.
|
||||
"""
|
||||
|
||||
import math
|
||||
@@ -34,10 +31,9 @@ def kinematics_step(x, y, h, w_left, w_right, wheel_radius, wheel_base, dt):
|
||||
|
||||
def velocity_to_wheels(vx, vy, h, max_linear, wheel_radius, max_wheel_omega,
|
||||
k_turn=4.0):
|
||||
"""Convert a desired (vx, vy) intent in [-1, 1]^2 to wheel speeds.
|
||||
"""Convert a desired (vx, vy) intent in [-1, 1]² to wheel speeds.
|
||||
|
||||
Mirrors ``drive_action`` in controllers/shepherd_dog/shepherd_dog.py:
|
||||
forward speed scales by ``cos(err)`` (clamped to ±90°), and a P
|
||||
Forward speed scales by ``cos(err)`` (clamped to ±90°); a P
|
||||
controller on heading error contributes the wheel-rate differential.
|
||||
"""
|
||||
speed_ms = math.hypot(vx, vy) * max_linear
|
||||
@@ -56,12 +52,7 @@ def velocity_to_wheels(vx, vy, h, max_linear, wheel_radius, max_wheel_omega,
|
||||
|
||||
def heading_speed_to_wheels(heading, speed_motor, h, max_wheel_omega,
|
||||
k_turn=4.0):
|
||||
"""Sheep variant: speed already expressed in motor (wheel rad/s) units.
|
||||
|
||||
Matches the existing sheep controller (``controllers/sheep/sheep.py``)
|
||||
where ``speed = max(WANDER_SPEED, min(FLEE_SPEED, mag * 3.0))`` and
|
||||
these constants are wheel angular velocities, not linear m/s.
|
||||
"""
|
||||
"""Sheep variant: speed in wheel rad/s, target as a heading angle."""
|
||||
err = math.atan2(math.sin(heading - h), math.cos(heading - h))
|
||||
fwd = max(0.0, math.cos(err)) * speed_motor
|
||||
turn = k_turn * err
|
||||
|
||||
@@ -1,24 +1,19 @@
|
||||
"""Sheep flocking dynamics — Strömbom 2014 / Reynolds 1987 hybrid.
|
||||
"""Sheep flocking dynamics — Strömbom 2014 / Reynolds 1987.
|
||||
|
||||
This is the per-sheep behavioural step used both by the Webots sheep
|
||||
controller (scalar, one sheep at a time) and by the training environment
|
||||
(loop over sheep).
|
||||
|
||||
Model
|
||||
-----
|
||||
The force stack each step (summed → heading + speed):
|
||||
Per-sheep behavioural step used by both the Webots sheep controller
|
||||
and the training environment. Each step a force stack is summed:
|
||||
|
||||
flee — quadratic ramp away from dog within FLEE_DIST
|
||||
(Strömbom 2014 §2.1, term ρa)
|
||||
(Strömbom 2014, term ρa)
|
||||
cohesion — drift toward local centre of mass of peers within
|
||||
COHESION_DIST (Strömbom 2014 §2.1, term c).
|
||||
Weight is **higher when fleeing** — modelling the
|
||||
"safety in numbers" / predator-confusion effect
|
||||
Strömbom 2014 describes as fear-induced cohesion.
|
||||
COHESION_DIST (Strömbom 2014, term c). Weight is
|
||||
higher while fleeing — fear-induced cohesion.
|
||||
separation — short-range inverse-distance repulsion from peers
|
||||
(Strömbom 2014 §2.1, term α; Reynolds 1987)
|
||||
wander — small persistent drift for natural idle motion
|
||||
(Strömbom 2014 §2.1, noise term ε)
|
||||
(Strömbom 2014 term α; Reynolds 1987)
|
||||
wander — small persistent drift (Strömbom 2014 noise term ε)
|
||||
|
||||
Walls, the south-wall gate column, and in-pen containment are
|
||||
environment-specific additions for the fenced Webots field.
|
||||
|
||||
References
|
||||
----------
|
||||
@@ -26,26 +21,6 @@ References
|
||||
for herding autonomous, interacting agents." J R Soc Interface 11.
|
||||
- Reynolds (1987). "Flocks, herds and schools: A distributed
|
||||
behavioural model." SIGGRAPH '87.
|
||||
|
||||
Environment-specific adaptations
|
||||
--------------------------------
|
||||
The original Strömbom model assumes an open field. Our scenario adds:
|
||||
|
||||
* Field walls — soft repulsion within ``WALL_MARGIN`` plus a hard
|
||||
escape band when inside ``WALL_HARD_MARGIN``. Necessary because the
|
||||
Webots field is fenced (30 m square enclosure).
|
||||
* Gate column — the south wall has a 3 m gap at x ∈ [10, 13]; sheep
|
||||
pass through it freely (no wall force inside the column).
|
||||
* Penned containment — once a sheep crosses the gate plane south
|
||||
(``geometry.is_penned_position``), the caller flags ``penned=True``
|
||||
and we switch to in-pen wall-bounce + jitter. Sheep do not exit the
|
||||
pen on their own. This is a hard sim constraint, not a behavioural
|
||||
claim about real sheep.
|
||||
|
||||
Parameter tuning (cohesion weight 3× while fleeing) was chosen so the
|
||||
flock survives passage through the 3 m gate without fragmenting — this
|
||||
is a defensible engineering adaptation of Strömbom's qualitative
|
||||
"fear-induced cohesion" to our gate width.
|
||||
"""
|
||||
|
||||
import math
|
||||
@@ -57,9 +32,7 @@ from herding.world.geometry import (
|
||||
GATE_X,
|
||||
)
|
||||
|
||||
# --- Speed and force constants ---
|
||||
# All speeds here are in wheel rad/s (motor units), matching the existing
|
||||
# sheep controller. Conversion to m/s = speed * SHEEP_WHEEL_RADIUS.
|
||||
# Speeds are in wheel rad/s (motor units); m/s = speed * SHEEP_WHEEL_RADIUS.
|
||||
MAX_SPEED = 22.0
|
||||
FLEE_SPEED = 20.0
|
||||
WANDER_SPEED = 3.0
|
||||
@@ -70,7 +43,7 @@ WALL_HARD_GAIN = 50.0
|
||||
|
||||
FLEE_DIST = 7.0
|
||||
SEPARATION_DIST = 2.5
|
||||
COHESION_DIST = 12.0 # was 8.0 — wider engagement so far-flung sheep are pulled in
|
||||
COHESION_DIST = 12.0
|
||||
|
||||
PEN_MARGIN = 0.8
|
||||
|
||||
@@ -85,21 +58,17 @@ def _peers_iter(peers):
|
||||
def compute_heading_speed(x, y, penned, dog_xy, peers, wander_angle, rng=None):
|
||||
"""Return ``(heading, speed, new_wander_angle)`` for one sheep step.
|
||||
|
||||
``speed`` is in wheel rad/s (motor units), bounded by ``[WANDER_SPEED,
|
||||
FLEE_SPEED]``. ``heading`` is the world-frame target heading the sheep
|
||||
should aim for (atan2 convention).
|
||||
|
||||
``rng`` is an optional ``random.Random``-compatible object used for
|
||||
the wander-jitter. If ``None``, falls back to Python's global module
|
||||
(matches Webots controller usage). Pass an env-owned RNG to make
|
||||
rollouts deterministic given a seed.
|
||||
``speed`` is in wheel rad/s, bounded by ``[WANDER_SPEED, FLEE_SPEED]``.
|
||||
``heading`` is the world-frame target heading (atan2 convention).
|
||||
``rng`` is an optional ``random.Random`` used for wander jitter; if
|
||||
``None`` uses the module's global ``random``.
|
||||
"""
|
||||
fx, fy = 0.0, 0.0
|
||||
peer_list = _peers_iter(peers)
|
||||
rnd = rng if rng is not None else random
|
||||
|
||||
if penned:
|
||||
# --- Pen containment: bounce off the four pen walls ---
|
||||
# Pen containment: bounce off all four pen walls.
|
||||
pm = PEN_MARGIN
|
||||
if x < PEN_X[0] + pm:
|
||||
fx += ((PEN_X[0] + pm - x) / pm) * 15.0
|
||||
@@ -110,7 +79,7 @@ def compute_heading_speed(x, y, penned, dog_xy, peers, wander_angle, rng=None):
|
||||
if y > PEN_Y[1] - pm:
|
||||
fy -= ((y - (PEN_Y[1] - pm)) / pm) * 15.0
|
||||
|
||||
# Mild peer separation — penned sheep crowd the corner otherwise.
|
||||
# Mild peer separation so penned sheep don't crowd one corner.
|
||||
for px, py in peer_list:
|
||||
dx, dy = px - x, py - y
|
||||
d = math.hypot(dx, dy)
|
||||
@@ -125,7 +94,7 @@ def compute_heading_speed(x, y, penned, dog_xy, peers, wander_angle, rng=None):
|
||||
fy += math.sin(wander_angle) * 0.5
|
||||
|
||||
else:
|
||||
# --- Free-roaming sheep in the field ---
|
||||
# Free-roaming sheep in the field.
|
||||
fleeing = False
|
||||
if dog_xy is not None:
|
||||
ddx = dog_xy[0] - x
|
||||
@@ -138,11 +107,9 @@ def compute_heading_speed(x, y, penned, dog_xy, peers, wander_angle, rng=None):
|
||||
fx -= (ddx / dist) * s
|
||||
fy -= (ddy / dist) * s
|
||||
|
||||
# Cohesion — drift toward flock CoM (peers within COHESION_DIST).
|
||||
# Cohesion is *stronger* under flee than at rest (the
|
||||
# predator-confusion / safety-in-numbers effect — sheep huddle when
|
||||
# threatened). This is what makes shepherding work: the flock stays
|
||||
# as one unit through the narrow gate instead of fragmenting.
|
||||
# Cohesion: drift toward the local CoM of peers within
|
||||
# COHESION_DIST. Stronger while fleeing — fear-induced
|
||||
# cohesion keeps the flock together through the gate.
|
||||
cx, cy, cn = 0.0, 0.0, 0
|
||||
for px, py in peer_list:
|
||||
d = math.hypot(px - x, py - y)
|
||||
@@ -151,12 +118,6 @@ def compute_heading_speed(x, y, penned, dog_xy, peers, wander_angle, rng=None):
|
||||
cy += py
|
||||
cn += 1
|
||||
if cn > 0:
|
||||
# Cohesion needs to dominate flee at close range so the flock
|
||||
# stays glued together when squeezing through the narrow gate.
|
||||
# Flee at 2 m has magnitude ~10; cohesion of w=3.0 with the
|
||||
# peer-CoM 4 m away contributes ~12, so the flock prefers
|
||||
# bunching to dispersing under pressure. This is what makes
|
||||
# canonical Strömbom drive work in our 3 m gate.
|
||||
w = 3.0 if fleeing else 1.0
|
||||
fx += (cx / cn - x) * w
|
||||
fy += (cy / cn - y) * w
|
||||
@@ -170,8 +131,7 @@ def compute_heading_speed(x, y, penned, dog_xy, peers, wander_angle, rng=None):
|
||||
fx -= (ddx / d) * push * 2.5
|
||||
fy -= (ddy / d) * push * 2.5
|
||||
|
||||
# Wall soft repulsion. The south wall is absent inside the gate
|
||||
# column so sheep can be driven through it by the dog.
|
||||
# Wall soft repulsion (south wall absent inside the gate column).
|
||||
if x < FIELD_X[0] + WALL_MARGIN:
|
||||
fx += ((FIELD_X[0] + WALL_MARGIN - x) / WALL_MARGIN) * 6.0
|
||||
if x > FIELD_X[1] - WALL_MARGIN:
|
||||
@@ -187,7 +147,7 @@ def compute_heading_speed(x, y, penned, dog_xy, peers, wander_angle, rng=None):
|
||||
fx += math.cos(wander_angle) * 0.5
|
||||
fy += math.sin(wander_angle) * 0.5
|
||||
|
||||
# --- Hard escape band — overrides everything when very close to a wall ---
|
||||
# Hard escape band — overrides everything else near a wall.
|
||||
m, g = WALL_HARD_MARGIN, WALL_HARD_GAIN
|
||||
if x - FIELD_X[0] < m:
|
||||
fx = max(fx, g * (1.0 - (x - FIELD_X[0]) / m))
|
||||
@@ -195,7 +155,6 @@ def compute_heading_speed(x, y, penned, dog_xy, peers, wander_angle, rng=None):
|
||||
fx = min(fx, -g * (1.0 - (FIELD_X[1] - x) / m))
|
||||
if FIELD_Y[1] - y < m:
|
||||
fy = min(fy, -g * (1.0 - (FIELD_Y[1] - y) / m))
|
||||
# South wall hard escape only when not in the gate column and not penned.
|
||||
if (not penned) and (y - FIELD_Y[0] < m) and not (GATE_X[0] <= x <= GATE_X[1]):
|
||||
fy = max(fy, g * (1.0 - (y - FIELD_Y[0]) / m))
|
||||
|
||||
|
||||
+14
-35
@@ -1,23 +1,15 @@
|
||||
"""World geometry and robot specs.
|
||||
|
||||
All coordinates are in meters. (0, 0) is the centre of the field, +x is
|
||||
east, +y is north. Z is up but unused here. These constants must match
|
||||
``worlds/field.wbt`` and the proto files; if the world changes, change
|
||||
this file and only this file.
|
||||
|
||||
Pen layout (post-refactor)
|
||||
--------------------------
|
||||
The pen is *external* to the field, accessed through a 3 m gate cut into
|
||||
the south stone wall at y = -15. Sheep entering through the gate end up
|
||||
in a fenced rectangle south of the field; the dog stays in the field
|
||||
(soft-limited above DOG_SOUTH_LIMIT during training and inference).
|
||||
Coordinates are metres; (0, 0) is the field centre, +x east, +y north.
|
||||
These constants mirror ``worlds/field.wbt`` and the proto files — if
|
||||
the world changes, this file is the single point of update.
|
||||
|
||||
field +y north
|
||||
+-----------+
|
||||
| |
|
||||
| |
|
||||
| ...... |
|
||||
+---||||----+ y = -15 (south wall, gate at x ∈ [10, 13])
|
||||
+---||||----+ y = -15 (south wall, 3 m gate at x ∈ [10, 13])
|
||||
||||
|
||||
|pen| y ∈ [-22, -15]
|
||||
+---+
|
||||
@@ -25,46 +17,38 @@ in a fenced rectangle south of the field; the dog stays in the field
|
||||
|
||||
import math
|
||||
|
||||
# --- Field (square, stone-walled) ---
|
||||
# Field (square, stone-walled)
|
||||
FIELD_X = (-15.0, 15.0)
|
||||
FIELD_Y = (-15.0, 15.0)
|
||||
|
||||
# Conservative inside bounds — sheep/dog should not graze the wall.
|
||||
FIELD_INSIDE_MARGIN = 0.5
|
||||
|
||||
# --- Pen (external, south of the field) ---
|
||||
# Pen (external, south of the field)
|
||||
PEN_X = (10.0, 13.0)
|
||||
PEN_Y = (-22.0, -15.0)
|
||||
PEN_CENTER = (0.5 * (PEN_X[0] + PEN_X[1]), 0.5 * (PEN_Y[0] + PEN_Y[1]))
|
||||
# The point the dog drives the flock toward: the gate centre on the field side.
|
||||
PEN_ENTRY = (0.5 * (PEN_X[0] + PEN_X[1]), -15.0)
|
||||
|
||||
# --- Gate (the hole in the south stone wall) ---
|
||||
# Gate (hole in the south wall)
|
||||
GATE_X = PEN_X
|
||||
GATE_Y = -15.0
|
||||
|
||||
# --- Robot specs (must match proto files) ---
|
||||
# Dog (controllers/shepherd_dog/, protos/ShepherdDog.proto)
|
||||
# Dog spec — protos/ShepherdDog.proto
|
||||
DOG_WHEEL_RADIUS = 0.038 # m
|
||||
DOG_WHEEL_BASE = 0.28 # m, axle-to-axle
|
||||
DOG_MAX_WHEEL_OMEGA = 70.0 # rad/s
|
||||
DOG_MAX_LINEAR = DOG_WHEEL_RADIUS * DOG_MAX_WHEEL_OMEGA # ~2.66 m/s
|
||||
DOG_MAX_LINEAR = DOG_WHEEL_RADIUS * DOG_MAX_WHEEL_OMEGA # ≈ 2.66 m/s
|
||||
|
||||
# Sheep (controllers/sheep/, protos/Sheep.proto)
|
||||
# Sheep spec — protos/Sheep.proto
|
||||
SHEEP_WHEEL_RADIUS = 0.031 # m
|
||||
SHEEP_WHEEL_BASE = 0.20 # m
|
||||
SHEEP_MAX_WHEEL_OMEGA = 25.0 # rad/s
|
||||
SHEEP_MAX_LINEAR = SHEEP_WHEEL_RADIUS * SHEEP_MAX_WHEEL_OMEGA # ~0.78 m/s
|
||||
SHEEP_MAX_LINEAR = SHEEP_WHEEL_RADIUS * SHEEP_MAX_WHEEL_OMEGA # ≈ 0.78 m/s
|
||||
|
||||
# --- Webots step ---
|
||||
WEBOTS_DT = 0.016 # seconds, matches WorldInfo.basicTimeStep = 16 in field.wbt
|
||||
WEBOTS_DT = 0.016 # seconds (matches WorldInfo.basicTimeStep)
|
||||
|
||||
# --- Dog "virtual south wall" (training keeps dog out of the pen) ---
|
||||
# At inference the controller also clips to this so a slightly miscalibrated
|
||||
# policy doesn't accidentally drive into the pen and trap the sheep.
|
||||
# Virtual south wall — env and controller both keep the dog north of this.
|
||||
DOG_SOUTH_LIMIT = -14.5
|
||||
|
||||
# --- Maximum supported flock size ---
|
||||
MAX_SHEEP = 10
|
||||
|
||||
|
||||
@@ -85,12 +69,7 @@ def in_gate_corridor(x: float, y: float, margin: float = 0.0) -> bool:
|
||||
|
||||
|
||||
def is_penned_position(x: float, y: float, latch_margin: float = 0.2) -> bool:
|
||||
"""A sheep latches to "penned" once it crosses the gate plane south.
|
||||
|
||||
True iff x is inside the gate column (with a small margin) AND
|
||||
y has dipped below the gate line. Once latched, the sheep is held by
|
||||
in-pen forces and will not exit on its own.
|
||||
"""
|
||||
"""True iff (x, y) is in the gate column and south of the gate line."""
|
||||
return (PEN_X[0] - latch_margin <= x <= PEN_X[1] + latch_margin
|
||||
and y <= GATE_Y)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user