Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add internal tide example in Docs #3132

Merged
merged 55 commits into from
Mar 11, 2024
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
a32f127
don't hardcode 3 halo points for pcg
navidcy Jun 3, 2023
e6a12ba
add barotropic tide example
navidcy Jun 3, 2023
d122aaf
add barotropic tide example
navidcy Jun 3, 2023
292b4e8
run for 4 days
navidcy Jun 3, 2023
72a2399
tidal forcing 0 at t=0
navidcy Jun 4, 2023
29a44fb
fix latex rendering
navidcy Jun 4, 2023
d2258d8
adds remarks about saved output
navidcy Jun 4, 2023
bbd8ee4
add more explanation regarding the tidal forcing
navidcy Jun 4, 2023
900db08
add remarks about IBG
navidcy Jun 4, 2023
e067cea
more remarks about IBG
navidcy Jun 4, 2023
8cbe588
(i) -> (ii)
navidcy Jun 4, 2023
ba801a2
first load, then plot
navidcy Jun 4, 2023
b3f39a4
missing _2
navidcy Jun 4, 2023
803d21b
some more remarks
navidcy Jun 4, 2023
dc0ea63
fix bottom redefinition + beautifications
navidcy Jun 4, 2023
fbde03b
simpler panel titles
navidcy Jun 4, 2023
373e5f5
simpler plotting
navidcy Jun 4, 2023
9f6590d
z [m] -> km
navidcy Jun 4, 2023
b8c0efb
simplify
navidcy Jun 4, 2023
86d622f
rename to internal tide + add some remarks from review
navidcy Jun 5, 2023
2dd8e9e
Merge branch 'main' into ncc/tide
navidcy Jun 5, 2023
2f8c059
apply review remarks
navidcy Jun 5, 2023
1b39507
define H
navidcy Jun 5, 2023
89fd853
undo pcg modifications
navidcy Jun 5, 2023
b2eb120
bring back all examples
navidcy Jun 5, 2023
273fd1e
bring back strictness
navidcy Jun 5, 2023
7810d49
simpler forcing description
navidcy Jun 5, 2023
8aa442d
\varepsilon -> \epsilon
navidcy Jun 5, 2023
f13a33e
code alignment
navidcy Jun 6, 2023
baff7d1
Merge branch 'main' into ncc/tide
navidcy Jun 6, 2023
8a57575
Merge branch 'main' into ncc/tide
navidcy Jun 12, 2023
adca6fe
Merge branch 'main' into ncc/tide
navidcy Jun 19, 2023
1ab32b0
Merge branch 'main' into ncc/tide
navidcy Jun 28, 2023
0ba8f27
order according to most computationally expensive
navidcy Jun 30, 2023
4f051c6
swe example takes long time
navidcy Jun 30, 2023
afb0a13
Merge branch 'main' into ncc/tide
tomchor Aug 7, 2023
d0c14b7
Merge branch 'main' into ncc/tide
navidcy Sep 14, 2023
00a1574
Merge branch 'main' into ncc/tide
glwagner Nov 14, 2023
ae36d67
Merge branch 'main' into ncc/tide
navidcy Jan 16, 2024
71d2661
updates deprecated syntax
navidcy Jan 16, 2024
4b73428
minor code organization
navidcy Jan 16, 2024
0c22012
remove stray line
navidcy Jan 16, 2024
3983882
update deprecated syntax
navidcy Jan 16, 2024
d2a40ac
Merge branch 'main' into ncc/tide
navidcy Jan 29, 2024
d3e2be2
Merge branch 'main' into ncc/tide
navidcy Mar 7, 2024
1350569
update internal_tide example
navidcy Mar 7, 2024
979c2a7
mask immersed output with 0
navidcy Mar 7, 2024
06e20a5
revert change
navidcy Mar 7, 2024
da4502a
add post-process masking step
navidcy Mar 7, 2024
84bebe8
revert changes
navidcy Mar 7, 2024
cf18546
find max abs values before masking
navidcy Mar 7, 2024
f8c0dea
Merge branch 'main' into ncc/tide
navidcy Mar 8, 2024
cf1ad7b
plot domain with band
navidcy Mar 8, 2024
ca43714
simpler masking
navidcy Mar 9, 2024
6777dd7
fix missing .jl
navidcy Mar 10, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/make.jl
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ examples = [
"One-dimensional diffusion" => "one_dimensional_diffusion",
"Two-dimensional turbulence" => "two_dimensional_turbulence",
"Internal wave" => "internal_wave",
"Barotropic tide" => "barotropic_tide",
navidcy marked this conversation as resolved.
Show resolved Hide resolved
"Convecting plankton" => "convecting_plankton",
"Ocean wind mixing and convection" => "ocean_wind_mixing_and_convection",
"Langmuir turbulence" => "langmuir_turbulence",
Expand Down
285 changes: 285 additions & 0 deletions examples/barotropic_tide.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,285 @@
# # Barotropic tide
#
# In this example, we simulate the evolution of a barotropic tide over a hill.
#
# ## Install dependencies
#
# First let's make sure we have all required packages installed.

# ```julia
# using Pkg
# pkg"add Oceananigans, CairoMakie"
# ```

using Oceananigans
using Oceananigans.Units

# ## Grid

# We use an immersed boundary two-dimensional grid (in ``x``--``z``) that is is periodic in
# the ``x``-direction. To construct an immersed boundary grid we first need to create what
# we refer to as "underlying grid", which the grid that encompasses the immersed boundary.
navidcy marked this conversation as resolved.
Show resolved Hide resolved

Nx, Nz = 200, 80

H = 2kilometers
Lx = 2000kilometers

underlying_grid = RectilinearGrid(size = (Nx, Nz),
x = (-Lx/2, Lx/2),
z = (-H, 0),
halo = (4, 4),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's too bad we need this. Should we make the default halo size 4?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should.
Is there a downside to that?
Memory footprint is really minimal, right?

topology = (Periodic, Flat, Bounded))
navidcy marked this conversation as resolved.
Show resolved Hide resolved

# Now we can create the non-trivial bathymetry as an immersed boundary. We use `GridFittedBottom` that
# gets as input either *(i)* a two-dimensional function whose arguments are the grid's native horizontal
# coordinates and it returns the ``z`` of the bottom, or *(ii)* a two-dimensional array with the values
# of ``z`` at the bottom at the cell centers.
#
# In this example we'd like to have a small Gaussian hill at the center of the domain.

# ```math
# h(x) = -H + h_0 \exp(-x^2 / 2σ^2)
# ```

h₀ = 50 # m
navidcy marked this conversation as resolved.
Show resolved Hide resolved
width = 5kilometers
hill(x) = h₀ * exp(-x^2 / 2width^2)
bottom(x, y) = - H + hill(x)

grid = ImmersedBoundaryGrid(underlying_grid, GridFittedBottom(bottom))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This example is probably the perfect test for PartialCellBottom

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, or even better for shaved cells!
I'd be very interested to see if these streaks of w-velocities in the initial condition diminish with shaved cells.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why don't we use PartialCellBottom here?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When I tried to change

grid = ImmersedBoundaryGrid(underlying_grid, GridFittedBottom(bottom))

to

using Oceananigans.ImmersedBoundaries: PartialCellBottom
grid = ImmersedBoundaryGrid(underlying_grid, PartialCellBottom(bottom))

the simulation NaNed immediately. Even when I reduced the max_Δt from 6 minutes down to 2 minutes..


# The hill is small; here's how it looks (note that we don't plot all the way to the ocean surface).

xC = xnodes(grid, Center())
bottom_boundary = grid.immersed_boundary.bottom_height[1:Nx, 1]

using CairoMakie

fig = Figure(resolution = (700, 200))
ax = Axis(fig[1, 1],
xlabel="x [km]",
ylabel="z [m]",
limits=((-Lx/2e3, Lx/2e3), (-H, -4H/5)))

lines!(ax, xC/1e3, bottom_boundary)

fig

# Now we want to add a barotropic tide forcing. For example, to add the ``M_2`` tidal forcing
# we need to add forcing in the ``u``-momentum equation of the form:
# ```math
# \partial_t u = \dotsb + F_0 \sin(\omega_2 t)
# ```
# where ``\omega_2 = 2π / T_2``, with ``T_2 = 12.421 \,\mathrm{hours}`` the period of the ``M_2`` tide.

# The excursion parameter is a nondimensional number that expresses the ratio of the flow movement
# due to the tide compared to the size of the width of the hill.
#
# ```math
# \epsilon = \frac{U_{\mathrm{tidal}} / \omega_2}{\sigma}
# ```
#
# We prescribe the excursion parameter which, in turn, implies a tidal velocity ``U_{\mathrm{tidal}}``
# which then allows us to determing the tidal forcing amplitude ``F_0``. For the last step, we
# use Fourier decomposition on the inviscid, linearized momentum equations to determine the
# flow response for a given tidal forcing. Doing so we get that for the sinusoidal forcing above,
# the tidal velocity and tidal forcing amplitudes are related via:
#
# ```math
# U_{\mathrm{tidal}} = \frac{\omega_2}{\omega_2^2 - f^2}F_0
# ```
#
# Now we have the way to find the value of the tidal forcing amplitude that would correspond to a
# given excursion parameter. The Coriolis frequency is needed, so we start by constructing a Coriolis on an ``f``-plane at the
# mid-latitudes.

coriolis = FPlane(latitude = -45)

# Now we have everything we require to construct the tidal forcing given a value of the
# excursion parameter.
#
T₂ = 12.421hours
const ω₂ = 2π / T₂ # radians/sec

ε = 0.25 # the excursion parameter
navidcy marked this conversation as resolved.
Show resolved Hide resolved

U_tidal = ε * ω₂ * width

const tidal_forcing_amplitude = U_tidal * (ω₂^2 - coriolis.f^2) / ω₂

@inline tidal_forcing(x, y, z, t) = tidal_forcing_amplitude * sin(ω₂ * t)
navidcy marked this conversation as resolved.
Show resolved Hide resolved
nothing #hide

# ## Model

# We built a `HydrostaticFreeSurfaceModel` with an `ImplicitFreeSurface` solver.

using Oceananigans.Models.HydrostaticFreeSurfaceModels: FFTImplicitFreeSurfaceSolver

fft_preconditioner = FFTImplicitFreeSurfaceSolver(grid)
free_surface = ImplicitFreeSurface(solver_method=:PreconditionedConjugateGradient, preconditioner=fft_preconditioner)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SplitExplicitFreeSurface(; grid, cfl = 0.7, max_Δt = 3minutes) would give you about 6 barotropic steps, so probably it's faster

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are a few bugs that didn't allow me to put SplitExplicit here the way you suggest. E.g, substep is not ensured to be integer.

#3131 deals with it; could you have a look there @simone-silvestri?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@simone-silvestri

julia> free_surface = SplitExplicitFreeSurface(; grid, cfl = 0.7, max_Δt)
SplitExplicitFreeSurface with 51895 steps

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this happens, I believe, because for a Flat dimension, e.g., like y here

julia> minimum_yspacing(grid)
1.0

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cc @glwagner remember why we made this choice? I believe there was a reason...

we should either change minimum_ψspacing for flat ψ coordinates or ensure the the substeps computation in the SplitExplicit takes this into account

Copy link
Collaborator Author

@navidcy navidcy Jun 4, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

have a look at b395445 in #3131

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

omg, with the fix the SplitExplicit rips!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@djlikesdjs we should change your experiments to use this free surface solver -- I can help you do it

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's unfortunate that the split explicit free surface always has to be configured (ie its not possible to make it default...)


model = HydrostaticFreeSurfaceModel(; grid, free_surface, coriolis,
buoyancy = BuoyancyTracer(),
tracers = :b,
momentum_advection = WENO(),
tracer_advection = WENO(),
forcing = (u = tidal_forcing,))
navidcy marked this conversation as resolved.
Show resolved Hide resolved

# We initialize the model with the tidal flow and a linear stratification.

uᵢ(x, y, z) = U_tidal

Nᵢ² = 2e-4 # [s⁻²] initial buoyancy frequency / stratification
bᵢ(x, y, z) = Nᵢ² * z

set!(model, u=uᵢ, b=bᵢ)

# Now let's built a `Simulation`.

Δt = 3minutes
stop_time = 4days

simulation = Simulation(model; Δt, stop_time)

# We add a callback to print a message about how the simulation is going,

using Printf

wall_clock = Ref(time_ns())

function print_progress(sim)

elapsed = 1e-9 * (time_ns() - wall_clock[])

msg = @sprintf("iteration: %d, time: %s, wall time: %s, max|w|: %6.3e, m s⁻¹\n",
iteration(sim), prettytime(sim), prettytime(elapsed),
maximum(abs, sim.model.velocities.w))

wall_clock[] = time_ns()

@info msg

return nothing
end

simulation.callbacks[:print_progress] = Callback(print_progress, IterationInterval(200))
nothing #hide

# ## Diagnostics/Output

# Add some diagnostics. Instead of ``u`` we save the deviation of ``u`` from its instantaneous
# domain average, ``u' = u - (L_x H)^{-1} \int u \, \mathrm{d}x \mathrm{d}z``. We also save
# the stratification ``N^2 = \partial_z b``.

b = model.tracers.b
u, v, w = model.velocities

U = Field(Average(u))

u′ = u - U

N² = ∂z(b)

filename = "barotropic_tide"
save_fields_interval = 30minutes

simulation.output_writers[:fields] = JLD2OutputWriter(model, (; u, u′, w, b, N²);
filename,
schedule = TimeInterval(save_fields_interval),
overwrite_existing = true)

# We are ready -- let's run!

run!(simulation)

# ## Load output

# First, we load the saved valocity and stratification output as `FieldTimeSeries`es.

saved_output_filename = filename * ".jld2"

u′_t = FieldTimeSeries(saved_output_filename, "u′")
w_t = FieldTimeSeries(saved_output_filename, "w")
N²_t = FieldTimeSeries(saved_output_filename, "N²")

times = u′_t.times
nothing #hide

# We retrieve each field's coordinates and convert from meters to kilometers.

xu, yu, zu = nodes(u′_t[1]) ./ 1e3
xw, yw, zw = nodes(w_t[1]) ./ 1e3
xN², yN², zN² = nodes(N²_t[1]) ./ 1e3
nothing #hide

# ## Visualize

# Now we can visualize our resutls! We use `CairoMakie` here. On a system with OpenGL
# `using GLMakie` is more convenient as figures will be displayed on the screen.

using CairoMakie

# First, a utility to mask the region that is within the immersed boundary with `NaN`s and
# also select the ``x``-``z`` interior slices of our fields.

using Oceananigans.ImmersedBoundaries: mask_immersed_field!

function mask_and_get_interior(φ_t, n; value=NaN)
mask_immersed_field!(φ_t[n], value)
return interior(φ_t[n], :, 1, :)
end
nothing #hide
navidcy marked this conversation as resolved.
Show resolved Hide resolved

# We use Makie's `Observable` to animate the data. To dive into how `Observable`s work we
# refer to [Makie.jl's Documentation](https://makie.juliaplots.org/stable/documentation/nodes/index.html).

n = Observable(1)

title = @lift @sprintf("t = %1.2f days = %1.2f T₂",
round(times[$n] / day, digits=2) , round(times[$n] / T₂, digits=2))

u′ₙ = @lift mask_and_get_interior(u′_t, $n)
wₙ = @lift mask_and_get_interior(w_t, $n)
N²ₙ = @lift mask_and_get_interior(N²_t, $n)

axis_kwargs = (xlabel = "x [km]",
ylabel = "z [km]",
limits = ((-Lx / 2e3, Lx / 2e3), (-H / 1e3, 0)), # note conversion to kilometers
titlesize = 20)

ulim = 0.8 * maximum(abs, u′_t[end])
wlim = 0.8 * maximum(abs, w_t[end])

fig = Figure(resolution = (700, 900))

fig[1, :] = Label(fig, title, fontsize=24, tellwidth=false)

ax_u = Axis(fig[2, 1]; title = "u'-velocity", axis_kwargs...)
hm_u = heatmap!(ax_u, xu, zu, u′ₙ; colorrange = (-ulim, ulim), colormap = :balance)
Colorbar(fig[2, 2], hm_u, label = "m s⁻¹")

ax_w = Axis(fig[3, 1]; title = "w-velocity", axis_kwargs...)
hm_w = heatmap!(ax_w, xw, zw, wₙ; colorrange = (-wlim, wlim), colormap = :balance)
Colorbar(fig[3, 2], hm_w, label = "m s⁻¹")

ax_N² = Axis(fig[4, 1]; title = "stratification N²", axis_kwargs...)
hm_N² = heatmap!(ax_N², xN², zN², N²ₙ; colorrange = (0.95Nᵢ², 1.05Nᵢ²), colormap = :thermal)
Colorbar(fig[4, 2], hm_N², label = "s⁻²")

fig

# Finally, we can record a movie.

@info "Making an animation from saved data..."

frames = 1:length(times)

record(fig, filename * ".mp4", frames, framerate=16) do i
@info string("Plotting frame ", i, " of ", frames[end])
n[] = i
end
nothing #hide

# ![](barotropic_tide.mp4)
Loading