Mode-1 internal wave reflection

This example simulates the propagation of a mode-1 internal wave using the ClimateMachine.Ocean subcomponent to solve the hydrostatic Boussinesq equations.

First we ClimateMachine.init().

using ClimateMachine

ClimateMachine.init()

Domain setup

We formulate a non-dimension problem in a Cartesian domain with oceanic anisotropy,

using ClimateMachine.CartesianDomains

domain = RectangularDomain(
    Ne = (32, 1, 4),
    Np = 4,
    x = (-128, 128),
    y = (-128, 128),
    z = (-1, 0),
    periodicity = (false, false, false),
)
RectangularDomain{Float64}
    Np = 4, Ne = (x = 32, y = 1, z = 4)
    L = (x = 256.0, y = 256.0, z = 1.0)

Parameters

We choose parameters appropriate for a hydrostatic internal wave,

Non-dimensional internal wave parameters

f = 1  # Coriolis
N = 10 # Buoyancy frequency
10

Note that the validity of the hydrostatic approximation requires small aspect ratio motions with $k / m \\ll 1$. The hydrostatic dispersion relation for inertia-gravity waves then implies that

λ = 8      # horizontal wave-length
k = 2π / λ # horizontal wavenumber
m = π

ω² = f^2 + N^2 * k^2 / m^2 # and

ω = √(ω²)
2.692582403567252

Internal wave initial condition

We impose modest gravitational acceleration to render time-stepping feasible,

using CLIMAParameters: AbstractEarthParameterSet, Planet
struct NonDimensionalParameters <: AbstractEarthParameterSet end

Planet.grav(::NonDimensionalParameters) = 256.0

we'd like to use θ as a buoyancy variable, which requires setting the thermal expansion coefficient $αᵀ$ to

g = Planet.grav(NonDimensionalParameters())

αᵀ = 1 / g
0.00390625

We then use the "polarization relations" for vertically-standing, horizontally- propagating hydrostatic internal waves to initialze two wave packets. The hydrostatic polarization relations require

\[\begin{gather} (∂_t^2 + f^2) u = - ∂_x ∂_t p ∂_t v = - f u b = ∂_z p \end{gather}\]

Thus given $p = \cos (k x - ω t) \cos (m z)$, we find

δ = domain.L.x / 15
a(x) = 1e-6 * exp(-x^2 / 2 * δ^2)

ũ(x, z, t) = +a(x) * ω * sin(k * x - ω * t) * cos(m * z)
ṽ(x, z, t) = -a(x) * f * cos(k * x - ω * t) * cos(m * z)
θ̃(x, z, t) = -a(x) * m / k * (ω^2 - f^2) * sin(k * x - ω * t) * sin(m * z)

uᵢ(x, y, z) = ũ(x, z, 0)
vᵢ(x, y, z) = ṽ(x, z, 0)
θᵢ(x, y, z) = θ̃(x, z, 0) + N^2 * z

using ClimateMachine.Ocean.OceanProblems: InitialConditions

