from __future__ import annotations import argparse import csv from pathlib import Path import matplotlib.pyplot as plt import numpy as np from matplotlib import font_manager BASE_FONT_SIZE = 25 TITLE_FONT_SIZE = BASE_FONT_SIZE + 1 AXIS_LABEL_FONT_SIZE = BASE_FONT_SIZE TICK_FONT_SIZE = BASE_FONT_SIZE - 2 LEGEND_FONT_SIZE = BASE_FONT_SIZE - 2 BAR_LABEL_FONT_SIZE = BASE_FONT_SIZE - 4 MAX_LOSS = 0.02 def configure_fonts() -> None: preferred_fonts = [ "Microsoft YaHei", "SimHei", "Noto Sans CJK SC", "Source Han Sans SC", "PingFang SC", "WenQuanYi Micro Hei", "Arial Unicode MS", ] installed_fonts = {font.name for font in font_manager.fontManager.ttflist} for font in preferred_fonts: if font in installed_fonts: plt.rcParams["font.sans-serif"] = [font] break plt.rcParams["axes.unicode_minus"] = False plt.rcParams.update( { "font.size": BASE_FONT_SIZE, "axes.titlesize": TITLE_FONT_SIZE, "axes.labelsize": AXIS_LABEL_FONT_SIZE, "xtick.labelsize": TICK_FONT_SIZE, "ytick.labelsize": TICK_FONT_SIZE, "legend.fontsize": LEGEND_FONT_SIZE, "figure.dpi": 150, } ) def read_throughput(csv_path: Path) -> tuple[np.ndarray, np.ndarray, np.ndarray]: losses: list[float] = [] cubic: list[float] = [] fec: list[float] = [] with csv_path.open("r", encoding="utf-8-sig", newline="") as file: reader = csv.DictReader(file) for row in reader: losses.append(float(row["loss"])) cubic.append(float(row["cubic_thpt_mbps"])) fec.append(float(row["fec_thpt_mbps"])) return np.array(losses), np.array(cubic), np.array(fec) def format_loss_label(loss: float) -> str: if loss == 0: return "0" percent = loss * 100 return f"{percent:g}%" def filter_results( losses: np.ndarray, cubic: np.ndarray, fec: np.ndarray, ) -> tuple[np.ndarray, np.ndarray, np.ndarray]: keep = losses <= MAX_LOSS return losses[keep], cubic[keep], fec[keep] def plot_absolute_throughput(losses: np.ndarray, cubic: np.ndarray, fec: np.ndarray, output_path: Path) -> None: labels = [format_loss_label(loss) for loss in losses] fig, ax = plt.subplots(figsize=(5.6, 5.0), constrained_layout=True) ax.plot(losses, cubic, marker="o", linewidth=2.4, markersize=7, label="直接转发", color='#4285F4') ax.plot(losses, fec, marker="s", linewidth=2.4, markersize=7, label="本文方法", color='#EA4335') ax.set_xlabel("丢包率") ax.set_ylabel("吞吐量(Mbps)") ax.set_xticks(losses, labels) plt.setp(ax.get_xticklabels(), rotation=-35, ha="left", rotation_mode="anchor") ax.grid(axis="y", linestyle="--", alpha=0.35) ax.grid(axis="x", linestyle="--", alpha=0.22) ax.legend(frameon=False) output_path.parent.mkdir(parents=True, exist_ok=True) fig.savefig(output_path, bbox_inches="tight") plt.close(fig) def plot_speedup(losses: np.ndarray, cubic: np.ndarray, fec: np.ndarray, output_path: Path) -> None: speedup = fec / cubic labels = [format_loss_label(loss) for loss in losses] category_x = np.arange(len(labels)) fig, ax = plt.subplots(figsize=(5.6, 5.0), constrained_layout=True) bars = ax.bar(category_x, speedup, color="#4285F4", width=0.62) ax.axhline(1.0, color="#444444", linewidth=1.2, linestyle="--") ax.set_xlabel("丢包率") ax.set_ylabel("相对吞吐提升") ax.set_xticks(category_x, labels) ax.grid(axis="y", linestyle="--", alpha=0.35) ax.set_ymargin(0.1) for bar, value in zip(bars, speedup): ax.text( bar.get_x() + bar.get_width() / 2, bar.get_height(), f"{value:.1f}x", ha="center", va="bottom", fontsize=BAR_LABEL_FONT_SIZE, ) output_path.parent.mkdir(parents=True, exist_ok=True) fig.savefig(output_path, bbox_inches="tight") plt.close(fig) def plot_throughput(csv_path: Path, absolute_output_path: Path, speedup_output_path: Path) -> None: configure_fonts() losses, cubic, fec = read_throughput(csv_path) losses, cubic, fec = filter_results(losses, cubic, fec) plot_absolute_throughput(losses, cubic, fec, absolute_output_path) plot_speedup(losses, cubic, fec, speedup_output_path) def parse_args() -> argparse.Namespace: parser = argparse.ArgumentParser(description="Plot throughput comparison under different loss rates.") parser.add_argument("--input", type=Path, default=Path("scripts/thpt.csv"), help="Path to throughput CSV.") parser.add_argument( "--absolute-output", type=Path, default=Path("figures/thpt_absolute.pdf"), help="Output path for the absolute throughput figure.", ) parser.add_argument( "--speedup-output", type=Path, default=Path("figures/thpt_speedup.pdf"), help="Output path for the relative speedup figure.", ) return parser.parse_args() def main() -> None: args = parse_args() plot_throughput(args.input, args.absolute_output, args.speedup_output) if __name__ == "__main__": main()