File size: 5,978 Bytes
ba4f7c6
 
 
 
 
 
 
 
 
 
20ad86b
 
ba4f7c6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20ad86b
 
 
ba4f7c6
 
 
 
 
 
 
 
 
20ad86b
ba4f7c6
 
20ad86b
ba4f7c6
 
 
 
20ad86b
 
 
ba4f7c6
 
20ad86b
aa41bac
 
 
 
 
 
 
 
 
 
ba4f7c6
 
 
 
 
20ad86b
ba4f7c6
20ad86b
ba4f7c6
 
20ad86b
 
ba4f7c6
20ad86b
 
 
ba4f7c6
 
20ad86b
 
 
 
ba4f7c6
20ad86b
 
ba4f7c6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20ad86b
 
 
ba4f7c6
 
 
 
 
 
 
 
 
 
20ad86b
ba4f7c6
 
 
 
 
 
aa41bac
ba4f7c6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20ad86b
 
ba4f7c6
 
 
 
 
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
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
"""
Minimum-Violation LTL Planning β€” Interactive Demo
Reproduces: Tumova et al., "Minimum-Violation LTL Planning with Conflicting
Specifications", ACC 2013 (arXiv:1303.3679)

Change spec priorities β†’ watch the plan change to satisfy the highest-priority rules.
"""

import gradio as gr

from src.grid_world import make_scenario
from src.automata import parse_spec
from src.planner import plan
from src.visualize import render_static, render_animation, spec_table_html


SCENARIO_SPECS = {
    "road": [
        ("G(!danger)",  "Never cross double line (danger zone)", 80),
        ("GF(zone_a)",  "Periodically visit pickup zone A",      50),
        ("GF(zone_b)",  "Periodically visit dropoff zone B",     30),
        ("F(goal)",     "Eventually reach the goal",             10),
    ],
    "patrol": [
        ("G(!danger)",  "Stay away from danger zones",    100),
        ("GF(zone_a)",  "Patrol zone A repeatedly",        60),
        ("GF(zone_b)",  "Patrol zone B repeatedly",        40),
    ],
    "rescue": [
        ("G(!danger)",  "Avoid hazardous areas",           90),
        ("F(zone_a)",   "Reach survivor site A",           70),
        ("F(zone_b)",   "Reach survivor site B",           50),
        ("GF(zone_c)",  "Return to base (zone C) always",  20),
    ],
}

SCENARIO_DESCRIPTIONS = {
    "road":   "πŸš— Road network β€” navigate around a danger zone to reach pickup/dropoff areas and a destination.",
    "patrol": "🏭 Warehouse patrol β€” periodically cover two inspection zones while avoiding a hazardous area.",
    "rescue": "🚁 Rescue mission β€” reach two survivor sites and periodically return to base, avoiding hazards.",
}


def run_planning(scenario, r0, r1, r2, r3, animate):
    grid = make_scenario(scenario)
    specs_raw = SCENARIO_SPECS[scenario]
    n_specs = len(specs_raw)
    rewards_input = [r0, r1, r2, r3][:n_specs]

    automata, rewards = [], []
    for i, (formula, _, _) in enumerate(specs_raw):
        try:
            automata.append(parse_spec(formula, label=formula))
            rewards.append(float(rewards_input[i]))
        except ValueError as e:
            return None, None, f"<p style='color:red'>Error: {e}</p>"

    result   = plan(grid, automata, rewards)
    html     = spec_table_html(result)
    static   = render_static(grid, result)

    if animate and result.success:
        gif = render_animation(grid, result, fps=3)
        # Embed as base64 so Gradio 5.x HTML component plays it natively
        import base64
        with open(gif, "rb") as f:
            b64 = base64.b64encode(f.read()).decode()
        gif_html = (
            f"<img src='data:image/gif;base64,{b64}' "
            f"style='max-width:100%;border-radius:8px'/>"
        )
        return static, gif_html, html
    return static, "", html


def update_scenario_ui(scenario):
    specs = SCENARIO_SPECS[scenario]
    desc  = SCENARIO_DESCRIPTIONS[scenario]
    n     = len(specs)

    slider_updates = []
    for i in range(4):
        if i < n:
            _, label, val = specs[i]
            slider_updates.append(gr.Slider(label=label, value=val, visible=True))
        else:
            slider_updates.append(gr.Slider(visible=False))

    return [gr.Markdown(value=f"**{desc}**")] + slider_updates


def load_defaults():
    ui   = update_scenario_ui("road")
    plan_out = run_planning("road", 80, 50, 30, 10, False)
    return ui + list(plan_out)


# ── UI ────────────────────────────────────────────────────────────────────────

with gr.Blocks(title="Minimum-Violation LTL Planner", theme=gr.themes.Soft()) as demo:

    gr.Markdown("""
# Minimum-Violation LTL Planning
**Reproducing:** Tumova et al., *"Minimum-Violation LTL Planning with Conflicting Specifications"*, ACC 2013

When robot specs conflict, instead of failing, this planner finds the path that satisfies
the **highest-priority** rules and minimally violates the rest.

> **Try it:** drag the reward sliders to swap priorities β€” watch the planned path change.
""")

    with gr.Row():
        with gr.Column(scale=1):
            scenario_dd = gr.Dropdown(
                choices=["road", "patrol", "rescue"],
                value="road",
                label="Scenario",
            )
            scenario_desc = gr.Markdown("**Loading...**")

            gr.Markdown("### Spec Priorities (higher reward = harder to violate)")

            sliders = []
            for i, (_, lbl, val) in enumerate(SCENARIO_SPECS["road"]):
                sliders.append(gr.Slider(minimum=0, maximum=200, step=5,
                                         value=val, label=lbl, visible=True))

            animate_cb = gr.Checkbox(label="Generate animation (GIF)", value=True)
            plan_btn   = gr.Button("β–Ά  Synthesize Plan", variant="primary")

            gr.Markdown("""
---
**How it works:**
1. Each spec Ο†α΅’ becomes a BΓΌchi automaton
2. Product automaton = grid Γ— aut₁ Γ— autβ‚‚ Γ— ...
3. SCCs with accepting states for each spec are found
4. Max-reward SCC β†’ lasso path reconstructed

πŸ”΅ Prefix path &nbsp;&nbsp; πŸ”΄ Repeating cycle &nbsp;&nbsp; 🟣 Start &nbsp;&nbsp; 🟠 Robot
""")

        with gr.Column(scale=2):
            grid_img  = gr.Image(label="Planned Path", type="pil", height=420)
            anim_img  = gr.HTML(label="Animation (GIF)")
            result_md = gr.HTML(label="Spec Satisfaction")

    scenario_dd.change(
        fn=update_scenario_ui,
        inputs=[scenario_dd],
        outputs=[scenario_desc] + sliders,
    )

    plan_btn.click(
        fn=run_planning,
        inputs=[scenario_dd] + sliders + [animate_cb],
        outputs=[grid_img, anim_img, result_md],
    )

    demo.load(
        fn=load_defaults,
        outputs=[scenario_desc] + sliders + [grid_img, anim_img, result_md],
    )


if __name__ == "__main__":
    demo.launch()