|
| 1 | +# Visualize bilinear and spectral interpolation remapping: slotted cylinder (Zalesak) |
| 2 | +# |
| 3 | +# Compares bilinear vs spectral horizontal remap on the slotted cylinder test case |
| 4 | +# (disk with rectangular slot; f ∈ {0, 1}). Parameters highlight spectral (Lagrange) |
| 5 | +# overshoot/undershoot vs bilinear. |
| 6 | +# |
| 7 | +# Run from repo root with: |
| 8 | +# julia --project=examples examples/remap_visualization.jl |
| 9 | +# or with the main project: |
| 10 | +# julia --project=. examples/remap_visualization.jl |
| 11 | + |
| 12 | +using ClimaComms |
| 13 | +using ClimaCore: |
| 14 | + Geometry, |
| 15 | + Domains, |
| 16 | + Meshes, |
| 17 | + Topologies, |
| 18 | + Spaces, |
| 19 | + Fields, |
| 20 | + Remapping, |
| 21 | + Quadratures |
| 22 | +using CairoMakie |
| 23 | + |
| 24 | +device = ClimaComms.CPUSingleThreaded() |
| 25 | + |
| 26 | +nelements_horz = 6 # horizontal elements per dimension |
| 27 | +Nq = 4 # GLL points per dimension |
| 28 | +n_interp = 24 # target grid resolution for interpolation |
| 29 | + |
| 30 | +# Slotted cylinder (Zalesak): disk with rectangular slot; f ∈ {0, 1} |
| 31 | +slot_radius = 0.15 |
| 32 | +slot_cx, slot_cy = 0.5, 0.5 |
| 33 | +slot_half_width = 0.025 |
| 34 | +slot_y_hi = slot_cy + slot_radius |
| 35 | + |
| 36 | +# --- Domain: square [0, 1] × [0, 1] (periodic) --- |
| 37 | +horzdomain = Domains.RectangleDomain( |
| 38 | + Geometry.XPoint(0.0) .. Geometry.XPoint(1.0), |
| 39 | + Geometry.YPoint(0.0) .. Geometry.YPoint(1.0), |
| 40 | + x1periodic = true, |
| 41 | + x2periodic = true, |
| 42 | +) |
| 43 | + |
| 44 | +# --- Vertical: single layer --- |
| 45 | +vertdomain = Domains.IntervalDomain( |
| 46 | + Geometry.ZPoint(0.0), |
| 47 | + Geometry.ZPoint(1.0); |
| 48 | + boundary_names = (:bottom, :top), |
| 49 | +) |
| 50 | +vertmesh = Meshes.IntervalMesh(vertdomain, nelems = 1) |
| 51 | +verttopo = Topologies.IntervalTopology(ClimaComms.SingletonCommsContext(device), vertmesh) |
| 52 | +vert_center_space = Spaces.CenterFiniteDifferenceSpace(verttopo) |
| 53 | + |
| 54 | +# --- Horizontal: spectral elements --- |
| 55 | +quad = Quadratures.GLL{Nq}() |
| 56 | +horzmesh = Meshes.RectilinearMesh(horzdomain, nelements_horz, nelements_horz) |
| 57 | +horztopology = Topologies.Topology2D(ClimaComms.SingletonCommsContext(device), horzmesh) |
| 58 | +horzspace = Spaces.SpectralElementSpace2D(horztopology, quad) |
| 59 | +hv_center_space = Spaces.ExtrudedFiniteDifferenceSpace(horzspace, vert_center_space) |
| 60 | + |
| 61 | +# --- Slotted cylinder field --- |
| 62 | +coords = Fields.coordinate_field(hv_center_space) |
| 63 | +function slotted_cylinder(x, y) |
| 64 | + in_disk = (x - slot_cx)^2 + (y - slot_cy)^2 <= slot_radius^2 |
| 65 | + in_slot = (abs(x - slot_cx) <= slot_half_width) && (y >= slot_cy) && (y <= slot_y_hi) |
| 66 | + return (in_disk && !in_slot) ? 1.0 : 0.0 |
| 67 | +end |
| 68 | +field = @. slotted_cylinder(coords.x, coords.y) |
| 69 | +Spaces.weighted_dss!(field) |
| 70 | + |
| 71 | +# --- Target grid: uniform n_interp×n_interp, single vertical level --- |
| 72 | +xpts = range(Geometry.XPoint(0.0), Geometry.XPoint(1.0), length = n_interp) |
| 73 | +ypts = range(Geometry.YPoint(0.0), Geometry.YPoint(1.0), length = n_interp) |
| 74 | +zpts = range(Geometry.ZPoint(0.5), Geometry.ZPoint(0.5), length = 1) |
| 75 | + |
| 76 | +# --- Interpolate: bilinear and spectral --- |
| 77 | +interp_bilinear = |
| 78 | + Remapping.interpolate_array(field, xpts, ypts, zpts; horizontal_method = :bilinear) |
| 79 | +interp_spectral = |
| 80 | + Remapping.interpolate_array(field, xpts, ypts, zpts; horizontal_method = :spectral) |
| 81 | +interp_bilinear_2d = interp_bilinear[:, :, 1] |
| 82 | +interp_spectral_2d = interp_spectral[:, :, 1] |
| 83 | +err_bilinear_spectral = interp_bilinear_2d .- interp_spectral_2d |
| 84 | + |
| 85 | +# --- Non-negativity stats (source f ∈ {0, 1}) --- |
| 86 | +min_bilinear, max_bilinear = extrema(interp_bilinear_2d) |
| 87 | +min_spectral, max_spectral = extrema(interp_spectral_2d) |
| 88 | +n_neg = count(<(0), interp_spectral_2d) |
| 89 | +n_gt1 = count(>(1), interp_spectral_2d) |
| 90 | +@info "Slotted cylinder: non-negativity (source f ∈ {0,1})" bilinear_min = min_bilinear bilinear_max = |
| 91 | + max_bilinear spectral_min = min_spectral spectral_max = max_spectral spectral_below_0 = |
| 92 | + n_neg spectral_above_1 = n_gt1 |
| 93 | + |
| 94 | +# --- Raw spectral element grid (GLL nodes, v=1) --- |
| 95 | +x_se = Float64[] |
| 96 | +y_se = Float64[] |
| 97 | +vals_se = Float64[] |
| 98 | +Fields.byslab(hv_center_space) do slabidx |
| 99 | + slabidx.v == 1 || return |
| 100 | + x_data = parent(Fields.slab(coords.x, slabidx)) |
| 101 | + y_data = parent(Fields.slab(coords.y, slabidx)) |
| 102 | + f_data = parent(Fields.slab(field, slabidx)) |
| 103 | + for j in 1:Nq, i in 1:Nq |
| 104 | + push!(x_se, x_data[i, j, 1]) |
| 105 | + push!(y_se, y_data[i, j, 1]) |
| 106 | + push!(vals_se, f_data[i, j, 1]) |
| 107 | + end |
| 108 | +end |
| 109 | + |
| 110 | +x_plot = [p.x for p in xpts] |
| 111 | +y_plot = [p.y for p in ypts] |
| 112 | +boundary_pos = (0:nelements_horz) ./ nelements_horz |
| 113 | + |
| 114 | +# --- Figure: bilinear | spectral | error; row 2 = raw GLL nodes --- |
| 115 | +fig = Figure(size = (1200, 800)) |
| 116 | + |
| 117 | +ax1 = Axis(fig[1, 1], title = "Bilinear ($n_interp×$n_interp)", xlabel = "x", ylabel = "y") |
| 118 | +hm1 = heatmap!( |
| 119 | + ax1, |
| 120 | + x_plot, |
| 121 | + y_plot, |
| 122 | + interp_bilinear_2d'; |
| 123 | + colorrange = (0, 1), |
| 124 | + colormap = :viridis, |
| 125 | + lowclip = :orange, |
| 126 | + highclip = :red, |
| 127 | +) |
| 128 | +Colorbar(fig[1, 2], hm1; label = "value") |
| 129 | + |
| 130 | +ax2 = Axis(fig[1, 3], title = "Spectral ($n_interp×$n_interp)", xlabel = "x", ylabel = "y") |
| 131 | +hm2 = heatmap!( |
| 132 | + ax2, |
| 133 | + x_plot, |
| 134 | + y_plot, |
| 135 | + interp_spectral_2d'; |
| 136 | + colorrange = (0, 1), |
| 137 | + colormap = :viridis, |
| 138 | + lowclip = :orange, |
| 139 | + highclip = :red, |
| 140 | +) |
| 141 | +Colorbar(fig[1, 4], hm2; label = "value") |
| 142 | + |
| 143 | +ax3 = Axis( |
| 144 | + fig[1, 5], |
| 145 | + title = "Error (bilinear − spectral)", |
| 146 | + xlabel = "x", |
| 147 | + ylabel = "y", |
| 148 | +) |
| 149 | +erange = extrema(err_bilinear_spectral) |
| 150 | +hm3 = heatmap!( |
| 151 | + ax3, |
| 152 | + x_plot, |
| 153 | + y_plot, |
| 154 | + err_bilinear_spectral'; |
| 155 | + colorrange = erange, |
| 156 | + colormap = :RdBu, |
| 157 | +) |
| 158 | +Colorbar(fig[1, 6], hm3; label = "error") |
| 159 | + |
| 160 | +ax_se = Axis( |
| 161 | + fig[2, 1], |
| 162 | + title = "Raw spectral element grid (GLL nodes)", |
| 163 | + xlabel = "x", |
| 164 | + ylabel = "y", |
| 165 | +) |
| 166 | +sc_se = scatter!( |
| 167 | + ax_se, |
| 168 | + y_se, |
| 169 | + x_se; |
| 170 | + color = vals_se, |
| 171 | + colorrange = (0, 1), |
| 172 | + colormap = :viridis, |
| 173 | + lowclip = :orange, |
| 174 | + highclip = :red, |
| 175 | + markersize = 8, |
| 176 | +) |
| 177 | +vlines!(ax_se, boundary_pos; color = :pink, linewidth = 2) |
| 178 | +hlines!(ax_se, boundary_pos; color = :pink, linewidth = 2) |
| 179 | +limits!(ax_se, 0, 1, 0, 1) |
| 180 | +Colorbar(fig[2, 2], sc_se; label = "value") |
| 181 | + |
| 182 | +outdir = joinpath(@__DIR__, "output") |
| 183 | +mkpath(outdir) |
| 184 | +outpath = joinpath(outdir, "remap_slotted_cylinder_$(n_interp)x$(n_interp).png") |
| 185 | +save(outpath, fig) |
| 186 | +@info "Saved to $outpath" |
0 commit comments