immunoorg-2 / visualization /metrics.py
Charan Sai Mamidala
deploy: fix openenv-core version and remove binaries
788dd2e
"""
Proof-of-Improvement Metrics
=============================
Plotting functions for Time-to-Containment, Org-Efficiency, and training curves.
"""
from __future__ import annotations
import json
from typing import Any
try:
import plotly.graph_objects as go
from plotly.subplots import make_subplots
HAS_PLOTLY = True
except ImportError:
HAS_PLOTLY = False
def plot_improvement_trajectory(generations: list[dict[str, float]]) -> Any:
"""Plot Time-to-Containment vs Org-Efficiency across self-improvement generations."""
if not HAS_PLOTLY or not generations:
return None
gens = [g["generation"] for g in generations]
ttc = [g["time_to_containment"] for g in generations]
eff = [g["org_efficiency"] for g in generations]
reward = [g["total_reward"] for g in generations]
complexity = [g["attack_complexity"] for g in generations]
fig = make_subplots(
rows=2, cols=2,
subplot_titles=(
"Time-to-Containment (↓ better)",
"Org Efficiency (↑ better)",
"Total Reward (↑ better)",
"Attack Complexity Handled (↑ better)",
),
)
fig.add_trace(go.Scatter(x=gens, y=ttc, mode="lines+markers", name="TTC",
line=dict(color="#ef4444", width=3)), row=1, col=1)
fig.add_trace(go.Scatter(x=gens, y=eff, mode="lines+markers", name="Efficiency",
line=dict(color="#22c55e", width=3)), row=1, col=2)
fig.add_trace(go.Scatter(x=gens, y=reward, mode="lines+markers", name="Reward",
line=dict(color="#3b82f6", width=3)), row=2, col=1)
fig.add_trace(go.Scatter(x=gens, y=complexity, mode="lines+markers", name="Complexity",
line=dict(color="#f59e0b", width=3)), row=2, col=2)
fig.update_layout(
title="ImmunoOrg Self-Improvement Trajectory",
template="plotly_dark",
height=600,
showlegend=False,
)
fig.update_xaxes(title_text="Generation")
return fig
def plot_curriculum_progress(level_stats: dict[int, dict]) -> Any:
"""Plot curriculum progression with success rates per level."""
if not HAS_PLOTLY:
return None
levels = list(level_stats.keys())
episodes = [level_stats[l]["episodes"] for l in levels]
success_rates = [level_stats[l]["success_rate"] * 100 for l in levels]
fig = go.Figure()
fig.add_trace(go.Bar(
x=[f"Level {l}" for l in levels], y=episodes,
name="Episodes", marker_color="#6366f1",
))
fig.add_trace(go.Scatter(
x=[f"Level {l}" for l in levels], y=success_rates,
mode="lines+markers", name="Success Rate %",
yaxis="y2", line=dict(color="#f59e0b", width=3),
))
fig.update_layout(
title="Curriculum Progress",
template="plotly_dark",
yaxis=dict(title="Episodes"),
yaxis2=dict(title="Success Rate %", overlaying="y", side="right", range=[0, 100]),
height=400,
)
return fig
def plot_belief_accuracy_convergence(accuracy_history: list[float]) -> Any:
"""Plot how belief map accuracy converges over steps."""
if not HAS_PLOTLY or not accuracy_history:
return None
fig = go.Figure()
fig.add_trace(go.Scatter(
y=accuracy_history, mode="lines",
fill="tozeroy", fillcolor="rgba(59, 130, 246, 0.2)",
line=dict(color="#3b82f6", width=2),
))
fig.add_hline(y=0.8, line_dash="dash", line_color="#22c55e",
annotation_text="Target: 80%")
fig.update_layout(
title="Belief Map Accuracy Convergence",
template="plotly_dark",
xaxis_title="Step",
yaxis_title="Accuracy",
yaxis=dict(range=[0, 1]),
height=350,
)
return fig
def plot_reward_breakdown(partial_rewards: dict[str, float]) -> Any:
"""Plot breakdown of reward components."""
if not HAS_PLOTLY or not partial_rewards:
return None
labels = list(partial_rewards.keys())
values = list(partial_rewards.values())
colors = ["#22c55e" if v >= 0 else "#ef4444" for v in values]
fig = go.Figure(go.Bar(
x=values, y=labels, orientation="h",
marker_color=colors,
))
fig.update_layout(
title="Reward Component Breakdown",
template="plotly_dark",
xaxis_title="Reward",
height=350,
)
return fig
def generate_proof_of_improvement_report(
trajectory: list[dict], curriculum: dict, partial_rewards: dict
) -> str:
"""Generate a text summary for the proof-of-improvement."""
lines = ["# ImmunoOrg β€” Proof of Improvement Report\n"]
if trajectory:
first = trajectory[0]
last = trajectory[-1]
ttc_improvement = ((first.get("time_to_containment", 1) - last.get("time_to_containment", 1))
/ max(0.01, first.get("time_to_containment", 1))) * 100
eff_improvement = ((last.get("org_efficiency", 0) - first.get("org_efficiency", 0))
/ max(0.01, first.get("org_efficiency", 1))) * 100
lines.append(f"## Self-Improvement: {len(trajectory)} Generations")
lines.append(f"- Time-to-Containment: **{ttc_improvement:+.1f}%** improvement")
lines.append(f"- Org Efficiency: **{eff_improvement:+.1f}%** improvement")
lines.append(f"- Best Reward: **{max(g.get('total_reward', 0) for g in trajectory):.3f}**\n")
if curriculum:
lines.append(f"## Curriculum: Level {curriculum.get('current_level', '?')}")
lines.append(f"- Total Episodes: {curriculum.get('total_episodes', 0)}")
lines.append(f"- Consecutive Successes: {curriculum.get('consecutive_successes', 0)}\n")
if partial_rewards:
lines.append("## Reward Breakdown")
for k, v in sorted(partial_rewards.items(), key=lambda x: x[1], reverse=True):
sign = "+" if v >= 0 else ""
lines.append(f"- {k}: {sign}{v:.3f}")
return "\n".join(lines)