ProCreations's picture
Publish validated GP-FVM reproduction
c711202 verified
Raw
History Blame Contribute Delete
9.35 kB
"""
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")
@add_arg_table! 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) == @__FILE__
main()
end