File size: 3,602 Bytes
f8faa82
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
import os
"""
Central obstacle definitions for the avoiding-v0 environment.

All coordinates are in real-world (unnormalized) space.
Import from here — do not duplicate obstacle geometry elsewhere.
"""

from dataclasses import dataclass


@dataclass
class CircularObstacle:
    """Circular keep-out zone defined by center and radius."""
    center: tuple  # (x, y)
    radius: float


@dataclass
class PlanarObstacle:
    """Half-plane constraint: slope * x + intercept - y >= 0 is the feasible side."""
    point_a: tuple  # (x, y)
    point_b: tuple  # (x, y)

    @property
    def slope(self):
        return (self.point_b[1] - self.point_a[1]) / (self.point_b[0] - self.point_a[0])

    @property
    def intercept(self):
        return self.point_b[1] - self.slope * self.point_b[0]

    def violated(self, x, y):
        return y > self.slope * x + self.intercept


# ---------------------------------------------------------------------------
# Obstacle sets
# ---------------------------------------------------------------------------

PILLARS = [
    CircularObstacle(center=(0.5, -0.1), radius=0.03),
    CircularObstacle(center=(0.425, 0.08), radius=0.025),
    CircularObstacle(center=(0.575, 0.08), radius=0.025),
    CircularObstacle(center=(0.35, 0.26), radius=0.025),
    CircularObstacle(center=(0.5, 0.26), radius=0.025),
    CircularObstacle(center=(0.65, 0.26), radius=0.025),
]

NOVEL_CIRCULAR = [
    CircularObstacle(center=(0.5, -0.1), radius=0.08),
]

# Three further keep-outs that ship commented out in the upstream release.
# They sit in the l2 -> l3 mid-band (y ~ 0.16-0.175), where the demonstrations
# run straight through, and narrow the tightest passage from 0.100 m to
# 0.063 m.  Enabled only by D3IL_HARD=1 so that the default constraint set
# stays bit-identical to every number already reported.
NOVEL_CIRCULAR_HARD = [
    CircularObstacle(center=(0.5, 0.16), radius=0.02),
    CircularObstacle(center=(0.4, 0.175), radius=0.02),
    CircularObstacle(center=(0.6, 0.175), radius=0.02),
]

if os.environ.get("D3IL_HARD", "0") == "1":
    NOVEL_CIRCULAR = NOVEL_CIRCULAR + NOVEL_CIRCULAR_HARD

NOVEL_PLANAR = [
    PlanarObstacle(point_a=(0.8, -0.3), point_b=(0.575, 0.5)),
    PlanarObstacle(point_a=(0.2, -0.3), point_b=(0.425, 0.5)),
]

CONSTRAINT_SETS = {
    "novel": {
        "circular": PILLARS + NOVEL_CIRCULAR,
        "planar": NOVEL_PLANAR,
    },
}


def count_constraints(constraint_name):
    if constraint_name == "" or constraint_name not in CONSTRAINT_SETS:
        return 0
    cs = CONSTRAINT_SETS[constraint_name]
    return len(cs["circular"]) + len(cs["planar"])


TARGET_Y = 0.35  # y coordinate of the arrival/goal region


def check_violation(observation, constraint_name):
    """Check if a single observation violates any constraint in the given set.

    Returns 1.0 if violated, 0.0 otherwise.
    Constraints are not checked once the agent reaches the target region (y >= TARGET_Y).
    """
    import numpy as np

    if constraint_name == "":
        return 0.0

    if constraint_name not in CONSTRAINT_SETS:
        raise NotImplementedError(f"Constraint '{constraint_name}' is not implemented.")

    x = observation[2]
    y = observation[3]

    if y >= TARGET_Y:
        return 0.0

    constraints = CONSTRAINT_SETS[constraint_name]

    for obs in constraints["circular"]:
        distance = np.sqrt((x - obs.center[0]) ** 2 + (y - obs.center[1]) ** 2)
        if distance <= obs.radius:
            return 1.0

    for obs in constraints["planar"]:
        if obs.violated(x, y):
            return 1.0

    return 0.0