|
| 1 | +""" |
| 2 | +Reproduce the PACE runtime processor layout plot. |
| 3 | +
|
| 4 | +The chart shows which MPI processor ranks are assigned to each component |
| 5 | +of a coupled climate model (e.g. E3SM/CESM). Each component is drawn as |
| 6 | +a filled rectangle whose y-extent gives the processor-rank range for that |
| 7 | +component. CPL (the coupler) spans all ranks; ATM shares the same rank |
| 8 | +range as CPL but is drawn with a narrower width to make it visible. |
| 9 | +
|
| 10 | +Processor layout for PACE run 224501 |
| 11 | +------------------------------------- |
| 12 | +Component | color | ranks (y) | x-width |
| 13 | +-----------+--------------+-----------------+----------- |
| 14 | +ICE | cyan | 0 – 356 | full |
| 15 | +LND | lime green | 356 – 375 | full |
| 16 | +OCN | blue-purple | 1374 – 2312 | full |
| 17 | +CPL | orange | 2312 – 2419 | full |
| 18 | +ATM | light blue | 2312 – 2419 | 107 (narrow, drawn over CPL) |
| 19 | +""" |
| 20 | + |
| 21 | +import matplotlib.pyplot as plt |
| 22 | +import matplotlib.patches as mpatches |
| 23 | + |
| 24 | +# --------------------------------------------------------------------------- |
| 25 | +# Data: (label, face_color, x_start, x_end, y_start, y_end) |
| 26 | +# x / y values are MPI processor ranks (0-based). |
| 27 | +# CPL and ATM share the same rank range; ATM is drawn on top with a narrower |
| 28 | +# x-extent so both are visible simultaneously. |
| 29 | +# --------------------------------------------------------------------------- |
| 30 | +TOTAL_PROCS = 4096 |
| 31 | + |
| 32 | +t_ice1=0 |
| 33 | +t_ice2=72.2 |
| 34 | + |
| 35 | +t_lnd1=t_ice2 |
| 36 | +t_lnd2=t_lnd1 + 3.6 |
| 37 | + |
| 38 | +t_ocn1=t_lnd2 |
| 39 | +t_ocn2=t_ocn1 + 199.7 |
| 40 | + |
| 41 | +t_atm1=t_lnd2 |
| 42 | +t_atm2=t_atm1+187.6 |
| 43 | + |
| 44 | +t_cpl1 = max(t_atm2,t_ocn2) |
| 45 | +t_cpl2 = t_cpl1 + 106.3 |
| 46 | + |
| 47 | +total = 307.2 |
| 48 | +if total > t_cpl2: |
| 49 | + print("Adding other overheads to CPL: original=",t_cpl2," new=",total) |
| 50 | + t_cpl2=total |
| 51 | + |
| 52 | + |
| 53 | +c_ocn1=0 |
| 54 | +c_ocn2=TOTAL_PROCS-256 |
| 55 | +c_atm1=c_ocn2 |
| 56 | +c_atm2=c_atm1+256 |
| 57 | + |
| 58 | + |
| 59 | + |
| 60 | + |
| 61 | +components = [ |
| 62 | + # name color x0 x1 y0 y1 |
| 63 | + ("ICE", "cyan", 0, TOTAL_PROCS, t_ice1, t_ice2), |
| 64 | + ("LND", "#00CC00", 0, TOTAL_PROCS, t_lnd1, t_lnd2), |
| 65 | + ("OCN", "#8888FF", c_ocn1, c_ocn2, t_ocn1, t_ocn2), |
| 66 | + ("CPL", "orange", 0, TOTAL_PROCS, t_cpl1, t_cpl2), |
| 67 | + # ATM shares the CPL rank range; its narrower x-width distinguishes it |
| 68 | + ("ATM", "#87CEEB", c_atm1, c_atm2, t_atm1, t_atm2), |
| 69 | +] |
| 70 | + |
| 71 | +# --------------------------------------------------------------------------- |
| 72 | +# Build figure |
| 73 | +# --------------------------------------------------------------------------- |
| 74 | +fig, ax = plt.subplots(figsize=(10, 7)) |
| 75 | + |
| 76 | +for name, color, x0, x1, y0, y1 in components: |
| 77 | + rect = mpatches.Rectangle( |
| 78 | + (x0, y0), x1 - x0, y1 - y0, |
| 79 | + linewidth=0.8, |
| 80 | + edgecolor="black", |
| 81 | + facecolor=color, |
| 82 | + zorder=2, |
| 83 | + ) |
| 84 | + ax.add_patch(rect) |
| 85 | + |
| 86 | + # Place the label at the visual centre of the rectangle |
| 87 | + cx = (x0 + x1) / 2 |
| 88 | + cy = (y0 + y1) / 2 |
| 89 | + ax.text( |
| 90 | + cx, cy, name, |
| 91 | + ha="center", va="center", |
| 92 | + fontsize=14, fontweight="bold", |
| 93 | + zorder=3, |
| 94 | + ) |
| 95 | + |
| 96 | +# --------------------------------------------------------------------------- |
| 97 | +# Axes formatting |
| 98 | +# --------------------------------------------------------------------------- |
| 99 | +ax.set_xlim(0, TOTAL_PROCS) |
| 100 | +ax.set_ylim(0, t_cpl2) |
| 101 | + |
| 102 | +# Y-axis ticks match the component boundaries visible in the original plot |
| 103 | +yticks = [t_ice1, t_ocn1, t_cpl1, t_cpl2] |
| 104 | +ax.set_yticks(yticks) |
| 105 | +ax.set_yticklabels([f"{v:.1f}" for v in yticks]) |
| 106 | + |
| 107 | +xticks = [0, 1024, 2048, 3072, 4096] |
| 108 | +ax.set_xticks(xticks) |
| 109 | +ax.set_xticklabels([f"{v:.0f}" for v in xticks]) |
| 110 | + |
| 111 | +ax.set_xlabel("Processor Cores", fontsize=12) |
| 112 | +ax.set_ylabel("Seconds per Model Day", fontsize=12) |
| 113 | + |
| 114 | +# Top x-axis in bottom-axis units, starting at 4096. |
| 115 | +ax_top = ax.secondary_xaxis( |
| 116 | + "top", |
| 117 | + functions=( |
| 118 | + lambda cores: cores + TOTAL_PROCS, |
| 119 | + lambda top_units: top_units - TOTAL_PROCS, |
| 120 | + ), |
| 121 | +) |
| 122 | +top_ticks = [4096, 5120, 6144, 7168, 8192] |
| 123 | +ax_top.set_xticks(top_ticks) |
| 124 | +ax_top.set_xticklabels([f"{v:.0f}" for v in top_ticks]) |
| 125 | +ax_top.set_xlabel("Processor Cores (+4096 offset)", fontsize=12) |
| 126 | + |
| 127 | +ax.tick_params(axis="both", labelsize=10) |
| 128 | + |
| 129 | +plt.tight_layout() |
| 130 | +plt.savefig("runtime_layout.png", dpi=100, bbox_inches="tight") |
| 131 | +plt.show() |
0 commit comments