Skip to content

Commit 97e08a3

Browse files
committed
Add playground belief entropy panel
1 parent 4d4f146 commit 97e08a3

5 files changed

Lines changed: 209 additions & 2 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ python3 examples/runtime/01_sense_act_loop.py
5252
| Failure recovery | `python3 examples/manipulation/01_pick_and_retry.py` | grasp miss -> belief update -> retry |
5353
| Runtime safety | `python3 examples/navigation/29_safety_filter_cbf.py` | nominal controller -> CBF projection -> safe motion |
5454
| Active perception | `python3 examples/navigation/07_active_slam_toy.py` | map and pose uncertainty -> information-seeking action |
55-
| Shareable live trace | [Try live](https://rsasaki0109.github.io/PythonInteractiveRobotics/playground.html?scenario=household&answer=red&compare=1&autoplay=1) | compare, scrub, and jump through failure timelines |
55+
| Shareable live trace | [Try live](https://rsasaki0109.github.io/PythonInteractiveRobotics/playground.html?scenario=household&answer=red&compare=1&autoplay=1) | belief entropy, compare mode, and failure timeline |
5656
| Human correction | [Open in Colab](https://colab.research.google.com/github/rsasaki0109/PythonInteractiveRobotics/blob/main/notebooks/human_correction_replanning.ipynb) | shortcut -> human correction -> cost update -> replan |
5757
| Language ambiguity | [Open in Colab](https://colab.research.google.com/github/rsasaki0109/PythonInteractiveRobotics/blob/main/notebooks/clarifying_question.ipynb) | ambiguous command -> ask question -> answer -> act |
5858
| Integrated household task | [Open in Colab](https://colab.research.google.com/github/rsasaki0109/PythonInteractiveRobotics/blob/main/notebooks/household_task_agent.ipynb) | clarify -> plan -> safety check -> retry -> human replan |

docs/playground.css

Lines changed: 81 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@
5757
.sim-panel {
5858
display: grid;
5959
gap: 14px;
60-
grid-template-rows: auto auto auto auto auto minmax(420px, 1fr);
60+
grid-template-rows: auto auto auto auto auto auto minmax(420px, 1fr);
6161
padding: 16px;
6262
}
6363

@@ -169,6 +169,81 @@ button:disabled {
169169
overflow-wrap: anywhere;
170170
}
171171

172+
.belief-panel {
173+
background: #fffdf7;
174+
border: 1px solid var(--line);
175+
border-radius: 8px;
176+
display: grid;
177+
gap: 12px;
178+
grid-template-columns: minmax(0, 1fr) minmax(180px, 0.65fr);
179+
padding: 12px;
180+
}
181+
182+
.belief-bars {
183+
display: grid;
184+
gap: 9px;
185+
min-width: 0;
186+
}
187+
188+
.belief-row {
189+
align-items: center;
190+
display: grid;
191+
gap: 8px;
192+
grid-template-columns: 42px minmax(0, 1fr) 52px;
193+
min-width: 0;
194+
}
195+
196+
.belief-row span,
197+
.belief-metrics span {
198+
color: var(--muted);
199+
font-size: 0.76rem;
200+
font-weight: 900;
201+
text-transform: uppercase;
202+
}
203+
204+
.belief-track {
205+
background: #eef3ef;
206+
border: 1px solid var(--line);
207+
border-radius: 999px;
208+
height: 12px;
209+
min-width: 0;
210+
overflow: hidden;
211+
}
212+
213+
.belief-fill {
214+
height: 100%;
215+
}
216+
217+
.belief-red {
218+
background: #d94b3d;
219+
}
220+
221+
.belief-blue {
222+
background: #2b6cb0;
223+
}
224+
225+
.belief-value {
226+
color: var(--ink);
227+
font-size: 0.82rem;
228+
font-weight: 900;
229+
text-align: right;
230+
}
231+
232+
.belief-metrics {
233+
display: grid;
234+
gap: 8px;
235+
grid-template-columns: repeat(3, minmax(0, 1fr));
236+
}
237+
238+
.belief-metrics strong {
239+
color: var(--ink);
240+
display: block;
241+
font-size: 0.95rem;
242+
margin-top: 3px;
243+
overflow-wrap: anywhere;
244+
text-transform: none;
245+
}
246+
172247
.replay-strip {
173248
align-items: center;
174249
background: #eef3ef;
@@ -684,6 +759,11 @@ button:disabled {
684759
grid-template-columns: 1fr 1fr;
685760
}
686761

762+
.belief-panel,
763+
.belief-metrics {
764+
grid-template-columns: 1fr;
765+
}
766+
687767
.compare-panel {
688768
grid-template-columns: 1fr;
689769
}

docs/playground.html

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,8 @@ <h1 id="playground-title">Playground</h1>
9494
</div>
9595
</div>
9696

97+
<div id="beliefPanel" class="belief-panel" aria-label="Belief state"></div>
98+
9799
<div class="replay-strip" aria-label="Trace replay">
98100
<label for="replaySlider">
99101
<span>Replay</span>

docs/playground.js

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
copyLink: document.getElementById("copyLinkButton"),
5252
copyTrace: document.getElementById("copyTraceButton"),
5353
copyStatus: document.getElementById("copyStatus"),
54+
beliefPanel: document.getElementById("beliefPanel"),
5455
replay: document.getElementById("replaySlider"),
5556
replayValue: document.getElementById("replayValue"),
5657
comparePanel: document.getElementById("comparePanel"),
@@ -186,6 +187,7 @@
186187
agentState: "parse_command",
187188
target: "unresolved",
188189
failure: "none",
190+
belief: ambiguousBelief(),
189191
picked: null,
190192
pickAt: null,
191193
focus: null,
@@ -205,6 +207,7 @@
205207
target: answer,
206208
agentState: "update_goal_from_answer",
207209
failure: "ambiguous_goal",
210+
belief: resolvedBelief(answer),
208211
question: "Which block?",
209212
answer,
210213
focus: answer,
@@ -219,6 +222,7 @@
219222
...initial,
220223
target: answer,
221224
agentState: "target_confirmed",
225+
belief: resolvedBelief(answer),
222226
focus: answer,
223227
},
224228
},
@@ -231,6 +235,7 @@
231235
...initial,
232236
target: answer,
233237
agentState: "done",
238+
belief: resolvedBelief(answer),
234239
picked: answer,
235240
pickAt: [targetX, 56],
236241
focus: answer,
@@ -310,6 +315,7 @@
310315
command: "put the block away",
311316
robot: [7, 1],
312317
target: "unresolved",
318+
belief: ambiguousBelief(),
313319
held: null,
314320
stored: null,
315321
blocked: [],
@@ -325,6 +331,7 @@
325331
command: "put the block away",
326332
robot: [7, 1],
327333
target: "unresolved",
334+
belief: ambiguousBelief(),
328335
held: null,
329336
stored: null,
330337
blocked: [],
@@ -343,6 +350,7 @@
343350
failure: "ambiguous_goal",
344351
agentState: "update_goal_from_answer",
345352
target: answer,
353+
belief: resolvedBelief(answer),
346354
path: answer === "blue" ? blueRoute : redRoute,
347355
});
348356

@@ -513,11 +521,46 @@
513521
});
514522
}
515523

