import osimport numpy as npimport matplotlib.pyplot as pltfrom matplotlib.colors import LinearSegmentedColormap, Normalizefrom matplotlib.lines import Line2D# 0. Output directoryOUTDIR = "sensitivity_spider_output_fixed"os.makedirs(OUTDIR, exist_ok=True)plt.rcParams["font.family"] = "DejaVu Sans"plt.rcParams["axes.unicode_minus"] = False# 1. Simulated dataparam_labels = [ "Elastic E", "Poisson ν", "Cohesion c", "Friction φ", "Density ρ", "Perm. k", "Stress σ", "Rate v"]schemes = { "Model A": np.array([0.72, 0.41, 0.88, 0.79, 0.54, 0.46, 0.83, 0.58]), "Model B": np.array([0.63, 0.52, 0.81, 0.73, 0.60, 0.49, 0.77, 0.66]), "Model C": np.array([0.82, 0.36, 0.92, 0.85, 0.47, 0.43, 0.88, 0.51]), "Model D": np.array([0.58, 0.47, 0.76, 0.69, 0.63, 0.55, 0.71, 0.72]),}scheme_colors = { "Model A": "#355C7D", "Model B": "#6C5B7B", "Model C": "#C06C84", "Model D": "#F67280",}heat_data = np.array([ [0.62, 0.44, 0.85, 0.76, 0.55, 0.42, 0.81, 0.60], [0.70, 0.48, 0.88, 0.81, 0.50, 0.46, 0.84, 0.57], [0.78, 0.40, 0.91, 0.86, 0.46, 0.45, 0.89, 0.52], [0.67, 0.53, 0.83, 0.74, 0.61, 0.50, 0.76, 0.64], [0.73, 0.46, 0.87, 0.80, 0.57, 0.48, 0.82, 0.59],])mean_profile = heat_data.mean(axis=0)top_idx = np.argsort(mean_profile)[::-1][:3]def close_curve(values): return np.concatenate([values, [values[0]]])def radar_angles(n): ang = np.linspace(0, 2 * np.pi, n, endpoint=False) return np.concatenate([ang, [ang[0]]])angles = radar_angles(len(param_labels))theta = np.linspace(0, 2 * np.pi, len(param_labels), endpoint=False)def style_theta_labels(ax, theta_vals, fontsize=12, pad=12): """ 让极坐标一圈文字往外一点,并根据位置调整对齐方式 """ ax.set_xticks(theta_vals) ax.set_xticklabels(param_labels, fontsize=fontsize) ax.tick_params(axis="x", pad=pad) for label, ang in zip(ax.get_xticklabels(), np.degrees(theta_vals)): ang = ang % 360 if ang == 0 or ang == 180: label.set_horizontalalignment("center") elif 0 < ang < 180: label.set_horizontalalignment("left") else: label.set_horizontalalignment("right")def add_radar_base(ax, rmax=1.0, theta_pad=12): ax.set_theta_zero_location("N") ax.set_theta_direction(-1) ax.set_ylim(0, rmax) style_theta_labels(ax, theta, fontsize=12, pad=theta_pad) yticks = np.linspace(0.2, rmax, 5) ax.set_yticks(yticks) ax.set_yticklabels([f"{v:.1f}" for v in yticks], fontsize=10, color="gray") ax.grid(True, linestyle="--", linewidth=0.8, alpha=0.35) ax.spines["polar"].set_alpha(0.35)def soft_fill(ax, ang, vals, color, alpha=0.16): ax.fill(ang, vals, color=color, alpha=alpha, zorder=2)# 3. Figure 1def plot_advanced_radar_compare(out_png): fig = plt.figure(figsize=(10.8, 10.2), dpi=240, facecolor="white") ax = plt.subplot(111, projection="polar", facecolor="white") add_radar_base(ax, rmax=1.05, theta_pad=13) # 背景圆环 for r in [0.2, 0.4, 0.6, 0.8, 1.0]: ax.plot( np.linspace(0, 2 * np.pi, 400), np.full(400, r), color="#d8dee9", lw=0.7, alpha=0.45, zorder=0 ) # 多模型对比 for name, vals in schemes.items(): vals_c = close_curve(vals) color = scheme_colors[name] ax.plot(angles, vals_c, color="white", lw=5.0, alpha=0.95, zorder=2) ax.plot(angles, vals_c, color=color, lw=2.5, alpha=0.98, zorder=3) soft_fill(ax, angles, vals_c, color=color, alpha=0.12) ax.scatter( angles[:-1], vals, s=42, color=color, edgecolors="white", linewidths=0.9, zorder=4 ) # 高敏感参数方向高亮 for idx in top_idx: ang = theta[idx] ax.plot([ang, ang], [0, 1.02], color="#f4a261", lw=1.4, alpha=0.55, zorder=1) ax.scatter([ang], [1.02], s=34, color="#f4a261", edgecolors="white", linewidths=0.8, zorder=5) ax.set_title( "Advanced Parameter Sensitivity Spider Chart", fontsize=22, pad=28, fontweight="bold" ) legend_elements = [ Line2D([0], [0], color=scheme_colors[k], lw=2.8, label=k) for k in schemes.keys() ] ax.legend( handles=legend_elements, loc="upper right", bbox_to_anchor=(1.18, 1.09), frameon=True, fontsize=11 ) plt.tight_layout() plt.savefig(out_png, bbox_inches="tight", facecolor="white") plt.show()# 4. Figure 2def plot_advanced_radar_heat(out_png): fig = plt.figure(figsize=(11.2, 10.8), dpi=260, facecolor="white") ax = plt.subplot(111, projection="polar", facecolor="white") ax.set_theta_zero_location("N") ax.set_theta_direction(-1) # 外面多留一些空间给 Peak 标注 ax.set_ylim(0, 1.55) style_theta_labels(ax, theta, fontsize=12, pad=13) ax.set_yticks([0.3, 0.6, 0.9, 1.2]) ax.set_yticklabels(["0.3", "0.6", "0.9", "1.2"], fontsize=10, color="gray") ax.grid(True, linestyle="--", linewidth=0.8, alpha=0.30) ax.spines["polar"].set_alpha(0.35) cmap = LinearSegmentedColormap.from_list( "radar_heat", ["#dbe4ee", "#7fb3e6", "#5f8ee6", "#8b5ce6", "#d94696"] ) norm = Normalize(vmin=heat_data.min(), vmax=heat_data.max()) n_layers = heat_data.shape[0] n_params = heat_data.shape[1] r0 = 0.18 dr = 0.17 width = 2 * np.pi / n_params # 分层热力环带 for i in range(n_layers): bottom = r0 + i * dr for j in range(n_params): val = heat_data[i, j] color = cmap(norm(val)) ax.bar( theta[j], height=dr * 0.88, width=width * 0.96, bottom=bottom, color=color, edgecolor="white", linewidth=1.0, align="center", alpha=0.95, zorder=2 ) # 平均蜘蛛轮廓 mean_r = 0.20 + 0.95 * mean_profile mean_r_c = close_curve(mean_r) ax.plot(angles, mean_r_c, color="#1f2937", lw=2.8, alpha=0.97, zorder=5) ax.fill(angles, mean_r_c, color="#1f2937", alpha=0.05, zorder=4) ax.scatter( angles[:-1], mean_r, s=42, color="#111827", edgecolors="white", linewidths=0.8, zorder=6 ) # 外侧趋势线 prof_norm = (mean_profile - mean_profile.min()) / (mean_profile.max() - mean_profile.min() + 1e-12) outer_base = 1.20 outer_curve = outer_base + 0.13 * prof_norm outer_curve_c = close_curve(outer_curve) ax.plot(angles, outer_curve_c, color="#17b2cf", lw=2.4, alpha=0.95, zorder=7) ax.fill(angles, outer_curve_c, outer_base, color="#17b2cf", alpha=0.10, zorder=3) # 峰值标注:只保留 Peak 1/2/3,并手动错开 peak_rank = np.argsort(mean_profile)[::-1][:3] # 每个峰值的角度偏移(度)、半径偏移、对齐方式 # 用来避免和外圈参数标签打架 peak_offsets = [ (-12, 0.10, "right"), # Peak 1 ( 10, 0.12, "left"), # Peak 2 ( 16, 0.11, "left"), # Peak 3 ] for n, idx in enumerate(peak_rank): ang = theta[idx] rr = outer_curve[idx] d_ang_deg, d_r, ha = peak_offsets[n] text_ang = ang + np.deg2rad(d_ang_deg) text_r = rr + d_r ax.scatter( [ang], [rr], s=60, color="#f59e0b", edgecolors="white", linewidths=0.9, zorder=8 ) ax.annotate( f"Peak {n+1}", xy=(ang, rr), xytext=(text_ang, text_r), textcoords="data", fontsize=11, color="black", ha=ha, va="center", arrowprops=dict( arrowstyle="->", color="black", lw=0.9, alpha=0.9, shrinkA=0, shrinkB=4 ), zorder=9 ) ax.set_title( "Heat-Enhanced Parameter Sensitivity Spider Chart", fontsize=22, pad=30, fontweight="bold" ) sm = plt.cm.ScalarMappable(cmap=cmap, norm=norm) sm.set_array([]) cbar = plt.colorbar(sm, ax=ax, pad=0.08, fraction=0.045) cbar.set_label("Sensitivity Index", fontsize=12) cbar.ax.tick_params(labelsize=10) plt.tight_layout() plt.savefig(out_png, bbox_inches="tight", facecolor="white") plt.show()def main(): out1 = os.path.join(OUTDIR, "sensitivity_spider_advanced_1_fixed.png") out2 = os.path.join(OUTDIR, "sensitivity_spider_advanced_2_fixed.png") plot_advanced_radar_compare(out1) plot_advanced_radar_heat(out2)if __name__ == "__main__": main()