Conservative Methods
We solve the Burgers equation (and the advection equation) using the KT2 (second-order Kurganov-Tadmor), MP5 (5th-order monotonicity preserving), and WENOZ methods.
You will need the functions defined here in a file named shocks_utils.jl
alongside this notebook. You can directly download the auxiliary file by clicking here.
#using DiffEqOperators using OrdinaryDiffEq #using DifferentialEquations using Plots using Printf include("shocks_utils.jl")
wenoz! (generic function with 1 method)
Here, we decide which equation to solve. This will be used later when choosing the Flux_x!
function.
#problem = :advection problem = :burgers
:burgers
We will use the Kurganov-Tadmor method (which is second order and semidiscrete), WENO-Z, and MP5
We define the maximum propagation speed. This is necessary to adjust the method's dissipation, as well as to ensure stability. What matters is to have a lower bound for the maximum wave propagation speed at any point.
#Velocidad máxima de propagación function advectionspeed(U, c) return abs(c) end function burgersspeed(U, c) return maximum(abs, U) end if problem == :advection SpeedMax = advectionspeed elseif problem == :burgers SpeedMax = burgersspeed end
burgersspeed (generic function with 1 method)
Here we define the Flux_x!
function. Recall that we are solving problems of the form
\[ u_t = F(u)_x \]
#Fluxes function advection!(F, U, c) @. F = c*U end function burgers!(F, U, Fpars) @. F = 0.5*U*U end if problem == :advection Flux_x! = advection! elseif problem == :burgers Flux_x! = burgers! end
burgers! (generic function with 1 method)
Now we finally begin to construct the problem. First, we define the spatial and temporal interval, along with the vector where we will store the solution. We also choose some parameters for the problem.
# --- Spatial Discretization --- N = 100 # Number of grid points N_FIELDS = 1 # Number of solution fields (e.g., 1 for a scalar equation) domain_start = 0.0 # Start of the spatial domain domain_stop = 2.0 * pi # End of the spatial domain # Create the spatial grid: N points, excluding the endpoint (domain_stop). x = range(domain_start, stop=domain_stop, length=N + 1)[1:end-1] dx = Float64(step(x)) # Spatial step size (Δx) h = 1.0 / dx # Inverse of dx # --- Initial Condition --- # Initialize the solution array 'u' and a temporary array 'du' (e.g., for Runge-Kutta stages or storing residuals) u = Array{Float64}(undef, N, N_FIELDS) du = similar(u) # Creates an array with the same dimensions and type as 'u', uninitialized. # Set the initial condition: u(x,0) = 0.5 + sin(x) # The @. macro broadcasts the operations element-wise. @. u[:, 1] = 0.5 + sin(x) # --- Temporal Discretization --- T = 4.0 # Total integration time tspan = (0.0, T) # Tuple defining the time interval [initial_time, final_time] # CFL condition: CFL = speed * dt / dx. # Here, we set the CFL number and calculate dt based on dx. # The effective 'speed' is implicitly 1 in this dt calculation, # or the numerical scheme itself handles the actual wave speeds. CFL = 0.1 dt = dx * CFL # Time step (Δt) # --- Problem-Specific Parameters --- # 'eqpars' will hold parameters specific to the chosen PDE. # This variable 'problem' (a Symbol, e.g., :advection or :burgers) # would need to be defined elsewhere in your script. if problem == :advection # For an advection equation like u_t + a * u_x = 0, # 'eqpars' could be the advection speed 'a'. eqpars = 1.0 # Example: advection speed elseif problem == :burgers # For Burgers' equation u_t + (0.5 * u^2)_x = 0, # 'eqpars' might not be needed if the non-linearity is handled directly in Flux_x!, # or it could be a coefficient if the equation was u_t + (coeff * u^2)_x = 0. # 'false' is an unconventional parameter; it might signify that no external # scalar parameter is needed for the Burgers' flux function, or it's a boolean flag # for some internal logic within the flux calculation. eqpars = false # Placeholder or specific flag for Burgers' end
false
#Initial data plot(x, u[:,1], label = "Dato inicial")
Here we choose the spatial resolution method and prepare the tuple par
for later integration. Several of these functions are defined in choques_utils.jl
.
# --- List of methods to compare --- # Kurganov-Tadmor (KT2) # θ: This value must be between 1.0 and 2.0. # The closer it is to 2.0, the lower the dissipation. # For systems of equations, it's better for it to be closer to 1.0 to avoid oscillations. θ = 2.0 auxvectorsKT = createKTauxvectors(N_FIELDS) parKT = (eqpars, h, θ, Flux_x!, SpeedMax, N, N_FIELDS, auxvectorsKT) # WENOZ auxvectorsWENOZ = createWENOZvectors(N_FIELDS) parWENOZ = (eqpars, h, N, N_FIELDS, Flux_x!, SpeedMax, auxvectorsWENOZ) # MP5 (Monotonicity Preserving 5th order) auxvectorsMP5 = createMP5auxvectors(N_FIELDS) parMP5 = (eqpars, h, N, N_FIELDS, Flux_x!, SpeedMax, auxvectorsMP5)
(false, 15.915494309189533, 100, 1, Main.burgers!, Main.burgersspeed, ([6.9 309829472321e-310], [6.9309829472321e-310], [6.9309829472321e-310], [6.9309 829472321e-310], [6.9309829472321e-310], [6.9309829472321e-310], [6.9309829 472321e-310], [6.9309829472321e-310], [6.9309829472321e-310], [6.9309829472 321e-310], [6.9309829472321e-310], [6.9309829472321e-310], [6.9309829472321 e-310], [6.9309829472321e-310], [6.9309829472321e-310], [6.9309829472321e-3 10], [6.9309829472321e-310], [6.9309829472321e-310], [6.9309829472321e-310] , [6.9309829472321e-310]))
The solution is a matrix of size N x N_Fields
. For scalar problems, this essentially means it's a vector with a single column, but you can try it with systems of equations and it will work the same. Remember that for convergence analysis, it is convenient to use initial data that is smooth and piecewise polynomial.
Now we are ready to define the problems, choose integrators, and set their behavior. Here it is important to choose a stable integrator, i.e., one whose stability region includes part of the imaginary axis, and preferably one that is TVD (Total Variation Diminishing). In our case, we will choose the Runge-Kutta integrator SSPRK33
, which is third-order in time and is TVD.
probKT = ODEProblem(KT!,u,tspan,parKT); probWENOZ = ODEProblem(wenoz!,u,tspan,parWENOZ); probMP5 = ODEProblem(mp5!,u,tspan,parMP5);
solKT = solve(probKT,SSPRK33(),dt=dt, saveat = T/100);
solWENOZ = solve(probWENOZ,SSPRK33(),dt=dt, saveat = T/100);
solMP5 = solve(probMP5,SSPRK33(),dt=dt, saveat = T/100);
Once we have the solution, we can analyze it. Here we show how to plot it.
anim = @animate for t in solKT.t plt = plot(x, solKT(t), ylims = (-1.0,2.0), label = "KT") plot!(plt, x, solWENOZ(t), ylims = (-1.0,2.0), label = "WENO-Z") plot!(plt, x, solMP5(t), ylims = (-1.0,2.0), label = "MP5") annotate!(plt, 0.0, 1.9, text("T = $(@sprintf("%.2f", t))", :black, :left, 20)) end gif(anim, "burgers_sin.gif", fps = 30)
Help for Convergence Analysis in KT
For the MP5 and WENOZ methods, you can analyze convergence using the $L_1$ norm. For the Kurganov-Tadmor method, you will need to use the $Lip'$ norm. We will provide the implementation of this norm and how to use it to calculate the factor $Q$.
function Lipprime(u, dx) pr = zeros(size(u)[1]) for i in 2:size(u)[1] pr[i] = pr[i-1] + (u[i]+u[i-1])*dx end return sum(abs.(pr))*dx end function QKT(t, sol1, sol2, sol4, dx1, dx2) n1 = Lipprime(sol1(t)[:,1]-sol2(t)[1:2:end,1], dx1) n2 = Lipprime(sol2(t)[:,1]-sol4(t)[1:2:end,1], dx2) return log2(n1/n2) end
QKT (generic function with 1 method)