524+
function ambiguousBelief() {
525+
const distribution = { red: 0.5, blue: 0.5 };
526+
return {
527+
red: distribution.red,
528+
blue: distribution.blue,
529+
entropy: beliefEntropy(distribution),
530+
askGain: beliefEntropy(distribution),
531+
policy: "ask",
532+
};
533+
}
534+
535+
function resolvedBelief(answer) {
536+
const distribution = {
537+
red: answer === "red" ? 1 : 0,
538+
blue: answer === "blue" ? 1 : 0,
539+
};
540+
return {
541+
red: distribution.red,
542+
blue: distribution.blue,
543+
entropy: beliefEntropy(distribution),
544+
askGain: 0,
545+
policy: "act",
546+
};
547+
}
548+
549+
function beliefEntropy(distribution) {
550+
return Object.values(distribution).reduce((total, probability) => {
551+
if (probability <= 0) {
552+
return total;
553+
}
554+
return total - probability * Math.log2(probability);
555+
}, 0);
556+
}
557+
516558
function snapshotFromContext(context) {
517559
return {
518560
command: context.command,
519561
robot: context.robot.slice(),
520562
target: context.target,
563+
belief: copyBelief(context.belief),
521564
held: context.held,
522565
stored: context.stored,
523566
blocked: context.blocked.map((cell) => cell.slice()),
@@ -530,6 +573,16 @@
530573
};
531574
}
532575

