import os
import sys
from datetime import datetime
from typing import Any, Sequence
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import seaborn as sns
from matplotlib.ticker import PercentFormatter
[docs]
def set_plot_theme(dpi: int) -> None:
sns.set_theme(context="paper", style="ticks")
plt.rcParams["figure.dpi"] = dpi
plt.rcParams["savefig.dpi"] = dpi
[docs]
def build_metric_specs(
v_label: str,
save_path: str,
run_dir: str,
sch_suffix: str,
x: str = "p_packet",
group_column: str | None = None,
group_labels: dict | None = None,
group_palette: Sequence[str] | None = None,
group_palette_map: dict[str, Any] | None = None,
individual_values: Sequence[int | float] | None = None,
group_line_styles: dict[str, str] | None = None,
create_individual: bool = False,
overlay_multipath: bool = False,
y_start_zero: bool = False,
) -> list[dict[str, Any]]:
metric_templates = [
{
"key": "throughput",
"ylabel": "Throughput (completed PGAs/s)",
},
{
"key": "completed_ratio",
"ylabel": "Completion ratio (%)",
"percentage": True,
},
{
"key": "drop_ratio",
"ylabel": "Drop ratio (%)",
"percentage": True,
},
{
"key": "avg_link_utilization",
"ylabel": "Average link utilization (%)",
"percentage": True,
},
{
"key": "p95_link_utilization",
"ylabel": "95th percentile link utilization (%)",
"percentage": True,
},
{
"key": "avg_waiting_time",
"ylabel": "Average waiting time (s)",
},
{
"key": "p95_link_avg_wait",
"ylabel": "95th percentile link average wait (s)",
},
{
"key": "avg_queue_length",
"ylabel": "Average queue length",
},
{
"key": "p95_avg_queue_length",
"ylabel": "95th percentile average queue length",
},
{
"key": "blocking_prob",
"ylabel": "Blocking probability",
},
{
"key": "avg_turnaround_time",
"ylabel": "Average turnaround time (s)",
},
{
"key": "p95_waiting_time",
"ylabel": "95th percentile waiting time (s)",
},
{
"key": "p95_turnaround_time",
"ylabel": "95th percentile turnaround time (s)",
},
{
"key": "p95_burst_time",
"ylabel": "95th percentile burst time (s)",
},
{
"key": "avg_e2e_fidelity",
"ylabel": "Average E2E fidelity",
},
{
"key": "avg_pga_duration",
"ylabel": "Average PGA duration (s)",
},
{
"key": "avg_defer_per_pga",
"ylabel": "Average deferrals per PGA",
},
{
"key": "avg_active_pgas",
"ylabel": "Average active PGAs",
},
{
"key": "avg_hops",
"ylabel": "Average number of hops",
},
{
"key": "fastest_path_rate",
"ylabel": "Fastest-path rate",
"percentage": True,
},
{
"key": "top5_busy_share",
"ylabel": "Top 5 busy time-share (%)",
"percentage": True,
},
{
"key": "fairness",
"ylabel": "Fairness",
},
{
"key": "routing_decision_count",
"ylabel": "Routing decisions",
"yscale": "log",
},
{
"key": "routing_decision_runtime",
"ylabel": "Routing computation time (s)",
"yscale": "log",
}
]
specs = []
indiv_values = list(individual_values or [])
for template in metric_templates:
spec = template.copy()
if y_start_zero and not spec.get("yscale"):
spec["ymin"] = 0
if spec["key"] == "admission_rate":
spec["plot_path"] = save_path
else:
file = f"{spec['key']}_vs_{x}_vary_{v_label}_{sch_suffix}.png"
spec["plot_path"] = os.path.join(run_dir, file)
spec["x_var"] = x
spec["group_column"] = group_column
spec["group_labels"] = group_labels
spec["group_palette"] = group_palette
spec["group_palette_map"] = group_palette_map
spec["group_line_styles"] = group_line_styles
spec["overlay_multipath"] = overlay_multipath
specs.append(spec)
if create_individual:
for v in indiv_values:
i = template.copy()
if y_start_zero and not i.get("yscale"):
i["ymin"] = 0
file = f"{i['key']}_vs_{x}_value_{v}_{sch_suffix}.png"
i["plot_path"] = os.path.join(run_dir, file)
i["x_var"] = x
i["filter_column"] = (
"p_packet" if x == "arrival_rate" else "arrival_rate"
)
i["filter_value"] = float(v)
i["color_override"] = group_palette_map.get(str(v))
i["overlay_multipath"] = overlay_multipath
specs.append(i)
return specs
[docs]
def render_plot(
spec: dict[str, Any],
raw_data: pd.DataFrame,
figsize: tuple[float, float],
dpi: int,
) -> None:
metric = spec["key"]
x_var = spec.get("x_var", "p_packet")
group_column = spec.get("group_column")
group_labels = spec.get("group_labels") or {}
group_palette = spec.get("group_palette")
palette_map = spec.get("group_palette_map") or {}
group_line_styles = spec.get("group_line_styles") or {}
color_override = spec.get("color_override")
filter_column = spec.get("filter_column")
filter_value = spec.get("filter_value")
base_color = color_override or sns.color_palette("tab10", 1)[0]
mp_metric = f"multipath_{metric}"
overlay = (
bool(spec.get("overlay_multipath"))
and not metric.startswith("multipath_")
and mp_metric in raw_data.columns
)
cols = [x_var, metric]
if overlay:
cols.append(mp_metric)
if group_column:
cols.append(group_column)
if filter_column:
cols.append(filter_column)
data = raw_data[cols]
if filter_column and filter_value is not None:
data = data[data[filter_column].isin(np.atleast_1d(filter_value))]
x_values = sorted(data[x_var].unique())
use_categorical = x_var not in ("p_packet", "load", "arrival_rate")
x_map = {val: idx for idx, val in enumerate(x_values)}
def _summarize(metric_col: str) -> pd.DataFrame:
keys = [x_var] + ([group_column] if group_column else [])
s = data.groupby(keys, as_index=False).agg(
mean=(metric_col, "mean"),
std=(metric_col, "std"),
count=(metric_col, "count"),
)
sem = s["std"] / np.sqrt(s["count"].clip(lower=1))
sem = sem.where(s["count"] >= 2)
ci95 = 1.96 * sem
s["lower"] = s["mean"] - ci95
s["upper"] = s["mean"] + ci95
if use_categorical:
s["x_plot"] = s[x_var].map(x_map)
else:
s["x_plot"] = s[x_var]
if group_column:
s["group_display"] = s[group_column].apply(
lambda x: group_labels.get(
x, group_labels.get(str(x), str(x))
)
)
return s
fig, ax = plt.subplots(figsize=figsize, dpi=dpi)
def _draw(sdf: pd.DataFrame, linestyle: str, scope_label: str) -> None:
if group_column:
group_values = sdf[group_column].dropna().unique().tolist()
base_palette = (
list(group_palette)
if group_palette
else sns.color_palette("tab10", max(1, len(group_values)))
)
markers = ["o", "s", "D", "^", "v"]
for i, gv in enumerate(group_values):
gdf = sdf[sdf[group_column] == gv]
disp = gdf["group_display"].iloc[0]
col = palette_map.get(
str(gv), base_palette[i % len(base_palette)]
)
style = (
linestyle
if scope_label
else group_line_styles.get(str(gv), "-")
)
label = f"{disp} ({scope_label})" if scope_label else disp
ax.plot(
gdf["x_plot"],
gdf["mean"],
marker=markers[i % len(markers)],
linestyle=style,
color=col,
label=label,
)
ax.fill_between(
gdf["x_plot"],
gdf["lower"],
gdf["upper"],
color=col,
alpha=0.2,
)
else:
ax.plot(
sdf["x_plot"],
sdf["mean"],
marker="o",
linestyle=linestyle,
color=base_color,
label=scope_label or None,
)
ax.fill_between(
sdf["x_plot"],
sdf["lower"],
sdf["upper"],
color=base_color,
alpha=0.2,
)
summary_df = _summarize(metric)
_draw(summary_df, "-", "All-paths" if overlay else "")
if overlay:
_draw(_summarize(mp_metric), "--", "Multi-path")
if group_column or overlay:
ax.legend()
if spec.get("yscale"):
if summary_df["mean"].gt(0).any():
ax.set_yscale(spec["yscale"])
ymin = spec.get("ymin")
ymax = spec.get("ymax")
if ymin is not None or ymax is not None:
ax.set_ylim(ymin, ymax)
if spec.get("percentage"):
ax.yaxis.set_major_formatter(
PercentFormatter(xmax=1.0, decimals=2)
)
xlabel_map = {
"p_packet": r"$p_{\mathrm{packet}}$",
"load": "Load",
"n_apps": "Number of applications",
"arrival_rate": r"Arrival rate $\lambda$",
"inst_range": (
"Average number of instances per application"
),
}
ax.set_xlabel(xlabel_map.get(x_var, x_var))
ax.set_ylabel(spec["ylabel"])
if use_categorical:
ax.set_xticks(range(len(x_values)))
ax.set_xticklabels([f"{int(v)}" for v in x_values])
else:
ax.set_xticks(x_values)
ax.margins(x=0, y=0.05)
plot_path = spec["plot_path"]
os.makedirs(os.path.dirname(plot_path), exist_ok=True)
fig.savefig(plot_path, bbox_inches="tight")
plt.close(fig)
[docs]
def plot_metrics_vs_ppacket(
raw_csv_path: str,
save_path: str | None = None,
figsize: tuple[float, float] = (7, 4.5),
dpi: int = 600,
gp_column: str | None = None,
gp_labels: dict | None = None,
group_palette: Sequence[str] | None = None,
n_apps_values: Sequence[int] | None = None,
scheduler: str | None = None,
create_individual: bool = False,
overlay_multipath: bool = False,
) -> pd.DataFrame:
results_df = pd.read_csv(raw_csv_path)
run_dir = os.path.dirname(raw_csv_path) or "."
if n_apps_values is None:
apps_list = results_df["n_apps"].dropna().astype(int).unique().tolist()
apps_list.sort()
else:
apps_list = [int(v) for v in n_apps_values]
n_apps_label = str(apps_list[0]) if len(apps_list) == 1 else "varied"
scheduler_value = scheduler or "dynamic"
scheduler_suffix = scheduler_value.title()
if not save_path:
save_path = os.path.join(
run_dir,
f"admission_rate_vs_ppacket_{n_apps_label}_{scheduler_suffix}.png",
)
if not gp_column and len(apps_list) > 1:
gp_column = "n_apps"
if not gp_labels and gp_column == "n_apps":
gp_labels = {v: f"{v} apps" for v in apps_list}
plt_map = None
if gp_column:
keys = apps_list if gp_column == "n_apps" else list(gp_labels.keys())
plt = (
list(group_palette)
if group_palette
else sns.color_palette("tab10", len(keys))
)
plt_map = {str(k): plt[i % len(plt)] for i, k in enumerate(keys)}
metrics_to_plot = build_metric_specs(
v_label=n_apps_label,
save_path=save_path,
run_dir=run_dir,
sch_suffix=scheduler_suffix,
x="p_packet",
group_column=gp_column,
group_labels=gp_labels,
group_palette=group_palette,
group_palette_map=plt_map,
individual_values=apps_list if len(apps_list) > 1 else None,
create_individual=create_individual,
overlay_multipath=overlay_multipath,
)
set_plot_theme(dpi)
for spec in metrics_to_plot:
render_plot(
spec=spec,
raw_data=results_df,
figsize=figsize,
dpi=dpi,
)
return results_df
[docs]
def plot_metrics_vs_load(
path: str | Sequence[str],
save_path: str | None = None,
figsize: tuple[float, float] = (7, 4.5),
dpi: int = 600,
group_column: str | None = None,
gp_labels: dict | None = None,
group_palette: Sequence[str] | None = None,
p_packet_values: Sequence[float] | None = None,
scheduler: str | None = None,
create_individual: bool = False,
multi: bool = False,
x_var: str = "arrival_rate",
overlay_multipath: bool = False,
out_dir: str | None = None,
) -> pd.DataFrame:
"""Example usage:
df = plot_metrics_vs_load(
path=[
"1.csv",
"2.csv",
"3.csv",
"4.csv",
"5.csv",
],
multi=True,
gp_labels={
"1": "Precomputed",
"2": "Proactive",
"3": "Hybrid",
"4": "Reactive (wc)",
"5": "Reactive (nwc)",
},
overlay_multipath=True,
)
"""
if multi:
paths = path if isinstance(path, (list, tuple)) else [path]
dfs = [pd.read_csv(p) for p in paths]
results_df = pd.concat(dfs, ignore_index=True)
if out_dir:
run_dir = out_dir
else:
timestamp = datetime.now().strftime("%Y%m%d-%H%M%S")
run_dir = os.path.join("results", timestamp)
os.makedirs(run_dir, exist_ok=True)
if p_packet_values is None:
val_list = results_df["scenario"].dropna().unique().tolist()
val_list.sort()
else:
val_list = list(p_packet_values)
value_label = str(val_list[0]) if len(val_list) == 1 else "varied"
def_gp = "scenario"
file_prefix = "scenario"
dft_labels = {v: f"{v}" for v in val_list}
else:
results_df = pd.read_csv(path)
run_dir = out_dir or os.path.dirname(path) or "."
os.makedirs(run_dir, exist_ok=True)
if x_var == "inst_range":
val_list = (
results_df["inst_range"].dropna().astype(int).unique().tolist()
)
val_list.sort()
def_gp = "arrival_rate"
file_prefix = "inst_range"
dft_labels = {v: fr"$\lambda={v}$" for v in val_list}
else:
if p_packet_values is None:
val_list = results_df["p_packet"].dropna().unique().tolist()
val_list.sort()
else:
val_list = [float(v) for v in p_packet_values]
def_gp = "p_packet"
file_prefix = "p_packet"
dft_labels = {
v: f"$p_{{\\mathrm{{packet}}}}={v}$" for v in val_list
}
value_label = str(val_list[0]) if len(val_list) == 1 else "varied"
scheduler_value = scheduler or "dynamic"
sch_suffix = scheduler_value.title()
if not save_path:
save_path = os.path.join(
run_dir,
f"admission_rate_{file_prefix}_{value_label}_{sch_suffix}.png",
)
if not group_column and len(val_list) > 1:
group_column = def_gp
if not gp_labels and group_column == def_gp:
gp_labels = dft_labels
plt_map = None
if group_column:
keys = val_list if group_column == def_gp else list(gp_labels.keys())
plt = (
list(group_palette)
if group_palette
else sns.color_palette("tab10", len(keys))
)
plt_map = {str(k): plt[i % len(plt)] for i, k in enumerate(keys)}
metrics_to_plot = build_metric_specs(
v_label=value_label,
save_path=save_path,
run_dir=run_dir,
sch_suffix=sch_suffix,
x=x_var,
group_column=group_column,
group_labels=gp_labels,
group_palette=group_palette,
group_palette_map=plt_map,
individual_values=val_list if len(val_list) > 1 else None,
create_individual=create_individual,
overlay_multipath=overlay_multipath,
y_start_zero=True,
)
set_plot_theme(dpi)
for spec in metrics_to_plot:
render_plot(
spec=spec,
raw_data=results_df,
figsize=figsize,
dpi=dpi,
)
return results_df
[docs]
def main():
out_dir = sys.argv[1]
paths = sys.argv[2:]
plot_metrics_vs_load(
path=paths,
multi=True,
gp_labels={
"1": "Precomputed",
"2": "Proactive",
"3": "Hybrid",
"4": "Reactive",
"5": "Reactive (nwc)",
},
overlay_multipath=False,
out_dir=out_dir,
)
if __name__ == "__main__":
main()