initial_conditions = InitialConditions(u = uᵢ, v = vᵢ, θ = θᵢ)
ClimateMachine.Ocean.OceanProblems.InitialConditions{typeof(Main.##382.uᵢ),typeof(Main.##382.vᵢ),typeof(Main.##382.θᵢ),typeof(ClimateMachine.Ocean.OceanProblems.resting)}(Main.##382.uᵢ, Main.##382.vᵢ, Main.##382.θᵢ, ClimateMachine.Ocean.OceanProblems.resting)

Model configuration

We choose a time-step that resolves the gravity wave phase speed,

time_step = 0.005 # close to Δx / c = 0.5 * 1/16, where Δx is nominal resolution
0.005

and build a model with a smidgeon of viscosity and diffusion,

using ClimateMachine.Ocean: HydrostaticBoussinesqSuperModel

model = HydrostaticBoussinesqSuperModel(
    domain = domain,
    time_step = time_step,
    initial_conditions = initial_conditions,
    parameters = NonDimensionalParameters(),
    turbulence_closure = (νʰ = 1e-6, νᶻ = 1e-6, κʰ = 1e-6, κᶻ = 1e-6),
    coriolis = (f₀ = f, β = 0),
    buoyancy = (αᵀ = αᵀ,),
    boundary_tags = ((1, 1), (1, 1), (1, 2)),
);
nothing
┌ Info: Initializing 
└ @ ClimateMachine /central/scratch/climaci/climatemachine-docs/1426/climatemachine-docs/src/Driver/solver_configs.jl:185

Fetching data for an animation

To animate the ClimateMachine.Ocean solution, we assemble and cache the horizontal velocity $u$ at periodic intervals:

using ClimateMachine.Ocean: current_time
using ClimateMachine.CartesianFields: assemble
using ClimateMachine.GenericCallbacks: EveryXSimulationTime

fetched_states = []
fetch_every = 0.2 * 2π / ω # time

data_fetcher = EveryXSimulationTime(fetch_every) do
    push!(
        fetched_states,
        (
            u = assemble(model.fields.u.elements),
            θ = assemble(model.fields.θ.elements),
            η = assemble(model.fields.η.elements),
            time = current_time(model),
        ),
    )
    return nothing
end
ClimateMachine.GenericCallbacks.EveryXSimulationTime(Main.##382.var"#1#2"(), 0.46670328817831874, 0)

We also build a callback to log the progress of our simulation,

using Printf
using ClimateMachine.GenericCallbacks: EveryXSimulationSteps
using ClimateMachine.Ocean: current_time, current_step, Δt

print_every = 100 # iterations
wall_clock = [time_ns()]

tiny_progress_printer = EveryXSimulationSteps(print_every) do

    @info(@sprintf(
        "Steps: %d, time: %.2f, Δt: %.2f, max(|u|): %.2e, elapsed time: %.2f secs",
        current_step(model),
        current_time(model),
        Δt(model),
        maximum(abs, model.fields.u),
        1e-9 * (time_ns() - wall_clock[1])
    ))

    wall_clock[1] = time_ns()
end
ClimateMachine.GenericCallbacks.EveryXSimulationSteps(Main.##382.var"#3#4"(), 100, 0)

Running the simulation and animating the results

We're ready to launch.

model.solver_configuration.timeend = 6 * 2π / ω
# model.solver.dt = 0.05 # make this work

@info """ Simulating a hydrostatic Gaussian wave packet with parameters

    f (Coriolis parameter):       $f
    N (buoyancy frequency):       $N
    Internal wave frequency:      $(abs(ω))
    Surface wave frequency:       $(k * sqrt(g * domain.L.z))
    Surface wave group velocity:  $(sqrt(g * domain.L.z))
    Internal wave group velocity: $(N^2 * k / (ω * m))
    Domain width:                 $(domain.L.x)
    Domain height:                $(domain.L.z)

"""

result = ClimateMachine.invoke!(
    model.solver_configuration;
    user_callbacks = [tiny_progress_printer, data_fetcher],
)
0.9999575475529562

Animating the result

We first analye the results to generate plotting limits and contour levels

ηmax = maximum([maximum(abs, state.η.data) for state in fetched_states])
umax = maximum([maximum(abs, state.u.data) for state in fetched_states])

ηlim = (-ηmax, ηmax)
ulim = (-umax, umax)
ulevels = range(ulim[1], ulim[2], length = 31)
-2.2110987101884194e-7:1.4740658067922796e-8:2.2110987101884194e-7

and then animate both fields in a loop,

using Plots

animation = @animate for (i, state) in enumerate(fetched_states)
    @info "Plotting frame $i of $(length(fetched_states))..."

    η_plot = plot(
        state.u.x[:, 1, 1],
        state.η.data[:, 1, 1],
        ylim = ηlim,
        label = nothing,
        title = @sprintf("η at t = %.2f", state.time),
    )

    u_plot = contourf(
        state.u.x[:, 1, 1],
        state.u.z[1, 1, :],
        clamp.(state.u.data[:, 1, :], ulim[1], ulim[2])';
        aspectratio = 64,
        linewidth = 0,
        xlim = domain.x,
        ylim = domain.z,
        xlabel = "x",
        ylabel = "z",
        color = :balance,
        colorbar = false,
        clim = ulim,
        levels = ulevels,
        title = @sprintf("u at t = %.2f", state.time),
    )

    plot(
        η_plot,
        u_plot,
        layout = Plots.grid(2, 1, heights = (0.3, 0.7)),
        link = :x,
        size = (600, 300),
    )
end

gif(animation, "internal_wave.mp4", fps = 5) # hide
Plots.AnimatedGif("/central/scratch/climaci/climatemachine-docs/1426/climatemachine-docs/docs/src/generated/Ocean/internal_wave.mp4")

This page was generated using Literate.jl.