576+
function copyBelief(belief) {
577+
return {
578+
red: belief.red,
579+
blue: belief.blue,
580+
entropy: belief.entropy,
581+
askGain: belief.askGain,
582+
policy: belief.policy,
583+
};
584+
}
585+
533586
function trimPath(path, robot) {
534587
if (!path || !path.length) {
535588
return [robot];
@@ -577,6 +630,7 @@
577630
renderReplay(replayIndex);
578631
renderCompare();
579632
renderTimeline(replayIndex);
633+
renderBelief(current);
580634
renderScene(current);
581635
renderTrace(replayIndex);
582636
}
@@ -652,6 +706,67 @@
652706
});
653707
}
654708

709+
function renderBelief(snapshot) {
710+
const belief = snapshot.belief;
711+
elements.beliefPanel.textContent = "";
712+
if (!belief) {
713+
elements.beliefPanel.hidden = true;
714+
return;
715+
}
716+
elements.beliefPanel.hidden = false;
717+
718+
const bars = document.createElement("div");
719+
bars.className = "belief-bars";
720+
[
721+
["red", belief.red],
722+
["blue", belief.blue],
723+
].forEach(([label, probability]) => {
724+
bars.appendChild(renderBeliefRow(label, probability));
725+
});
726+
727+
const metrics = document.createElement("div");
728+
metrics.className = "belief-metrics";
729+
[
730+
["entropy", formatBits(belief.entropy)],
731+
["ask gain", "+" + formatBits(belief.askGain)],
732+
["policy", belief.policy],
733+
].forEach(([label, value]) => {
734+
const metric = document.createElement("span");
735+
metric.textContent = label;
736+
const strong = document.createElement("strong");
737+
strong.textContent = value;
738+
metric.appendChild(strong);
739+
metrics.appendChild(metric);
740+
});
741+
742+
elements.beliefPanel.appendChild(bars);
743+
elements.beliefPanel.appendChild(metrics);
744+
}
745+
746+
function renderBeliefRow(label, probability) {
747+
const row = document.createElement("div");
748+
row.className = "belief-row";
749+
750+
const name = document.createElement("span");
751+
name.textContent = label;
752+
row.appendChild(name);
753+
754+
const track = document.createElement("div");
755+
track.className = "belief-track";
756+
const fill = document.createElement("div");
757+
fill.className = "belief-fill belief-" + label;
758+
fill.style.width = Math.round(probability * 100) + "%";
759+
track.appendChild(fill);
760+
row.appendChild(track);
761+
762+
const value = document.createElement("strong");
763+
value.className = "belief-value";
764+
value.textContent = Math.round(probability * 100) + "%";
765+
row.appendChild(value);
766+
767+
return row;
768+
}
769+
655770
function timelineClass(event) {
656771
if (event.failure === "ambiguous_goal") return "timeline-ambiguous";
657772
if (event.failure === "unsafe_nominal_step") return "timeline-unsafe";
@@ -1106,4 +1221,8 @@
11061221
function formatReward(value) {
11071222
return Number(value).toFixed(2);
11081223
}
1224+
1225+
function formatBits(value) {
1226+
return Number(value).toFixed(2) + " bit";
1227+
}
11091228
})();

tests/test_playground_assets.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ def test_playground_assets_support_shareable_scenarios() -> None:
2222
"copyLinkButton",
2323
"copyTraceButton",
2424
"copyStatus",
25+
"beliefPanel",
2526
"replaySlider",
2627
"replayValue",
2728
"comparePanel",
@@ -45,6 +46,10 @@ def test_playground_assets_support_shareable_scenarios() -> None:
4546
"renderMiniMap",
4647
"renderTimeline",
4748
"timelineClass",
49+
"ambiguousBelief",
50+
"resolvedBelief",
51+
"beliefEntropy",
52+
"renderBelief",
4853
"clampReplayIndex",
4954
"snapshotForReplayIndex",
5055
"trace-active",
@@ -59,6 +64,7 @@ def test_playground_assets_support_shareable_scenarios() -> None:
5964
assert ".replay-strip" in css
6065
assert ".compare-panel" in css
6166
assert ".timeline" in css
67+
assert ".belief-panel" in css
6268

6369

6470
def test_readme_links_to_shareable_live_trace() -> None:

0 commit comments

Comments
 (0)