Skip to content

Commit 806b84a

Browse files
committed
Try simpler bilinear remapping (for use in netcdf diagnostics)
Add BilinearRemapping util to preserve existing interface
1 parent 3c150fa commit 806b84a

File tree

9 files changed

+1176
-52
lines changed

9 files changed

+1176
-52
lines changed

docs/src/remapping.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,12 @@ given `field`. To obtain such coordinates, you can call the
4545
functions. These functions return an `Array` with the coordinates over which
4646
interpolation will occur. These arrays are of type `Geometry.Point`s.
4747

48-
By default, vertical interpolation is switched off and the `field` is evaluated
49-
directly on the levels.
48+
By default, vertical interpolation is off (field evaluated on levels). Horizontal
49+
interpolation: `SpectralElementRemapping()` (default; uses spectral element quadrature weights) or `BilinearRemapping()`:
50+
51+
```julia
52+
interpolated_array = Remapping.interpolate(field; horizontal_method = Remapping.BilinearRemapping())
53+
```
5054

5155
`ClimaCore.Remapping.interpolate` allocates new output arrays. As such, it is
5256
not suitable for performance-critical applications.
@@ -76,6 +80,7 @@ hcoords = [Geometry.LatLongPoint(lat, long) for long in longpts, lat in latpts]
7680
zcoords = [Geometry.ZPoint(z) for z in zpts]
7781

7882
interpolated_array = interpolate(field, hcoords, zcoords)
83+
# interpolate(field, hcoords, zcoords; horizontal_method = Remapping.BilinearRemapping())
7984
```
8085
The output is defined on the Cartesian product of `hcoords` with `zcoords`.
8186

examples/remap_visualization.jl

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

0 commit comments

Comments
 (0)