File size: 7,996 Bytes
9f60832
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
"""AUBIN Control ölçümü: komutlu ızgara oyunu (anlık hamle, doğru cevap her adımda kesin bilinir).

Her bölüm: 7x7 ızgara, duvarlar, lav (~), renkli nesneler (engel), komut "go to the <renk> <nesne>".
Her adımda doğru hamleler = hedefe giden EN KISA yolların ilk adımları (BFS, lav ve nesnelerden kaçınarak).
Ölçüler: adım doğruluğu, bölüm başarısı, lava düşme, güven ≥ eşik iken doğruluk ve kapsama, karar başına gecikme (ms).
Karşılaştırma: rastgele ve açgözlü (Manhattan, engelleri görmez) politikalar — aynı bölümler.
    python control_bench.py --adapter aubin12-plain --episodes 100 --out control.json
"""
import argparse, collections, json, os, random, sys, time

HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, HERE)
MOVES = {"up": (-1, 0), "down": (1, 0), "left": (0, -1), "right": (0, 1)}
ACTIONS = {"up": "move one cell up (row - 1)", "down": "move one cell down (row + 1)",
           "left": "move one cell left (column - 1)", "right": "move one cell right (column + 1)"}
COLORS, KINDS = ["red", "green", "blue", "yellow"], ["key", "ball", "box"]


def make(rng, n=7, lava=5, objs=4):
    while True:
        cells = [(r, c) for r in range(1, n + 1) for c in range(1, n + 1)]
        rng.shuffle(cells)
        agent, rest = cells[0], cells[1:]
        items = [(rest[i], rng.choice(COLORS), rng.choice(KINDS)) for i in range(objs)]
        names = {(c, k) for _, c, k in items}
        if len(names) < objs:
            continue
        lv = set(rest[objs: objs + lava])
        tgt = items[0]
        if optimal(agent, tgt[0], lv, {p for p, _, _ in items[1:]}, n)[0] is not None:
            return {"n": n, "agent": agent, "items": items, "lava": lv, "target": tgt}


def optimal(start, goal, lava, block, n):
    """BFS uzaklık haritası hedeften; dönüş: (uzaklık, en kısa yolun ilk adımları)."""
    ok = lambda p: 1 <= p[0] <= n and 1 <= p[1] <= n and p not in lava and p not in block
    dist, dq = {goal: 0}, collections.deque([goal])
    while dq:
        p = dq.popleft()
        for dr, dc in MOVES.values():
            q = (p[0] + dr, p[1] + dc)
            if q not in dist and (ok(q) or q == start):
                dist[q] = dist[p] + 1; dq.append(q)
    if start not in dist:
        return None, set()
    best = {a for a, (dr, dc) in MOVES.items() if dist.get((start[0] + dr, start[1] + dc), 1e9) == dist[start] - 1}
    return dist[start], best


def render(ep, pos):
    n = ep["n"]; g = [["#"] * (n + 2) for _ in range(n + 2)]
    for r in range(1, n + 1):
        for c in range(1, n + 1):
            g[r][c] = "."
    for p in ep["lava"]:
        g[p[0]][p[1]] = "~"
    for p, col, k in ep["items"]:
        g[p[0]][p[1]] = col[0].upper() if k == "key" else col[0]
    g[pos[0]][pos[1]] = "@"
    obj = [f"{col} {k} at row {p[0]}, column {p[1]}" for p, col, k in ep["items"]]
    return {"grid": "\n".join("".join(r) for r in g),
            "legend": "@ = you, # = wall, ~ = lava (deadly), letters = objects (they block movement)",
            "you": f"row {pos[0]}, column {pos[1]}", "objects": obj}


