arudradey's picture
Add interactive step-by-step autoplay dashboard
16a7209 verified
Raw History Blame Contribute Delete
18.6 kB
import os
import json
import time
from typing import Dict, List, Any, Tuple
import gradio as gr
from umg_engine import UniversalMathGame, MathematicalState
from laya_engine import LayaDecisionOracle
# Session storage for active games
GAMES: Dict[str, UniversalMathGame] = {}
def get_game(sess_id: str, prob_str: str) -> UniversalMathGame:
if sess_id not in GAMES or GAMES[sess_id].initial_problem != prob_str:
GAMES[sess_id] = UniversalMathGame(prob_str)
return GAMES[sess_id]
PRESETS = {
"Quadratic Equation": "x^2 - 5x + 6 = 0",
"Difference of Squares": "x^2 - 49 = 0",
"Cubic Polynomial": "x^3 - 4*x = 0",
"Linear Equation": "3*x + 9 = 24",
"Adversarial Fallacy Trap": "x^2 = x",
"Rational Fraction": "(x^2 - 1)/(x - 1) = 4",
}
# ============================================================
# Visual HTML Renderers
# ============================================================
def render_proof_timeline(history: List[Dict[str, Any]], current_eqs: List[str], is_goal: bool) -> str:
"""Render interactive vertical/horizontal proof steps."""
cards = []
# Initial state S0
init_eq = history[0]["state_before"][0] if history else (current_eqs[0] if current_eqs else "None")
cards.append(f"""
<div style="background: rgba(59, 130, 246, 0.08); border: 1px solid rgba(59, 130, 246, 0.3); border-radius: 10px; padding: 10px 14px; margin-bottom: 10px;">
<div style="display: flex; justify-content: space-between; align-items: center;">
<span style="font-weight: 700; color: #2563eb; font-size: 0.85rem;">STATE S₀ (Initial)</span>
<span style="font-size: 0.75rem; background: #dbeafe; color: #1e40af; padding: 2px 8px; border-radius: 999px;">Start</span>
</div>
<div style="font-family: monospace; font-size: 1.1rem; margin: 6px 0; color: #1e293b; font-weight: 600;">{init_eq}</div>
</div>
""")
for idx, item in enumerate(history):
act = item.get("action", {})
act_name = act.get("type", "unknown")
state_after = " and ".join(item.get("state_after", []))
delta = item.get("delta_distance", 0.0)
badge_color = "#10b981" if delta > 0 else "#6b7280"
cards.append(f"""
<div style="display: flex; align-items: center; justify-content: center; margin: 4px 0;">
<span style="background: #f1f5f9; border: 1px solid #cbd5e1; border-radius: 999px; padding: 2px 10px; font-size: 0.75rem; color: #475569; font-weight: 600;">
⚡ Action: <b>{act_name}</b> (Δdist: {delta:+.1f})
</span>
</div>
<div style="background: rgba(16, 185, 129, 0.06); border: 1px solid rgba(16, 185, 129, 0.3); border-radius: 10px; padding: 10px 14px; margin-bottom: 10px;">
<div style="display: flex; justify-content: space-between; align-items: center;">
<span style="font-weight: 700; color: #059669; font-size: 0.85rem;">STATE S_{idx+1} (Verified by SymPy)</span>
<span style="font-size: 0.75rem; background: #d1fae5; color: #065f46; padding: 2px 8px; border-radius: 999px;">Valid</span>
</div>
<div style="font-family: monospace; font-size: 1.1rem; margin: 6px 0; color: #0f172a; font-weight: 600;">{state_after}</div>
</div>
""")
if is_goal:
cards.append("""
<div style="background: linear-gradient(135deg, rgba(16, 185, 129, 0.15), rgba(5, 150, 105, 0.15)); border: 2px solid #10b981; border-radius: 12px; padding: 14px; text-align: center;">
<div style="font-size: 1.2rem; font-weight: 800; color: #065f46;">🏆 Q.E.D. — Proof Formally Completed!</div>
<div style="font-size: 0.85rem; color: #047857; margin-top: 4px;">All roots isolated · Invariant equivalence verified · Proof Certificate generated</div>
</div>
""")
return "".join(cards)
def render_laya_radar(conf: float, dist: float, is_goal: bool, latency_ms: float = 24.5) -> str:
"""Render Laya System-1 Intuition Radar."""
conf_pct = int(conf * 100)
risk_label = "0% (Invariant Geodesic)" if conf > 0.85 else "12% (Medium Complexity)"
status_label = "SOLVED (Q.E.D.)" if is_goal else f"ACTIVE (D={dist:.1f})"
return f"""
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); gap: 10px; margin-bottom: 12px;">
<div style="background: #f8fafc; border: 1px solid #e2e8f0; border-radius: 8px; padding: 8px 12px; text-align: center;">
<div style="font-size: 0.75rem; color: #64748b; font-weight: 600;">LAYA INTUITION</div>
<div style="font-size: 1.15rem; font-weight: 800; color: #2563eb;">{conf_pct}%</div>
<div style="font-size: 0.7rem; color: #16a34a;">Calibrated Confidence</div>
</div>
<div style="background: #f8fafc; border: 1px solid #e2e8f0; border-radius: 8px; padding: 8px 12px; text-align: center;">
<div style="font-size: 0.75rem; color: #64748b; font-weight: 600;">DIVERGENCE RISK</div>
<div style="font-size: 1.05rem; font-weight: 800; color: #16a34a;">{risk_label}</div>
<div style="font-size: 0.7rem; color: #64748b;">Error Well Barrier</div>
</div>
<div style="background: #f8fafc; border: 1px solid #e2e8f0; border-radius: 8px; padding: 8px 12px; text-align: center;">
<div style="font-size: 0.75rem; color: #64748b; font-weight: 600;">GOAL DISTANCE</div>
<div style="font-size: 1.15rem; font-weight: 800; color: #7c3aed;">{dist:.1f}</div>
<div style="font-size: 0.7rem; color: #64748b;">{status_label}</div>
</div>
<div style="background: #f8fafc; border: 1px solid #e2e8f0; border-radius: 8px; padding: 8px 12px; text-align: center;">
<div style="font-size: 0.75rem; color: #64748b; font-weight: 600;">SYSTEM-1 LATENCY</div>
<div style="font-size: 1.15rem; font-weight: 800; color: #0284c7;">~{latency_ms:.1f}ms</div>
<div style="font-size: 0.7rem; color: #64748b;">Single Forward Pass</div>
</div>
</div>
"""
# ============================================================
# Handlers with Step-by-Step Autoplay Streaming
# ============================================================
def init_game_ui(preset_name: str, custom_problem: str):
prob = custom_problem.strip() if preset_name == "Custom" and custom_problem.strip() else PRESETS.get(preset_name, PRESETS["Quadratic Equation"])
game = UniversalMathGame(prob)
sess_id = str(time.time())
GAMES[sess_id] = game
latex_display = f"$${game.state.to_display_latex()}$$"
m_ir_json = json.dumps(game.state.to_dict(), indent=2)
dist = game.heuristic_distance(game.state)
timeline_html = render_proof_timeline([], game.state.equations, False)
radar_html = render_laya_radar(0.95, dist, False)
legal_acts = [a["description"] for a in game.legal_actions()]
legal_choices = gr.update(choices=legal_acts, value=legal_acts[0] if legal_acts else None)
cert_json = json.dumps(game.export_proof_certificate(), indent=2)
return (
sess_id,
prob,
latex_display,
radar_html,
timeline_html,
"Game initialized at state S₀. Click 'Start Autoplay' or 'Laya Single Step' to watch Laya solve the problem.",
m_ir_json,
legal_choices,
cert_json,
)
def laya_step_ui(sess_id: str, prob: str):
game = get_game(sess_id, prob)
if game.is_goal(game.state):
dist = 0.0
return (
f"$${game.state.to_display_latex()}$$",
render_laya_radar(0.99, dist, True),
render_proof_timeline(game.state.proof_history, game.state.equations, True),
"🏆 Goal already reached! Proof certificate is verified.",
json.dumps(game.state.to_dict(), indent=2),
gr.update(),
json.dumps(game.export_proof_certificate(), indent=2),
)
legal_acts = game.legal_actions()
chosen_act, conf, reason = LayaDecisionOracle.decide_next_action(
game.state.equations,
game.state.goals,
legal_acts,
depth=game.state.depth,
)
res = game.step(chosen_act)
dist = game.heuristic_distance(game.state)
is_goal = game.is_goal(game.state)
latex_display = f"$${game.state.to_display_latex()}$$"
radar_html = render_laya_radar(conf, dist, is_goal)
timeline_html = render_proof_timeline(game.state.proof_history, game.state.equations, is_goal)
log_msg = f"{reason}\n{res['message']}"
m_ir_json = json.dumps(game.state.to_dict(), indent=2)
new_legal = [a["description"] for a in game.legal_actions()]
legal_choices = gr.update(choices=new_legal, value=new_legal[0] if new_legal else None)
cert_json = json.dumps(game.export_proof_certificate(), indent=2)
return (
latex_display,
radar_html,
timeline_html,
log_msg,
m_ir_json,
legal_choices,
cert_json,
)
def laya_autoplay_stream(sess_id: str, prob: str, step_delay: float = 1.0, max_steps: int = 8):
"""
Generator streaming step-by-step updates with animated time delay.
Users watch the self-play game evolve live!
"""
game = get_game(sess_id, prob)
for step_idx in range(int(max_steps)):
if game.is_goal(game.state):
# Final Q.E.D. state
dist = 0.0
yield (
f"$${game.state.to_display_latex()}$$",
render_laya_radar(0.99, dist, True),
render_proof_timeline(game.state.proof_history, game.state.equations, True),
"🎉 Formally Solved! Laya completed the mathematical game in self-play.",
json.dumps(game.state.to_dict(), indent=2),
gr.update(),
json.dumps(game.export_proof_certificate(), indent=2),
)
break
legal_acts = game.legal_actions()
chosen_act, conf, reason = LayaDecisionOracle.decide_next_action(
game.state.equations,
game.state.goals,
legal_acts,
depth=game.state.depth,
)
res = game.step(chosen_act)
dist = game.heuristic_distance(game.state)
is_goal = game.is_goal(game.state)
latex_display = f"$${game.state.to_display_latex()}$$"
radar_html = render_laya_radar(conf, dist, is_goal)
timeline_html = render_proof_timeline(game.state.proof_history, game.state.equations, is_goal)
log_msg = f"Step {game.state.depth}: {reason}\n{res['message']}"
m_ir_json = json.dumps(game.state.to_dict(), indent=2)
new_legal = [a["description"] for a in game.legal_actions()]
legal_choices = gr.update(choices=new_legal, value=new_legal[0] if new_legal else None)
cert_json = json.dumps(game.export_proof_certificate(), indent=2)
yield (
latex_display,
radar_html,
timeline_html,
log_msg,
m_ir_json,
legal_choices,
cert_json,
)
if not res["valid"] or is_goal:
break
time.sleep(float(step_delay))
def human_step_ui(sess_id: str, prob: str, selected_action_desc: str, custom_val: str):
game = get_game(sess_id, prob)
legal_acts = game.legal_actions()
chosen_act = None
for a in legal_acts:
if a["description"] == selected_action_desc:
chosen_act = dict(a)
break
if not chosen_act:
chosen_act = legal_acts[0] if legal_acts else {"type": "finish"}
if custom_val and custom_val.strip():
chosen_act["value"] = custom_val.strip()
res = game.step(chosen_act)
dist = game.heuristic_distance(game.state)
is_goal = game.is_goal(game.state)
latex_display = f"$${game.state.to_display_latex()}$$"
radar_html = render_laya_radar(0.85, dist, is_goal)
timeline_html = render_proof_timeline(game.state.proof_history, game.state.equations, is_goal)
m_ir_json = json.dumps(game.state.to_dict(), indent=2)
new_legal = [a["description"] for a in game.legal_actions()]
legal_choices = gr.update(choices=new_legal, value=new_legal[0] if new_legal else None)
cert_json = json.dumps(game.export_proof_certificate(), indent=2)
return (
latex_display,
radar_html,
timeline_html,
res["message"],
m_ir_json,
legal_choices,
cert_json,
)
# ============================================================
# Gradio Interface
# ============================================================
CUSTOM_CSS = """
.gradio-container { max-width: 1260px !important; margin: 0 auto !important; }
.hero-title { font-size: 2.2rem; font-weight: 800; text-align: center; margin-bottom: 0.25rem; }
.hero-desc { text-align: center; color: var(--body-text-color-subdued); margin-bottom: 1.5rem; font-size: 1.05rem; }
.control-card { background: var(--background-fill-secondary); border-radius: 12px; padding: 1.25rem; border: 1px solid var(--border-color-primary); }
"""
with gr.Blocks(title="Universal Math Game (UMG v0.1) with Laya") as demo:
session_holder = gr.State(value="init_session")
gr.Markdown(
"# ♟️ Universal Math Game (UMG v0.1) with Laya\n"
"### Transforming Mathematics into a Verifiable Self-Play RL Game\n"
"**System-1 Decision Agent (Laya)** proposes moves in <50ms · **Multi-Tier Symbolic Engine (SymPy)** serves as the referee"
)
with gr.Row():
with gr.Column(scale=4):
with gr.Group():
gr.Markdown("### 🎯 Problem Setup")
preset_dropdown = gr.Dropdown(
choices=list(PRESETS.keys()) + ["Custom"],
value="Quadratic Equation",
label="Choose Problem Preset",
)
problem_text = gr.Textbox(
value=PRESETS["Quadratic Equation"],
label="Equation Expression",
lines=1,
)
init_btn = gr.Button("🔄 Initialize S₀ State", variant="secondary")
with gr.Group():
gr.Markdown("### 🤖 Laya System-1 Autoplay")
gr.Markdown("Watch Laya solve the problem autonomously in a live self-play animation.")
with gr.Row():
laya_autoplay_btn = gr.Button("▶️ Start Autoplay", variant="primary")
laya_single_btn = gr.Button("⏭️ Single Step", variant="secondary")
step_delay_slider = gr.Slider(
minimum=0.3,
maximum=2.5,
value=1.0,
step=0.1,
label="Autoplay Step Speed (Seconds per move)",
)
with gr.Group():
gr.Markdown("### 🎮 Play as Human")
human_action_dropdown = gr.Dropdown(
choices=[],
label="Select Legal Move",
)
custom_arg_input = gr.Textbox(
label="Optional Parameter",
placeholder="e.g. constant or divisor",
)
human_step_btn = gr.Button("🎯 Play Move as Human", variant="secondary")
with gr.Column(scale=8):
with gr.Group():
gr.Markdown("#### 📐 Current Mathematical State ($S_t$)")
state_latex = gr.Markdown("$$x^2 - 5x + 6 = 0$$")
radar_display = gr.HTML()
with gr.Group():
gr.Markdown("#### 🗺️ Verified Proof Trajectory (Live Timeline)")
timeline_display = gr.HTML()
with gr.Group():
gr.Markdown("#### 🛡️ Verifier & Laya Oracle Feedback")
verifier_log = gr.Textbox(
label="Referee Judgment & System-1 Gut Feeling",
interactive=False,
lines=3,
)
with gr.Accordion("🧬 M-IR (Mathematical Intermediate Representation)", open=False):
m_ir_display = gr.Code(
language="json",
label="Structured JSON State Object",
)
with gr.Accordion("📜 Verified Proof Certificate", open=True):
cert_display = gr.Code(
language="json",
label="Machine-Checkable Proof Certificate",
)
# Wire up Event Handlers
preset_dropdown.change(
fn=lambda p: PRESETS.get(p, ""),
inputs=[preset_dropdown],
outputs=[problem_text],
)
init_btn.click(
fn=init_game_ui,
inputs=[preset_dropdown, problem_text],
outputs=[
session_holder,
problem_text,
state_latex,
radar_display,
timeline_display,
verifier_log,
m_ir_display,
human_action_dropdown,
cert_display,
],
)
laya_single_btn.click(
fn=laya_step_ui,
inputs=[session_holder, problem_text],
outputs=[
state_latex,
radar_display,
timeline_display,
verifier_log,
m_ir_display,
human_action_dropdown,
cert_display,
],
)
laya_autoplay_btn.click(
fn=laya_autoplay_stream,
inputs=[session_holder, problem_text, step_delay_slider],
outputs=[
state_latex,
radar_display,
timeline_display,
verifier_log,
m_ir_display,
human_action_dropdown,
cert_display,
],
)
human_step_btn.click(
fn=human_step_ui,
inputs=[session_holder, problem_text, human_action_dropdown, custom_arg_input],
outputs=[
state_latex,
radar_display,
timeline_display,
verifier_log,
m_ir_display,
human_action_dropdown,
cert_display,
],
)
demo.load(
fn=init_game_ui,
inputs=[preset_dropdown, problem_text],
outputs=[
session_holder,
problem_text,
state_latex,
radar_display,
timeline_display,
verifier_log,
m_ir_display,
human_action_dropdown,
cert_display,
],
)
if __name__ == "__main__":
demo.launch(css=CUSTOM_CSS)