#!/usr/bin/env python # -*- coding: utf-8 -*- """Figure A2: wall-clock time of the four computational kernels. Input: ../Data/stats/time/. Output: ../Figures/FigA2_Kernel_Timing_v1.png and .pdf. Run from this folder; ../Data holds the inputs and the figure is written to ../Figures. """ from pathlib import Path import numpy as np import matplotlib as mpl import matplotlib.pyplot as plt # ============================================================================ # Style # ============================================================================ mpl.rcParams.update({ "font.family": "Helvetica", "font.size": 10, "axes.labelsize": 11, "legend.fontsize": 9, "xtick.labelsize": 10, "ytick.labelsize": 9, "mathtext.fontset": "cm", "axes.linewidth": 0.8, }) # ============================================================================ # Paths # ============================================================================ HERE = Path(__file__).resolve().parent STATS_DIR = HERE.parent / "Data" / "stats" / "time" FIG_DIR = HERE.parent / "Figures" FIG_DIR.mkdir(parents=True, exist_ok=True) # ============================================================================ # Timing-file parser # ============================================================================ def parse_kv(path): """Sum io / core / write / total (s) across all lines.""" sums = {"io": 0.0, "core": 0.0, "write": 0.0, "total": 0.0} n = 0 with open(path) as f: for line in f: line = line.strip() if not line: continue for token in line.split(): if "=" not in token: continue k, v = token.split("=", 1) v = v.rstrip("s") try: v = float(v) except ValueError: continue if k in sums: sums[k] += v n += 1 return n, sums def kernel_stats(path): """Return (n, io_h, compute_h, total_h).""" n, s = parse_kv(path) io_s = s["io"] + s["write"] total_s = s["total"] compute_s = max(0.0, total_s - io_s) return n, io_s / 3600.0, compute_s / 3600.0, total_s / 3600.0 # ============================================================================ # Main # ============================================================================ def main(): # (display label, sub-label, timing file) kernels = [ ("Derive DFS", "dfs_in / dfs_out\n(2 topological indices)", "derive_dfs_interval_time.txt"), ("Upstream aggregation", "mean / min / max / std elv +\nmean / std slp\n(6 pixel attributes)", "relief_hypsometry_pixel_time.txt"), ("Boundary walk", "basin length / basin width /\nperimeter / convexity\n(4 pixel attributes)", "exp_bf_boundary_axial_time.txt"), ("Arithmetic", "circularity / gravelius / elongation /\nlemniscate + relief + HI\n(6 pixel attributes)", "exp_bf_arith_shape_time.txt"), ] labels, sublabels, io_h, cp_h, tot_h, ns = [], [], [], [], [], [] for label, sub, fname in kernels: n, io, cp, tot = kernel_stats(STATS_DIR / fname) labels.append(label) sublabels.append(sub) io_h.append(io) cp_h.append(cp) tot_h.append(tot) ns.append(n) print(f" {label:28s} n={n:3d} I/O={io:5.2f}h " f"Compute={cp:5.2f}h Total={tot:5.2f}h") io_h = np.array(io_h) cp_h = np.array(cp_h) tot_h = np.array(tot_h) # ---- Plot: 4 vertical stacked bars ---- fig, ax = plt.subplots(figsize=(7.6, 5.0)) x = np.arange(len(labels)) width = 0.55 c_io = "#D9D9D9" # light gray for I/O c_cp = "#21918c" # viridis-deep teal for Compute (paper accent) ax.bar(x, io_h, width, color=c_io, edgecolor="black", lw=0.7, label="I/O (GeoTIFF read + write, incl. (de)compression)") ax.bar(x, cp_h, width, bottom=io_h, color=c_cp, edgecolor="black", lw=0.7, label="Compute (algorithm + stream setup + allocation)") # Total label on top for i, t in enumerate(tot_h): ax.text(x[i], t + max(tot_h) * 0.06, f"Total {t:.2f} h", ha="center", va="bottom", fontsize=10, fontweight="bold") # Inline numbers (no percentages) for i in range(len(labels)): if io_h[i] >= 0.3: ax.text(x[i], io_h[i] / 2, f"{io_h[i]:.2f} h", ha="center", va="center", fontsize=9, color="black") if cp_h[i] >= 0.3: ax.text(x[i], io_h[i] + cp_h[i] / 2, f"{cp_h[i]:.2f} h", ha="center", va="center", fontsize=9, color="white", fontweight="bold") # (white text on dark gray still readable) elif cp_h[i] > 0: ax.text(x[i], io_h[i] + cp_h[i] + max(tot_h) * 0.015, f"Compute {cp_h[i]:.2f} h", ha="center", va="bottom", fontsize=8, color="0.2") # Multi-line xticklabels xtick_labels = [f"{lab}\n\n{sub}" for lab, sub in zip(labels, sublabels)] ax.set_xticks(x) ax.set_xticklabels(xtick_labels, fontsize=8.5) ax.set_ylabel("Global wall-clock time (hours)") ax.set_ylim(0, max(tot_h) * 1.20) ax.legend(loc="upper left", frameon=True, framealpha=0.95, edgecolor="0.7") ax.grid(True, axis="y", lw=0.3, alpha=0.3) ax.spines["top"].set_visible(False) ax.spines["right"].set_visible(False) fig.subplots_adjust(bottom=0.30, top=0.96, left=0.09, right=0.98) stem = "FigA2_Kernel_Timing_v1" for ext in ("png", "pdf"): out = FIG_DIR / f"{stem}.{ext}" fig.savefig(out, dpi=300, bbox_inches="tight", facecolor="white") print(f" wrote {out}") if __name__ == "__main__": main()