def run(policy, eps, max_extra=4):
    steps = hit = succ = dead = 0; lat, conf_rows = [], []
    for ep in eps:
        getattr(policy, "reset", lambda: None)()                 # yeni bölüm: hamle geçmişi sıfır (eğitim biçimiyle aynı)
        pos = ep["agent"]; tp, col, k = ep["target"]; block = {p for p, _, _ in ep["items"][1:]}
        d0, _ = optimal(pos, tp, ep["lava"], block, ep["n"])
        for _ in range(d0 + max_extra):
            d, best = optimal(pos, tp, ep["lava"], block, ep["n"])
            obs = render(ep, pos)
            # görünen haritadan güvenli hamleler (lav / duvar / nesneye gitmeyen; hedef hücre serbest) — güvenlik kalkanı için
            safe = {a for a, (dr, dc) in MOVES.items() if (q := (pos[0] + dr, pos[1] + dc)) == tp or
                    (1 <= q[0] <= ep["n"] and 1 <= q[1] <= ep["n"] and q not in ep["lava"] and q not in block)}
            t0 = time.perf_counter(); a, c = policy(obs, f"go to the {col} {k}", pos, tp, safe); lat.append((time.perf_counter() - t0) * 1000)
            steps += 1; ok = a in best; hit += ok; conf_rows.append((c, ok))
            dr, dc = MOVES.get(a, (0, 0)); q = (pos[0] + dr, pos[1] + dc)
            if q == tp:
                succ += 1; break
            if q in ep["lava"]:
                dead += 1; break
            if 1 <= q[0] <= ep["n"] and 1 <= q[1] <= ep["n"] and q not in block:
                pos = q
    out = {"episodes": len(eps), "decisions": steps, "step_accuracy": round(hit / steps, 4), "success": round(succ / len(eps), 4),
           "lava_deaths": dead, "ms_per_decision_median": round(sorted(lat)[len(lat) // 2], 1)}
    for th in (0.8, 0.9):
        sel = [ok for c, ok in conf_rows if c is not None and c >= th]
        if sel:
            out[f"acc_when_conf_ge_{th}"] = round(sum(sel) / len(sel), 4); out[f"coverage_conf_ge_{th}"] = round(len(sel) / len(conf_rows), 4)
    return out


def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("--adapter", default="aubin12-plain"); ap.add_argument("--episodes", type=int, default=100)
    ap.add_argument("--seed", type=int, default=0); ap.add_argument("--work", default=""); ap.add_argument("--out", default="control.json")
    ap.add_argument("--no_model", action="store_true"); ap.add_argument("--label", default="AUBIN-12B")
    a = ap.parse_args()
    rng = random.Random(a.seed); eps = [make(rng) for _ in range(a.episodes)]
    R = {"episodes": a.episodes, "seed": a.seed, "rows": {}}
    r2 = random.Random(1)
    R["rows"]["random"] = run(lambda o, cmd, p, t, s: (r2.choice(list(MOVES)), None), eps)
    def greedy(o, cmd, p, t, s=None):
        dr, dc = t[0] - p[0], t[1] - p[1]
        return ("down" if dr > 0 else "up") if abs(dr) >= abs(dc) and dr else ("right" if dc > 0 else "left"), None
    R["rows"]["greedy (ignores obstacles)"] = run(greedy, eps)
    # aynı güvenlik kalkanıyla kural tabanlı taban çizgileri (kalkanın tek başına katkısını ayırmak için)
    r3 = random.Random(2)
    R["rows"]["random + safety shield"] = run(lambda o, cmd, p, t, s: (r3.choice(sorted(s)) if s else "up", None), eps)
    def greedy_shield(o, cmd, p, t, s):
        dr, dc = t[0] - p[0], t[1] - p[1]
        pref = []                                                  # hedefe yaklaştıran hamleler önce (büyük eksen önce)
        vert = ("down" if dr > 0 else "up") if dr else None; hor = ("right" if dc > 0 else "left") if dc else None
        pref += [m for m in ((vert, hor) if abs(dr) >= abs(dc) else (hor, vert)) if m]
        for m in pref + sorted(s):
            if m in s:
                return m, None
        return "up", None
    R["rows"]["greedy + safety shield"] = run(greedy_shield, eps)
    if not a.no_model:
        from kev_llm import resolve_adapter
        from aubin.core import Aubin
        from aubin.control import AubinController
        A = Aubin(resolve_adapter(a.adapter) or None, think_margin=0.0)
        ctl = AubinController(A, ACTIONS, instructions="Choose the next move that follows the command along a shortest safe path "
                                                       "(never step on lava, objects block movement).")
        class Pol:                                               # bölüm başında ctl.reset; kalkan isteğe bağlı
            def __init__(self, shield): self.shield = shield
            def reset(self): ctl.reset()
            def __call__(self, o, cmd, p, t, s):
                r = ctl.act(o, command=cmd, allowed=s if self.shield else None); return r["action"], r["confidence"]
        R["rows"][f"{a.label} (one pass per move)"] = run(Pol(False), eps)
        R["rows"][f"{a.label} + safety shield (never steps on visible lava/walls)"] = run(Pol(True), eps)
    json.dump(R, open(a.out, "w"), indent=1)
    print("SONUC", json.dumps(R), flush=True)


if __name__ == "__main__":
    main()