repro-adam-zero-sum-games-ode-dynamics / official_code /experiments /accuracy_vs_compute /plot_hyperparam_sensitivity.jl
| """ | |
| Appendix Figure: Hyperparameter Sensitivity | |
| Creates a single-column figure with two panels showing hyperparameter effects: | |
| - (a) Effect of Smoothness: Different Matérn orders for GP-FVM vs GP-Collocation | |
| - (b) Effect of ρ: Different sparsity thresholds | |
| Uses hyperparameter sweep data (single seed, multiple hyperparameter configs). | |
| Usage: | |
| julia --project=../.. plot_hyperparam_sensitivity.jl --sweep results/hyperparam_sweep/sweep_*.csv | |
| julia --project=../.. plot_hyperparam_sensitivity.jl --sweep results/hyperparam_sweep/sweep_*.csv --output figures/ | |
| """ | |
| using CSV | |
| using DataFrames | |
| using CairoMakie | |
| using TuePlots | |
| using Statistics | |
| using ArgParse | |
| # ============================================================================== | |
| # Style Constants | |
| # ============================================================================== | |
| const COLORS = Dict( | |
| "sparse_fvm" => colorant"#0072B2", # Blue | |
| "sparse_collocation" => colorant"#D55E00", # Vermillion/orange | |
| "classical_fvm" => colorant"#009E73" # Teal/green | |
| ) | |
| const MARKERS = Dict( | |
| "sparse_fvm" => :circle, | |
| "sparse_collocation" => :diamond, | |
| "classical_fvm" => :utriangle | |
| ) | |
| const LABELS = Dict( | |
| "sparse_fvm" => "GP-FVM", | |
| "sparse_collocation" => "GP-Collocation", | |
| "classical_fvm" => "Classical FVM" | |
| ) | |
| const SMOOTHNESS_LABELS = Dict( | |
| 1 => "Matérn 3/2", | |
| 2 => "Matérn 5/2", | |
| 3 => "Matérn 7/2" | |
| ) | |
| # ============================================================================== | |
| # Helper Functions | |
| # ============================================================================== | |
| """ | |
| Add panel label (a), (b), etc. at top-left corner of axis. | |
| """ | |
| function add_panel_label!(ax, label; fontsize=8) | |
| text!(ax, 0.03, 0.97, text=label, align=(:left, :top), | |
| fontsize=fontsize, font=:bold, space=:relative) | |
| end | |
| """ | |
| Get best lengthscale per (method, N, smoothness, rho) config. | |
| """ | |
| function get_best_ls_per_config(df) | |
| # For classical_fvm, just return as-is | |
| classical = filter(r -> r.method == "classical_fvm", df) | |
| # For GP methods, group by (method, N, smoothness, rho) and pick best ls | |
| gp_df = filter(r -> r.method != "classical_fvm", df) | |
| result = copy(classical) | |
| for method in ["sparse_fvm", "sparse_collocation"] | |
| method_df = filter(r -> r.method == method, gp_df) | |
| if nrow(method_df) == 0 | |
| continue | |
| end | |
| for N in unique(method_df.N) | |
| n_df = filter(r -> r.N == N, method_df) | |
| for smooth in unique(n_df.smoothness) | |
| s_df = filter(r -> r.smoothness == smooth, n_df) | |
| for rho in unique(s_df.rho) | |
| r_df = filter(r -> r.rho == rho, s_df) | |
| if nrow(r_df) > 0 | |
| best_idx = argmin(r_df.rel_l2_error) | |
| push!(result, r_df[best_idx, :]) | |
| end | |
| end | |
| end | |
| end | |
| end | |
| return result | |
| end | |
| # ============================================================================== | |
| # Appendix Figure: Hyperparameter Sensitivity | |
| # ============================================================================== | |
| """ | |
| plot_hyperparam_sensitivity(csv_path; output_path, height) | |
| Create appendix figure: (a) Smoothness effect, (b) ρ effect. | |
| Single-column, 2 panels stacked. | |
| """ | |
| function plot_hyperparam_sensitivity(csv_path::String; | |
| output_path::String="figures/hyperparam_sensitivity.pdf", | |
| height::Float64=3.5) | |
| df = CSV.read(csv_path, DataFrame) | |
| # Get best lengthscale per config | |
| config_df = get_best_ls_per_config(df) | |
| # Filter to GP methods only | |
| gp_df = filter(r -> r.method != "classical_fvm", config_df) | |
| # TuePlots theme: single-column, 2 panels stacked | |
| theme = Theme( | |
| TuePlots.SETTINGS[:ICML]; | |
| font=true, fontsize=true, figsize=true, | |
| single_column=true, nrows=2, ncols=1, | |
| subplot_height_to_width_ratio=height / 3.25 # ICML single column is 3.25" | |
| ) | |
| set_theme!(theme) | |
| fig = Figure() | |
| # Linestyles for methods | |
| method_linestyles = Dict( | |
| "sparse_fvm" => :solid, | |
| "sparse_collocation" => :dash | |
| ) | |
| # ========================================================================== | |
| # (a) Effect of Smoothness - best ρ per (method, N, smoothness) | |
| # ========================================================================== | |
| ax1 = Axis(fig[1,1], | |
| ylabel=L"Relative $L^2$ error", | |
| xscale=log10, yscale=log10, | |
| xticklabelsvisible=false) | |
| # For each (method, smoothness), pick best ρ per N | |
| for method in ["sparse_fvm", "sparse_collocation"] | |
| method_df = filter(r -> r.method == method, gp_df) | |
| if nrow(method_df) == 0 | |
| continue | |
| end | |
| available_smooth = sort(unique(method_df.smoothness)) | |
| for smooth in available_smooth | |
| s_df = filter(r -> r.smoothness == smooth, method_df) | |
| # Pick best ρ for each N | |
| best_per_N = DataFrame() | |
| for N in unique(s_df.N) | |
| n_df = filter(r -> r.N == N, s_df) | |
| if nrow(n_df) > 0 | |
| best_idx = argmin(n_df.rel_l2_error) | |
| push!(best_per_N, n_df[best_idx, :]) | |
| end | |
| end | |
| sort!(best_per_N, :N) | |
| label_str = "$(LABELS[method]), $(SMOOTHNESS_LABELS[smooth])" | |
| scatterlines!(ax1, best_per_N.N, best_per_N.rel_l2_error, | |
| color=COLORS[method], | |
| linestyle=method_linestyles[method], | |
| marker=MARKERS[method], | |
| markersize=5, | |
| alpha=0.4 + 0.3 * smooth, # Lighter = higher smoothness | |
| label=label_str) | |
| end | |
| end | |
| add_panel_label!(ax1, "(a)") | |
| axislegend(ax1, position=:rt, labelsize=6, framevisible=false) | |
| # ========================================================================== | |
| # (b) Effect of ρ - best smoothness per (method, N, ρ) | |
| # ========================================================================== | |
| ax2 = Axis(fig[2,1], | |
| xlabel=L"Grid size $N$", | |
| ylabel=L"Relative $L^2$ error", | |
| xscale=log10, yscale=log10) | |
| # Markers for different ρ values | |
| rho_markers = Dict( | |
| 2.0 => :circle, | |
| 3.0 => :diamond, | |
| 4.0 => :utriangle, | |
| 5.0 => :rect | |
| ) | |
| for method in ["sparse_fvm", "sparse_collocation"] | |
| method_df = filter(r -> r.method == method, gp_df) | |
| if nrow(method_df) == 0 | |
| continue | |
| end | |
| available_rhos = sort(unique(method_df.rho)) | |
| for rho in available_rhos | |
| r_df = filter(r -> r.rho == rho, method_df) | |
| # Pick best smoothness for each N | |
| best_per_N = DataFrame() | |
| for N in unique(r_df.N) | |
| n_df = filter(r -> r.N == N, r_df) | |
| if nrow(n_df) > 0 | |
| best_idx = argmin(n_df.rel_l2_error) | |
| push!(best_per_N, n_df[best_idx, :]) | |
| end | |
| end | |
| sort!(best_per_N, :N) | |
| rho_int = Int(rho) | |
| label_str = "$(LABELS[method]), ρ=$rho_int" | |
| marker = get(rho_markers, rho, :star5) | |
| scatterlines!(ax2, best_per_N.N, best_per_N.rel_l2_error, | |
| color=COLORS[method], | |
| linestyle=method_linestyles[method], | |
| marker=marker, | |
| markersize=5, | |
| label=label_str) | |
| end | |
| end | |
| add_panel_label!(ax2, "(b)") | |
| axislegend(ax2, position=:rt, labelsize=6, framevisible=false) | |
| # Link axes | |
| linkyaxes!(ax1, ax2) | |
| # Save | |
| mkpath(dirname(output_path)) | |
| save(output_path, fig, pt_per_unit=1) | |
| png_path = replace(output_path, ".pdf" => ".png") | |
| save(png_path, fig, px_per_unit=3) | |
| set_theme!() | |
| println("Saved: $output_path") | |
| println("Saved: $png_path") | |
| return fig | |
| end | |
| # ============================================================================== | |
| # CLI | |
| # ============================================================================== | |
| function parse_commandline() | |
| s = ArgParseSettings(description = "Generate appendix figure for hyperparameter sensitivity analysis") | |
| ! s begin | |
| "--sweep", "-s" | |
| help = "Path to hyperparameter sweep CSV file" | |
| arg_type = String | |
| required = true | |
| "--output", "-o" | |
| help = "Output directory for figures" | |
| arg_type = String | |
| default = "figures" | |
| "--height" | |
| help = "Figure height in inches" | |
| arg_type = Float64 | |
| default = 3.5 | |
| end | |
| return parse_args(s) | |
| end | |
| function main() | |
| args = parse_commandline() | |
| csv_path = args["sweep"] | |
| output_dir = args["output"] | |
| println("="^60) | |
| println("Appendix Figure: Hyperparameter Sensitivity") | |
| println("="^60) | |
| println("Input: $csv_path") | |
| println("Output: $output_dir") | |
| println() | |
| mkpath(output_dir) | |
| output_path = joinpath(output_dir, "hyperparam_sensitivity.pdf") | |
| plot_hyperparam_sensitivity(csv_path; output_path=output_path, height=args["height"]) | |
| println("\nDone!") | |
| end | |
| if abspath(PROGRAM_FILE) == | |
| main() | |
| end | |