From 781152e6227114ca79537675bf81f6ad1582c32c Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Thu, 15 Jan 2026 15:24:31 -0600 Subject: [PATCH 001/360] add framework for non-ideal EOS --- src/Trixi.jl | 2 + src/equations/equations.jl | 2 + src/equations/equations_of_state.jl | 63 +++++ .../nonideal_compressible_euler_1d.jl | 219 ++++++++++++++++++ 4 files changed, 286 insertions(+) create mode 100644 src/equations/equations_of_state.jl create mode 100644 src/equations/nonideal_compressible_euler_1d.jl diff --git a/src/Trixi.jl b/src/Trixi.jl index fbbf827f602..c776258fdc8 100644 --- a/src/Trixi.jl +++ b/src/Trixi.jl @@ -182,6 +182,8 @@ export AcousticPerturbationEquations2D, LinearElasticityEquations1D, PassiveTracerEquations +export NonIdealCompressibleEulerEquations1D, IdealGas + export LaplaceDiffusion1D, LaplaceDiffusion2D, LaplaceDiffusion3D, LaplaceDiffusionEntropyVariables1D, LaplaceDiffusionEntropyVariables2D, LaplaceDiffusionEntropyVariables3D, diff --git a/src/equations/equations.jl b/src/equations/equations.jl index c4456e86cbb..322e411606b 100644 --- a/src/equations/equations.jl +++ b/src/equations/equations.jl @@ -646,6 +646,8 @@ include("compressible_euler_1d.jl") include("compressible_euler_2d.jl") include("compressible_euler_3d.jl") include("compressible_euler_quasi_1d.jl") +include("equations_of_state.jl") +include("nonideal_compressible_euler_1d.jl") # CompressibleEulerMulticomponentEquations abstract type AbstractCompressibleEulerMulticomponentEquations{NDIMS, NVARS, NCOMP} <: diff --git a/src/equations/equations_of_state.jl b/src/equations/equations_of_state.jl new file mode 100644 index 00000000000..e9a4a51d541 --- /dev/null +++ b/src/equations/equations_of_state.jl @@ -0,0 +1,63 @@ +@doc raw""" + The interface for an `AbstractEquationOfState` requires specifying +the following four functions: +- `pressure(V, T, eos)` +- `internal_energy(V, T, eos)` +- `specific_entropy(V, T, eos)` +- `speed_of_sound(V, T, eos)` +where `eos = equations.equation_of_state`. + +`specific_entropy` is required only to calculate `dSdt`, and `speed_of_sound` +is required to calculate wavespeed estimates for e.g., local Lax-Friedrichs fluxes. +""" +abstract type AbstractEquationOfState end + +include("equation_of_state_ideal_gas.jl") +# include("equation_of_state_vdw.jl") + +##### +# Some general fallback routines are provided below +##### + +function gibbs_free_energy(V, T, eos) + s = specific_entropy(V, T, eos) + p = pressure(V, T, eos) + e = internal_energy(V, T, eos) + h = e + p * V + return h - T * s +end + +# calculates dpdV_T, dpdT_V +@inline function calc_pressure_derivatives(V, T, eos::AbstractEquationOfState) + dpdV_T = ForwardDiff.derivative(V -> pressure(V, T, eos), V) + dpdT_V = ForwardDiff.derivative(T -> pressure(V, T, eos), T) + return dpdV_T, dpdT_V +end + +@inline function heat_capacity_constant_volume(V, T, eos::AbstractEquationOfState) + return ForwardDiff.derivative(T -> internal_energy(V, T, eos), T) +end + +# calculate the temperature as a function of specific volume `V` and internal energy `e` +# by using Newton's method to determine `T` such that `internal_energy(V, T, eos) = e`. +function temperature(V, e, eos::AbstractEquationOfState; initial_T = 1.0) + tol = 10 * eps() + T = initial_T + de = internal_energy(V, T, eos) - e + iter = 1 + while abs(de) / abs(e) > tol && iter < 100 + de = internal_energy(V, T, eos) - e + + # c_v = dedT_V > 0, which should guarantee convergence of this iteration + dedT_V = heat_capacity_constant_volume(V, T, eos) + + T = T - de / dedT_V + iter += 1 + end + if iter==100 + println("Warning: nonlinear solve in `temperature(V, T, eos)` did not converge. " * + "Final states: iter = $iter, V, e = $V, $e with de = $de") + end + return T +end + diff --git a/src/equations/nonideal_compressible_euler_1d.jl b/src/equations/nonideal_compressible_euler_1d.jl new file mode 100644 index 00000000000..911db24cbd9 --- /dev/null +++ b/src/equations/nonideal_compressible_euler_1d.jl @@ -0,0 +1,219 @@ +# By default, Julia/LLVM does not use fused multiply-add operations (FMAs). +# Since these FMAs can increase the performance of many numerical algorithms, +# we need to opt-in explicitly. +# See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. +@muladd begin +#! format: noindent + +@doc raw""" + NonIdealCompressibleEulerEquations1D(gamma) + +The compressible Euler equations +```math +\frac{\partial}{\partial t} +\begin{pmatrix} +\rho \\ \rho v_1 \\ \rho E +\end{pmatrix} ++ +\frac{\partial}{\partial x} +\begin{pmatrix} +\rho v_1 \\ \rho v_1^2 + p \\ (\rho E + p) v_1 +\end{pmatrix} += +\begin{pmatrix} +0 \\ 0 \\ 0 +\end{pmatrix} +``` +for an ideal gas with ratio of specific heats `gamma` in one space dimension. +Here, ``\rho`` is the density, ``v_1`` the velocity, ``E`` the specific total energy, +and the pressure in terms of specific volume `V = inv(rho)` and temperature `T` is given +by some user-specified equation of state (EOS) +```math +p = p(V, T), +``` +Similarly, the internal energy is specified by `e = e(V, T)`. + +Because of this, the primitive variables are also defined to be `V, v1, T` (instead of +`rho, v1, p` for `CompressibleEulerEquations1D`). The implementation also assumes +mass basis unless otherewise specified. +""" +struct NonIdealCompressibleEulerEquations1D{EoS_T <: AbstractEquationOfState} <: + AbstractCompressibleEulerEquations{1, 3} + equation_of_state::EoS_T +end + +function varnames(::typeof(cons2cons), ::NonIdealCompressibleEulerEquations1D) + return ("rho", "rho_v1", "rho_e") +end +varnames(::typeof(cons2prim), ::NonIdealCompressibleEulerEquations1D) = ("V", "v1", "T") + +""" + initial_condition_constant(x, t, equations::NonIdealCompressibleEulerEquations1D) + +A constant initial condition to test free-stream preservation. +""" +function initial_condition_constant(x, t, equations::NonIdealCompressibleEulerEquations1D) + RealT = eltype(x) + rho = 1 + rho_v1 = convert(RealT, 0.1) + rho_e = 10 + return SVector(rho, rho_v1, rho_e) +end + +""" + boundary_condition_slip_wall(u_inner, orientation, direction, x, t, + surface_flux_function, equations::NonIdealCompressibleEulerEquations1D) +Determine the boundary numerical surface flux for a slip wall condition. +Imposes a zero normal velocity at the wall. +Density and pressure are taken from the internal solution state and pressure. +Should be used together with [`TreeMesh`](@ref). +""" +@inline function boundary_condition_slip_wall(u_inner, orientation, + direction, x, t, + surface_flux_function, + equations::NonIdealCompressibleEulerEquations1D) + # compute the primitive variables + rho_local, v_normal, p_local = cons2prim(u_inner, equations) + + if isodd(direction) # flip sign of normal to make it outward pointing + v_normal *= -1 + end + + # For the slip wall we directly set the flux as the normal velocity is zero + return SVector(0, p_local, 0) +end + +# Calculate 1D flux for a single point +@inline function flux(u, orientation::Integer, equations::NonIdealCompressibleEulerEquations1D) + eos = equations.equation_of_state + + rho, rho_v1, rho_E = u + V, v1, T = cons2prim(u, equations) + p = pressure(V, T, eos) + + # Ignore orientation since it is always "1" in 1D + f1 = rho_v1 + f2 = rho_v1 * v1 + p + f3 = (rho_E + p) * v1 + return SVector(f1, f2, f3) +end + +# Calculate estimates for minimum and maximum wave speeds for HLL-type fluxes +@inline function min_max_speed_naive(u_ll, u_rr, orientation::Integer, + equations::NonIdealCompressibleEulerEquations1D) + V_ll, v1_ll, T_ll = cons2prim(u_ll, equations) + V_rr, v1_rr, T_rr = cons2prim(u_rr, equations) + + eos = equations.equation_of_state + c_ll = speed_of_sound(V_ll, T_ll, eos) + c_rr = speed_of_sound(V_rr, T_rr, eos) + λ_min = v1_ll - c_ll + λ_max = v1_rr + c_rr + + return λ_min, λ_max +end + +# Less "cautious", i.e., less overestimating `λ_max` compared to `max_abs_speed_naive` +@inline function max_abs_speed(u_ll, u_rr, orientation::Integer, + equations::NonIdealCompressibleEulerEquations1D) + V_ll, v1_ll, T_ll = cons2prim(u_ll, equations) + V_rr, v1_rr, T_rr = cons2prim(u_rr, equations) + + v_mag_ll = abs(v1_ll) + v_mag_rr = abs(v1_rr) + + # Calculate primitive variables and speed of sound + eos = equations.equation_of_state + c_ll = speed_of_sound(V_ll, T_ll, eos) + c_rr = speed_of_sound(V_rr, T_rr, eos) + + return max(v_mag_ll + c_ll, v_mag_rr + c_rr) +end + +# More refined estimates for minimum and maximum wave speeds for HLL-type fluxes +@inline function min_max_speed_davis(u_ll, u_rr, orientation::Integer, + equations::NonIdealCompressibleEulerEquations1D) + V_ll, v1_ll, T_ll = cons2prim(u_ll, equations) + V_rr, v1_rr, T_rr = cons2prim(u_rr, equations) + + # Calculate primitive variables and speed of sound + eos = equations.equation_of_state + c_ll = speed_of_sound(V_ll, T_ll, eos) + c_rr = speed_of_sound(V_rr, T_rr, eos) + + λ_min = min(v1_ll - c_ll, v1_rr - c_rr) + λ_max = max(v1_ll + c_ll, v1_rr + c_rr) + + return λ_min, λ_max +end + +@inline function max_abs_speeds(u, equations::NonIdealCompressibleEulerEquations1D) + V, v1, T = cons2prim(u, equations) + + # Calculate primitive variables and speed of sound + eos = equations.equation_of_state + c = speed_of_sound(V, T, eos) + + return (abs(v1) + c,) +end + +# Convert conservative variables to primitive +@inline function cons2prim(u, equations::NonIdealCompressibleEulerEquations1D) + eos = equations.equation_of_state + rho, rho_v1, rho_E = u + + V = inv(rho) + v1 = rho_v1 * V + e = (rho_E - 0.5 * rho_v1 * v1) * V + T = temperature(V, e, eos) + + return SVector(V, v1, T) +end + +# Convert conservative variables to entropy +@inline function cons2entropy(u, equations::NonIdealCompressibleEulerEquations1D) + V, v1, T = cons2prim(u, equations) + eos = equations.equation_of_state + gibbs = gibbs_free_energy(V, T, eos) + return inv(T) * SVector(gibbs - 0.5 * v1^2, v1, -1) +end + +# Convert primitive to conservative variables +@inline function prim2cons(prim, equations::NonIdealCompressibleEulerEquations1D) + eos = equations.equation_of_state + V, v1, T = prim + rho = inv(V) + rho_v1 = rho * v1 + e = internal_energy(V, T, eos) + rho_E = rho * e + 0.5 * rho_v1 * v1 + return SVector(rho, rho_v1, rho_E) +end + +@doc raw""" + entropy_math(cons, equations::NonIdealCompressibleEulerEquations1D) + +Calculate mathematical entropy for a conservative state `cons` as +```math +S = -\rho s +``` +where `s` is the specific entropy determined by the equation of state. +""" +@inline function entropy_math(u, equations::NonIdealCompressibleEulerEquations1D) + eos = equations.equation_of_state + V, v1, T = cons2prim(u, equations) + rho = u[1] + S = -rho * specific_entropy(V, T, eos) + return S +end + +""" + entropy(cons, equations::AbstractCompressibleEulerEquations) + +Default entropy is the mathematical entropy +[`entropy_math(cons, equations::AbstractCompressibleEulerEquations)`](@ref). +""" +@inline function entropy(cons, equations::NonIdealCompressibleEulerEquations1D) + return entropy_math(cons, equations) +end + +end # @muladd From cb338e8f7e05c271430a5e6a160c041a855897d5 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Thu, 15 Jan 2026 15:24:37 -0600 Subject: [PATCH 002/360] add IdealGas example --- .../elixir_euler_nonideal_density_wave.jl | 75 +++++++++++++++++++ src/equations/equation_of_state_ideal_gas.jl | 51 +++++++++++++ 2 files changed, 126 insertions(+) create mode 100644 examples/tree_1d_dgsem/elixir_euler_nonideal_density_wave.jl create mode 100644 src/equations/equation_of_state_ideal_gas.jl diff --git a/examples/tree_1d_dgsem/elixir_euler_nonideal_density_wave.jl b/examples/tree_1d_dgsem/elixir_euler_nonideal_density_wave.jl new file mode 100644 index 00000000000..274d49cee35 --- /dev/null +++ b/examples/tree_1d_dgsem/elixir_euler_nonideal_density_wave.jl @@ -0,0 +1,75 @@ +using OrdinaryDiffEqLowStorageRK +using Trixi +using Trixi: ForwardDiff + +############################################################################### +# semidiscretization of the compressible Euler equations + +eos = IdealGas(1.4) +equations = NonIdealCompressibleEulerEquations1D(eos) + +function initial_condition_density_wave(x, t, equations::NonIdealCompressibleEulerEquations1D) + rho = 1 + 0.5 * sin(pi * x[1]) + v1 = 0.1 + p = 10.0 + + V = inv(rho) + + # invert for temperature given p, V + tol = 10 * eps() + T = 1.0 + dp = pressure(V, T, eos) - p + while abs(dp) / abs(p) > tol + dp = pressure(V, T, eos) - p + dpdT_V = ForwardDiff.derivative(T -> pressure(V, T, eos), T) + T = T - dp / dpdT_V + end + + return prim2cons(SVector(V, v1, T), equations) +end + +initial_condition = initial_condition_density_wave + +solver = DGSEM(polydeg = 3, surface_flux = flux_central) + +coordinates_min = -1.0 +coordinates_max = 1.0 +mesh = TreeMesh(coordinates_min, coordinates_max, + initial_refinement_level = 4, + n_cells_max = 30_000) + +semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) + +############################################################################### +# ODE solvers, callbacks etc. + +tspan = (0.0, 2.0) +ode = semidiscretize(semi, tspan) + +summary_callback = SummaryCallback() + +analysis_interval = 2000 +analysis_callback = AnalysisCallback(semi, interval = analysis_interval) + +alive_callback = AliveCallback(analysis_interval = analysis_interval) + +save_solution = SaveSolutionCallback(interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim) + +stepsize_callback = StepsizeCallback(cfl = 0.5) + +callbacks = CallbackSet(summary_callback, + analysis_callback, alive_callback, + save_solution, + stepsize_callback) + +############################################################################### +# run the simulation + +sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false); + dt = stepsize_callback(ode), # solve needs some value here but it will be overwritten by the stepsize_callback + ode_default_options()..., callback = callbacks); + +plot(PlotData1D(sol, solution_variables=cons2cons)["rho"]) \ No newline at end of file diff --git a/src/equations/equation_of_state_ideal_gas.jl b/src/equations/equation_of_state_ideal_gas.jl new file mode 100644 index 00000000000..8d1408a35f4 --- /dev/null +++ b/src/equations/equation_of_state_ideal_gas.jl @@ -0,0 +1,51 @@ +@doc raw""" + This defines the polytropic ideal gas equation of state + given by the pressure and internal energy relations +```math +p = rho * R * T, e = cv * T +``` +""" +struct IdealGas{RealT} <: AbstractEquationOfState + gamma::RealT + R::RealT + cv::RealT +end + +# If not specified, `R` is taken to be the gas constant for air. However, the +# precise value does not matter since eliminating temperature yields non-dimensional +# formulas in terms of only `gamma`. +function IdealGas(gamma = 1.4, R = 287) + cv = R / (gamma - 1) + return IdealGas(promote(gamma, R, cv)...) +end + +function pressure(V, T, eos::IdealGas) + (; R) = eos + rho = inv(V) + p = rho * R * T + return p +end + +function internal_energy(V, T, eos::IdealGas) + (; cv) = eos + e = cv * T + return e +end + +function specific_entropy(V, T, eos::IdealGas) + (; cv, R) = eos + s = cv * log(T) + R * log(V) + return s +end + +function speed_of_sound(V, T, eos::IdealGas) + (; gamma) = eos + p = pressure(V, T, eos) + c2 = gamma * p * V + return sqrt(c2) +end + +################################################ +# additional helper functions +################################################ + From 9b489d0920fcfb84db3367b1b1c61b4adb6babd5 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Thu, 15 Jan 2026 16:51:18 -0600 Subject: [PATCH 003/360] adding vdW --- .../elixir_euler_nonideal_density_wave.jl | 17 ++++--- src/Trixi.jl | 2 +- src/equations/equation_of_state_vdw.jl | 44 +++++++++++++++++++ src/equations/equations_of_state.jl | 2 +- 4 files changed, 58 insertions(+), 7 deletions(-) create mode 100644 src/equations/equation_of_state_vdw.jl diff --git a/examples/tree_1d_dgsem/elixir_euler_nonideal_density_wave.jl b/examples/tree_1d_dgsem/elixir_euler_nonideal_density_wave.jl index 274d49cee35..765a23c254e 100644 --- a/examples/tree_1d_dgsem/elixir_euler_nonideal_density_wave.jl +++ b/examples/tree_1d_dgsem/elixir_euler_nonideal_density_wave.jl @@ -5,24 +5,30 @@ using Trixi: ForwardDiff ############################################################################### # semidiscretization of the compressible Euler equations -eos = IdealGas(1.4) +#eos = IdealGas(1.4) +eos = VanDerWaals(; a = 10, b = 1e-2, gamma = 1.4, R = 287) equations = NonIdealCompressibleEulerEquations1D(eos) function initial_condition_density_wave(x, t, equations::NonIdealCompressibleEulerEquations1D) - rho = 1 + 0.5 * sin(pi * x[1]) v1 = 0.1 + rho = 1 + 0.5 * sin(pi * (x[1] - v1 * t)) p = 10.0 V = inv(rho) # invert for temperature given p, V - tol = 10 * eps() T = 1.0 + tol = 100 * eps(T) dp = pressure(V, T, eos) - p - while abs(dp) / abs(p) > tol + iter = 1 + while abs(dp) / abs(p) > tol && iter < 100 dp = pressure(V, T, eos) - p dpdT_V = ForwardDiff.derivative(T -> pressure(V, T, eos), T) T = T - dp / dpdT_V + iter += 1 + end + if iter == 100 + println("Warning: solver for temperature(V, p) did not converge") end return prim2cons(SVector(V, v1, T), equations) @@ -72,4 +78,5 @@ sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false); dt = stepsize_callback(ode), # solve needs some value here but it will be overwritten by the stepsize_callback ode_default_options()..., callback = callbacks); -plot(PlotData1D(sol, solution_variables=cons2cons)["rho"]) \ No newline at end of file +# using Plots +# plot(PlotData1D(sol, solution_variables=cons2cons)["rho"]) \ No newline at end of file diff --git a/src/Trixi.jl b/src/Trixi.jl index c776258fdc8..78fe7829ffb 100644 --- a/src/Trixi.jl +++ b/src/Trixi.jl @@ -182,7 +182,7 @@ export AcousticPerturbationEquations2D, LinearElasticityEquations1D, PassiveTracerEquations -export NonIdealCompressibleEulerEquations1D, IdealGas +export NonIdealCompressibleEulerEquations1D, IdealGas, VanDerWaals export LaplaceDiffusion1D, LaplaceDiffusion2D, LaplaceDiffusion3D, LaplaceDiffusionEntropyVariables1D, LaplaceDiffusionEntropyVariables2D, diff --git a/src/equations/equation_of_state_vdw.jl b/src/equations/equation_of_state_vdw.jl new file mode 100644 index 00000000000..4562eda0aaf --- /dev/null +++ b/src/equations/equation_of_state_vdw.jl @@ -0,0 +1,44 @@ +# assume mass basis for simplicity +struct VanDerWaals{RealT} <: AbstractEquationOfState + a::RealT + b::RealT + R::RealT + gamma::RealT + cv::RealT +end + +# by default, van der Waals parameters are for N2 +function VanDerWaals(; a=174.64049524257663, b=0.001381308696129041, + gamma = 5/3, R=296.8390795484912) + cv = R / (gamma - 1) + return VanDerWaals(promote(a, b, R, gamma, cv)...) +end + +function pressure(V, T, eos::VanDerWaals) + (; a, b, R) = eos + rho = inv(V) + p = rho * R * T / (1 - rho * b) - a * rho^2 + return p +end + +function internal_energy(V, T, eos::VanDerWaals) + (; cv, a) = eos + rho = inv(V) + e = cv * T - a * rho + return e +end + +function specific_entropy(V, T, eos::VanDerWaals) + (; cv, b, R) = eos + s = cv * log(T) + R * log(V - b) # + s0 = -319.1595051898981 for consistency with Clapeyron.jl + return s +end + +function speed_of_sound(V, T, eos::VanDerWaals) + (; a, b, gamma) = eos + rho = inv(V) + e = internal_energy(V, T, eos) + c2 = gamma * (gamma - 1) * (e + rho * a) / (1 - rho * b)^2 - 2 * a * rho + return sqrt(c2) +end + diff --git a/src/equations/equations_of_state.jl b/src/equations/equations_of_state.jl index e9a4a51d541..bcfd6c9d0bb 100644 --- a/src/equations/equations_of_state.jl +++ b/src/equations/equations_of_state.jl @@ -13,7 +13,7 @@ is required to calculate wavespeed estimates for e.g., local Lax-Friedrichs flux abstract type AbstractEquationOfState end include("equation_of_state_ideal_gas.jl") -# include("equation_of_state_vdw.jl") +include("equation_of_state_vdw.jl") ##### # Some general fallback routines are provided below From ecf953551aaba8816e637e572378ea73b8c0dea9 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Thu, 15 Jan 2026 17:05:49 -0600 Subject: [PATCH 004/360] adding tests --- .../elixir_euler_nonideal_density_wave.jl | 5 ++-- test/test_tree_1d_euler.jl | 24 +++++++++++++++++++ 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/examples/tree_1d_dgsem/elixir_euler_nonideal_density_wave.jl b/examples/tree_1d_dgsem/elixir_euler_nonideal_density_wave.jl index 765a23c254e..b1ff652e04a 100644 --- a/examples/tree_1d_dgsem/elixir_euler_nonideal_density_wave.jl +++ b/examples/tree_1d_dgsem/elixir_euler_nonideal_density_wave.jl @@ -5,7 +5,6 @@ using Trixi: ForwardDiff ############################################################################### # semidiscretization of the compressible Euler equations -#eos = IdealGas(1.4) eos = VanDerWaals(; a = 10, b = 1e-2, gamma = 1.4, R = 287) equations = NonIdealCompressibleEulerEquations1D(eos) @@ -36,12 +35,12 @@ end initial_condition = initial_condition_density_wave -solver = DGSEM(polydeg = 3, surface_flux = flux_central) +solver = DGSEM(polydeg = 3, surface_flux = flux_lax_friedrichs) coordinates_min = -1.0 coordinates_max = 1.0 mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 4, + initial_refinement_level = 3, n_cells_max = 30_000) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) diff --git a/test/test_tree_1d_euler.jl b/test/test_tree_1d_euler.jl index e27d55b9609..94571c17f68 100644 --- a/test/test_tree_1d_euler.jl +++ b/test/test_tree_1d_euler.jl @@ -500,6 +500,30 @@ end # (e.g., from type instabilities) @test_allocations(Trixi.rhs!, semi, sol, 1000) end + +@trixi_testset "elixir_euler_nonideal_density_wave.jl" begin + mean_convergence = convergence_test(@__MODULE__, + joinpath(EXAMPLES_DIR, "tree_1d_dgsem", + "elixir_euler_nonideal_density_wave.jl"), + 3, initial_refinement_level = 3) + + @test isapprox(mean_convergence[:l2], [4.31664, 4.78633, 4.28456], rtol = 0.05) + + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + @test_allocations(Trixi.rhs!, semi, sol, 1000) +end + +@trixi_testset "elixir_euler_nonideal_density_wave.jl with ideal gas" begin + @test_trixi_include(joinpath(EXAMPLES_DIR, "tree_1d_dgsem", "elixir_euler_nonideal_density_wave.jl"), + eos = IdealGas(1.4), tspan = (0.0, 0.1), + l2 = [4.572238675002162e-5, 4.572238675006431e-6, 2.2861193337425137e-7], linf = [9.083503726636799e-5, 9.083503726865783e-6, 4.54175189901207e-7]) + + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + @test_allocations(Trixi.rhs!, semi, sol, 1000) +end + end end # module From 488c427816e75fa9903de6532f75f366d17fc2c9 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Thu, 15 Jan 2026 17:06:02 -0600 Subject: [PATCH 005/360] formatting --- .../elixir_euler_nonideal_density_wave.jl | 5 ++-- src/equations/equation_of_state_ideal_gas.jl | 7 +++--- src/equations/equation_of_state_vdw.jl | 11 ++++----- src/equations/equations_of_state.jl | 11 ++++----- .../nonideal_compressible_euler_1d.jl | 21 +++++++++-------- test/test_tree_1d_euler.jl | 23 +++++++++++++------ 6 files changed, 43 insertions(+), 35 deletions(-) diff --git a/examples/tree_1d_dgsem/elixir_euler_nonideal_density_wave.jl b/examples/tree_1d_dgsem/elixir_euler_nonideal_density_wave.jl index b1ff652e04a..e8e7950ff7d 100644 --- a/examples/tree_1d_dgsem/elixir_euler_nonideal_density_wave.jl +++ b/examples/tree_1d_dgsem/elixir_euler_nonideal_density_wave.jl @@ -8,7 +8,8 @@ using Trixi: ForwardDiff eos = VanDerWaals(; a = 10, b = 1e-2, gamma = 1.4, R = 287) equations = NonIdealCompressibleEulerEquations1D(eos) -function initial_condition_density_wave(x, t, equations::NonIdealCompressibleEulerEquations1D) +function initial_condition_density_wave(x, t, + equations::NonIdealCompressibleEulerEquations1D) v1 = 0.1 rho = 1 + 0.5 * sin(pi * (x[1] - v1 * t)) p = 10.0 @@ -78,4 +79,4 @@ sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false); ode_default_options()..., callback = callbacks); # using Plots -# plot(PlotData1D(sol, solution_variables=cons2cons)["rho"]) \ No newline at end of file +# plot(PlotData1D(sol, solution_variables=cons2cons)["rho"]) diff --git a/src/equations/equation_of_state_ideal_gas.jl b/src/equations/equation_of_state_ideal_gas.jl index 8d1408a35f4..962be98870a 100644 --- a/src/equations/equation_of_state_ideal_gas.jl +++ b/src/equations/equation_of_state_ideal_gas.jl @@ -22,14 +22,14 @@ end function pressure(V, T, eos::IdealGas) (; R) = eos rho = inv(V) - p = rho * R * T + p = rho * R * T return p end -function internal_energy(V, T, eos::IdealGas) +function internal_energy(V, T, eos::IdealGas) (; cv) = eos e = cv * T - return e + return e end function specific_entropy(V, T, eos::IdealGas) @@ -48,4 +48,3 @@ end ################################################ # additional helper functions ################################################ - diff --git a/src/equations/equation_of_state_vdw.jl b/src/equations/equation_of_state_vdw.jl index 4562eda0aaf..0c8babdf635 100644 --- a/src/equations/equation_of_state_vdw.jl +++ b/src/equations/equation_of_state_vdw.jl @@ -8,8 +8,8 @@ struct VanDerWaals{RealT} <: AbstractEquationOfState end # by default, van der Waals parameters are for N2 -function VanDerWaals(; a=174.64049524257663, b=0.001381308696129041, - gamma = 5/3, R=296.8390795484912) +function VanDerWaals(; a = 174.64049524257663, b = 0.001381308696129041, + gamma = 5 / 3, R = 296.8390795484912) cv = R / (gamma - 1) return VanDerWaals(promote(a, b, R, gamma, cv)...) end @@ -21,11 +21,11 @@ function pressure(V, T, eos::VanDerWaals) return p end -function internal_energy(V, T, eos::VanDerWaals) +function internal_energy(V, T, eos::VanDerWaals) (; cv, a) = eos rho = inv(V) - e = cv * T - a * rho - return e + e = cv * T - a * rho + return e end function specific_entropy(V, T, eos::VanDerWaals) @@ -41,4 +41,3 @@ function speed_of_sound(V, T, eos::VanDerWaals) c2 = gamma * (gamma - 1) * (e + rho * a) / (1 - rho * b)^2 - 2 * a * rho return sqrt(c2) end - diff --git a/src/equations/equations_of_state.jl b/src/equations/equations_of_state.jl index bcfd6c9d0bb..f12a17e94d1 100644 --- a/src/equations/equations_of_state.jl +++ b/src/equations/equations_of_state.jl @@ -24,7 +24,7 @@ function gibbs_free_energy(V, T, eos) p = pressure(V, T, eos) e = internal_energy(V, T, eos) h = e + p * V - return h - T * s + return h - T * s end # calculates dpdV_T, dpdT_V @@ -49,15 +49,14 @@ function temperature(V, e, eos::AbstractEquationOfState; initial_T = 1.0) de = internal_energy(V, T, eos) - e # c_v = dedT_V > 0, which should guarantee convergence of this iteration - dedT_V = heat_capacity_constant_volume(V, T, eos) + dedT_V = heat_capacity_constant_volume(V, T, eos) T = T - de / dedT_V - iter += 1 + iter += 1 end - if iter==100 - println("Warning: nonlinear solve in `temperature(V, T, eos)` did not converge. " * + if iter == 100 + println("Warning: nonlinear solve in `temperature(V, T, eos)` did not converge. " * "Final states: iter = $iter, V, e = $V, $e with de = $de") end return T end - diff --git a/src/equations/nonideal_compressible_euler_1d.jl b/src/equations/nonideal_compressible_euler_1d.jl index 911db24cbd9..2d4fa3fcf7c 100644 --- a/src/equations/nonideal_compressible_euler_1d.jl +++ b/src/equations/nonideal_compressible_euler_1d.jl @@ -38,7 +38,7 @@ Because of this, the primitive variables are also defined to be `V, v1, T` (inst mass basis unless otherewise specified. """ struct NonIdealCompressibleEulerEquations1D{EoS_T <: AbstractEquationOfState} <: - AbstractCompressibleEulerEquations{1, 3} + AbstractCompressibleEulerEquations{1, 3} equation_of_state::EoS_T end @@ -52,7 +52,8 @@ varnames(::typeof(cons2prim), ::NonIdealCompressibleEulerEquations1D) = ("V", "v A constant initial condition to test free-stream preservation. """ -function initial_condition_constant(x, t, equations::NonIdealCompressibleEulerEquations1D) +function initial_condition_constant(x, t, + equations::NonIdealCompressibleEulerEquations1D) RealT = eltype(x) rho = 1 rho_v1 = convert(RealT, 0.1) @@ -84,7 +85,8 @@ Should be used together with [`TreeMesh`](@ref). end # Calculate 1D flux for a single point -@inline function flux(u, orientation::Integer, equations::NonIdealCompressibleEulerEquations1D) +@inline function flux(u, orientation::Integer, + equations::NonIdealCompressibleEulerEquations1D) eos = equations.equation_of_state rho, rho_v1, rho_E = u @@ -106,7 +108,7 @@ end eos = equations.equation_of_state c_ll = speed_of_sound(V_ll, T_ll, eos) - c_rr = speed_of_sound(V_rr, T_rr, eos) + c_rr = speed_of_sound(V_rr, T_rr, eos) λ_min = v1_ll - c_ll λ_max = v1_rr + c_rr @@ -121,11 +123,11 @@ end v_mag_ll = abs(v1_ll) v_mag_rr = abs(v1_rr) - + # Calculate primitive variables and speed of sound eos = equations.equation_of_state c_ll = speed_of_sound(V_ll, T_ll, eos) - c_rr = speed_of_sound(V_rr, T_rr, eos) + c_rr = speed_of_sound(V_rr, T_rr, eos) return max(v_mag_ll + c_ll, v_mag_rr + c_rr) end @@ -139,7 +141,7 @@ end # Calculate primitive variables and speed of sound eos = equations.equation_of_state c_ll = speed_of_sound(V_ll, T_ll, eos) - c_rr = speed_of_sound(V_rr, T_rr, eos) + c_rr = speed_of_sound(V_rr, T_rr, eos) λ_min = min(v1_ll - c_ll, v1_rr - c_rr) λ_max = max(v1_ll + c_ll, v1_rr + c_rr) @@ -165,7 +167,7 @@ end V = inv(rho) v1 = rho_v1 * V e = (rho_E - 0.5 * rho_v1 * v1) * V - T = temperature(V, e, eos) + T = temperature(V, e, eos) return SVector(V, v1, T) end @@ -202,7 +204,7 @@ where `s` is the specific entropy determined by the equation of state. eos = equations.equation_of_state V, v1, T = cons2prim(u, equations) rho = u[1] - S = -rho * specific_entropy(V, T, eos) + S = -rho * specific_entropy(V, T, eos) return S end @@ -215,5 +217,4 @@ Default entropy is the mathematical entropy @inline function entropy(cons, equations::NonIdealCompressibleEulerEquations1D) return entropy_math(cons, equations) end - end # @muladd diff --git a/test/test_tree_1d_euler.jl b/test/test_tree_1d_euler.jl index 94571c17f68..eaa1951e090 100644 --- a/test/test_tree_1d_euler.jl +++ b/test/test_tree_1d_euler.jl @@ -503,9 +503,9 @@ end @trixi_testset "elixir_euler_nonideal_density_wave.jl" begin mean_convergence = convergence_test(@__MODULE__, - joinpath(EXAMPLES_DIR, "tree_1d_dgsem", - "elixir_euler_nonideal_density_wave.jl"), - 3, initial_refinement_level = 3) + joinpath(EXAMPLES_DIR, "tree_1d_dgsem", + "elixir_euler_nonideal_density_wave.jl"), + 3, initial_refinement_level = 3) @test isapprox(mean_convergence[:l2], [4.31664, 4.78633, 4.28456], rtol = 0.05) @@ -515,15 +515,24 @@ end end @trixi_testset "elixir_euler_nonideal_density_wave.jl with ideal gas" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "tree_1d_dgsem", "elixir_euler_nonideal_density_wave.jl"), - eos = IdealGas(1.4), tspan = (0.0, 0.1), - l2 = [4.572238675002162e-5, 4.572238675006431e-6, 2.2861193337425137e-7], linf = [9.083503726636799e-5, 9.083503726865783e-6, 4.54175189901207e-7]) + @test_trixi_include(joinpath(EXAMPLES_DIR, "tree_1d_dgsem", + "elixir_euler_nonideal_density_wave.jl"), + eos=IdealGas(1.4), tspan=(0.0, 0.1), + l2=[ + 4.572238675002162e-5, + 4.572238675006431e-6, + 2.2861193337425137e-7 + ], + linf=[ + 9.083503726636799e-5, + 9.083503726865783e-6, + 4.54175189901207e-7 + ]) # Ensure that we do not have excessive memory allocations # (e.g., from type instabilities) @test_allocations(Trixi.rhs!, semi, sol, 1000) end - end end # module From 085357878bd04d8b63f736143bd478aa3676d373 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Thu, 15 Jan 2026 17:24:40 -0600 Subject: [PATCH 006/360] fix spelling and formatting --- src/equations/equations_of_state.jl | 24 +++++++++++-------- .../nonideal_compressible_euler_1d.jl | 2 +- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/equations/equations_of_state.jl b/src/equations/equations_of_state.jl index f12a17e94d1..a5842621558 100644 --- a/src/equations/equations_of_state.jl +++ b/src/equations/equations_of_state.jl @@ -5,19 +5,23 @@ the following four functions: - `internal_energy(V, T, eos)` - `specific_entropy(V, T, eos)` - `speed_of_sound(V, T, eos)` -where `eos = equations.equation_of_state`. - -`specific_entropy` is required only to calculate `dSdt`, and `speed_of_sound` -is required to calculate wavespeed estimates for e.g., local Lax-Friedrichs fluxes. +where `eos = equations.equation_of_state`. `specific_entropy` is required to calculate the +mathematical entropy and entropy variables, and `speed_of_sound` is required to calculate +wavespeed estimates for e.g., local Lax-Friedrichs fluxes. + +Additional functions can also be specialized to particular equations of state to improve +efficiency. """ abstract type AbstractEquationOfState end include("equation_of_state_ideal_gas.jl") include("equation_of_state_vdw.jl") -##### +####################################################### +# # Some general fallback routines are provided below -##### +# +####################################################### function gibbs_free_energy(V, T, eos) s = specific_entropy(V, T, eos) @@ -48,14 +52,14 @@ function temperature(V, e, eos::AbstractEquationOfState; initial_T = 1.0) while abs(de) / abs(e) > tol && iter < 100 de = internal_energy(V, T, eos) - e - # c_v = dedT_V > 0, which should guarantee convergence of this iteration - dedT_V = heat_capacity_constant_volume(V, T, eos) + # c_v = de_dT_V > 0, which should guarantee convergence of this iteration + de_dT_V = heat_capacity_constant_volume(V, T, eos) - T = T - de / dedT_V + T = T - de / de_dT_V iter += 1 end if iter == 100 - println("Warning: nonlinear solve in `temperature(V, T, eos)` did not converge. " * + println("Warning: nonlinear solve in `temperature(V, T, eos)` did not converge within 100 iterations. " * "Final states: iter = $iter, V, e = $V, $e with de = $de") end return T diff --git a/src/equations/nonideal_compressible_euler_1d.jl b/src/equations/nonideal_compressible_euler_1d.jl index 2d4fa3fcf7c..cbd7c9655de 100644 --- a/src/equations/nonideal_compressible_euler_1d.jl +++ b/src/equations/nonideal_compressible_euler_1d.jl @@ -35,7 +35,7 @@ Similarly, the internal energy is specified by `e = e(V, T)`. Because of this, the primitive variables are also defined to be `V, v1, T` (instead of `rho, v1, p` for `CompressibleEulerEquations1D`). The implementation also assumes -mass basis unless otherewise specified. +mass basis unless otherwise specified. """ struct NonIdealCompressibleEulerEquations1D{EoS_T <: AbstractEquationOfState} <: AbstractCompressibleEulerEquations{1, 3} From 89a70247db1aba92acc03aa4b796406967c5d71b Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Thu, 15 Jan 2026 17:25:31 -0600 Subject: [PATCH 007/360] comments --- src/equations/equations_of_state.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/equations/equations_of_state.jl b/src/equations/equations_of_state.jl index a5842621558..08e34b1ccdc 100644 --- a/src/equations/equations_of_state.jl +++ b/src/equations/equations_of_state.jl @@ -52,7 +52,8 @@ function temperature(V, e, eos::AbstractEquationOfState; initial_T = 1.0) while abs(de) / abs(e) > tol && iter < 100 de = internal_energy(V, T, eos) - e - # c_v = de_dT_V > 0, which should guarantee convergence of this iteration + # for thermodynamically admissible states, c_v = de_dT_V > 0, which should + # guarantee convergence of this iteration. de_dT_V = heat_capacity_constant_volume(V, T, eos) T = T - de / de_dT_V From de5e713fef17e8e6e3ceb7f2138e5d3b0763e6fb Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Thu, 15 Jan 2026 19:19:15 -0600 Subject: [PATCH 008/360] add muladd --- src/equations/equation_of_state_vdw.jl | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/equations/equation_of_state_vdw.jl b/src/equations/equation_of_state_vdw.jl index 0c8babdf635..4514858c70d 100644 --- a/src/equations/equation_of_state_vdw.jl +++ b/src/equations/equation_of_state_vdw.jl @@ -1,3 +1,10 @@ +# By default, Julia/LLVM does not use fused multiply-add operations (FMAs). +# Since these FMAs can increase the performance of many numerical algorithms, +# we need to opt-in explicitly. +# See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. +@muladd begin +#! format: noindent + # assume mass basis for simplicity struct VanDerWaals{RealT} <: AbstractEquationOfState a::RealT @@ -41,3 +48,4 @@ function speed_of_sound(V, T, eos::VanDerWaals) c2 = gamma * (gamma - 1) * (e + rho * a) / (1 - rho * b)^2 - 2 * a * rho return sqrt(c2) end +end # @muladd From a0e7095541202853103a759f6a167a2e9e771a95 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Thu, 15 Jan 2026 19:19:19 -0600 Subject: [PATCH 009/360] fix tests --- test/test_tree_1d_euler.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_tree_1d_euler.jl b/test/test_tree_1d_euler.jl index 67833b6a632..37bf74e678f 100644 --- a/test/test_tree_1d_euler.jl +++ b/test/test_tree_1d_euler.jl @@ -503,7 +503,7 @@ end @trixi_testset "elixir_euler_nonideal_density_wave.jl" begin mean_convergence = convergence_test(@__MODULE__, - joinpath(EXAMPLES_DIR, "tree_1d_dgsem", + joinpath(EXAMPLES_DIR, "elixir_euler_nonideal_density_wave.jl"), 3, initial_refinement_level = 3) @@ -515,7 +515,7 @@ end end @trixi_testset "elixir_euler_nonideal_density_wave.jl with ideal gas" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "tree_1d_dgsem", + @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_nonideal_density_wave.jl"), eos=IdealGas(1.4), tspan=(0.0, 0.1), l2=[ From 1f96f2daf82775f6b5134599aaa2e07d53bd9dde Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Thu, 15 Jan 2026 19:45:29 -0600 Subject: [PATCH 010/360] remove convergence test, replace with l2, linf check --- test/test_tree_1d_euler.jl | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/test/test_tree_1d_euler.jl b/test/test_tree_1d_euler.jl index 37bf74e678f..f30616f6534 100644 --- a/test/test_tree_1d_euler.jl +++ b/test/test_tree_1d_euler.jl @@ -502,12 +502,19 @@ end end @trixi_testset "elixir_euler_nonideal_density_wave.jl" begin - mean_convergence = convergence_test(@__MODULE__, - joinpath(EXAMPLES_DIR, - "elixir_euler_nonideal_density_wave.jl"), - 3, initial_refinement_level = 3) - - @test isapprox(mean_convergence[:l2], [4.31664, 4.78633, 4.28456], rtol = 0.05) + @test_trixi_include(joinpath(EXAMPLES_DIR, + "elixir_euler_nonideal_density_wave.jl"), + tspan=(0.0, 0.1), + l2=[ + 0.0014455251184177067, + 0.00013913263972132486, + 0.05885337316113668 + ], + linf=[ + 0.006398094477253258, + 0.0006812697403103529, + 0.25849830799577944 + ]) # Ensure that we do not have excessive memory allocations # (e.g., from type instabilities) From 86d3f531460bf7f5b6ad9a969103894ea48f37a7 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Thu, 15 Jan 2026 20:09:19 -0600 Subject: [PATCH 011/360] fix test again --- test/test_tree_1d_euler.jl | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/test_tree_1d_euler.jl b/test/test_tree_1d_euler.jl index f30616f6534..c983516dddf 100644 --- a/test/test_tree_1d_euler.jl +++ b/test/test_tree_1d_euler.jl @@ -506,14 +506,14 @@ end "elixir_euler_nonideal_density_wave.jl"), tspan=(0.0, 0.1), l2=[ - 0.0014455251184177067, - 0.00013913263972132486, - 0.05885337316113668 + 7.286913401825591e-5, + 5.34303887566311e-5, + 0.003571648870380331 ], linf=[ - 0.006398094477253258, - 0.0006812697403103529, - 0.25849830799577944 + 0.0002452699378829859, + 0.0002907556176190429, + 0.010577510634327325 ]) # Ensure that we do not have excessive memory allocations From 930b152126fa2b79fd87999e7e867e1951f4e281 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Thu, 15 Jan 2026 20:58:51 -0600 Subject: [PATCH 012/360] comments --- src/equations/equation_of_state_vdw.jl | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/equations/equation_of_state_vdw.jl b/src/equations/equation_of_state_vdw.jl index 4514858c70d..d278776a708 100644 --- a/src/equations/equation_of_state_vdw.jl +++ b/src/equations/equation_of_state_vdw.jl @@ -37,7 +37,10 @@ end function specific_entropy(V, T, eos::VanDerWaals) (; cv, b, R) = eos - s = cv * log(T) + R * log(V - b) # + s0 = -319.1595051898981 for consistency with Clapeyron.jl + + # The specific entropy is defined up to some reference value. The value + # s0 = -319.1595051898981 recovers the specific entropy defined in Clapeyron.jl + s = cv * log(T) + R * log(V - b) return s end From 315879e2ec176a32d1e8255f997a3a8d3fe3008f Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Thu, 15 Jan 2026 21:12:01 -0600 Subject: [PATCH 013/360] test entropy --- test/test_unit.jl | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/test_unit.jl b/test/test_unit.jl index 585262fb06a..e78c94d45e9 100644 --- a/test/test_unit.jl +++ b/test/test_unit.jl @@ -764,6 +764,13 @@ end end end +@timed_testset "Test nonideal compressible Euler entropy" begin + eos = VanDerWaals(; a = 10, b = 0.01, R=287, gamma = 1.4) + equations = NonIdealCompressibleEulerEquations1D(eos) + u = prim2cons(SVector(2.0, 0.1, 10.0), equations) + @test ForwardDiff.gradient(u -> entropy(u, equations), u) ≈ cons2entropy(u, equations) +end + @timed_testset "StepsizeCallback" begin # Ensure a proper error is thrown if used with adaptive time integration schemes @test_trixi_include(joinpath(examples_dir(), "tree_2d_dgsem", From bda101fa014f8ae01c917b62eeedc42e500fb008 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Thu, 15 Jan 2026 21:12:16 -0600 Subject: [PATCH 014/360] test different wavespeeds --- test/test_tree_1d_euler.jl | 36 +++++++++++++++++++++++++++++------- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/test/test_tree_1d_euler.jl b/test/test_tree_1d_euler.jl index c983516dddf..419f78a04c6 100644 --- a/test/test_tree_1d_euler.jl +++ b/test/test_tree_1d_euler.jl @@ -521,25 +521,47 @@ end @test_allocations(Trixi.rhs!, semi, sol, 1000) end +@trixi_testset "elixir_euler_nonideal_density_wave.jl with FluxHLL(min_max_speed_davis)" begin + @test_trixi_include(joinpath(EXAMPLES_DIR, + "elixir_euler_nonideal_density_wave.jl"), + surface_flux=FluxHLL(min_max_speed_davis), tspan=(0.0, 0.1), + l2=[ + 7.167268496711242e-5, + 5.2305205608634944e-5, + 0.003511214352313455 + ], + linf=[ + 0.00025426725892341295, + 0.0002606497395930829, + 0.010007819676602026 + ]) + + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + @test_allocations(Trixi.rhs!, semi, sol, 1000) +end + @trixi_testset "elixir_euler_nonideal_density_wave.jl with ideal gas" begin @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_nonideal_density_wave.jl"), - eos=IdealGas(1.4), tspan=(0.0, 0.1), + eos=IdealGas(1.4), surface_flux=FluxHLL(min_max_speed_naive), + tspan=(0.0, 0.1), l2=[ - 4.572238675002162e-5, - 4.572238675006431e-6, - 2.2861193337425137e-7 + 4.5722592525355665e-5, + 4.572259252601982e-6, + 2.2861296102376542e-7 ], linf=[ - 9.083503726636799e-5, - 9.083503726865783e-6, - 4.54175189901207e-7 + 9.083779053276064e-5, + 9.083779053398189e-6, + 4.54188960219426e-7 ]) # Ensure that we do not have excessive memory allocations # (e.g., from type instabilities) @test_allocations(Trixi.rhs!, semi, sol, 1000) end + end end # module From 63d7e0fa1f0aaad456c55d2e2ec25de3ed6d8485 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Thu, 15 Jan 2026 21:12:24 -0600 Subject: [PATCH 015/360] formatting --- test/test_tree_1d_euler.jl | 1 - test/test_unit.jl | 5 +++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/test/test_tree_1d_euler.jl b/test/test_tree_1d_euler.jl index 419f78a04c6..77cf1241146 100644 --- a/test/test_tree_1d_euler.jl +++ b/test/test_tree_1d_euler.jl @@ -561,7 +561,6 @@ end # (e.g., from type instabilities) @test_allocations(Trixi.rhs!, semi, sol, 1000) end - end end # module diff --git a/test/test_unit.jl b/test/test_unit.jl index e78c94d45e9..35af7aeaeb1 100644 --- a/test/test_unit.jl +++ b/test/test_unit.jl @@ -765,10 +765,11 @@ end end @timed_testset "Test nonideal compressible Euler entropy" begin - eos = VanDerWaals(; a = 10, b = 0.01, R=287, gamma = 1.4) + eos = VanDerWaals(; a = 10, b = 0.01, R = 287, gamma = 1.4) equations = NonIdealCompressibleEulerEquations1D(eos) u = prim2cons(SVector(2.0, 0.1, 10.0), equations) - @test ForwardDiff.gradient(u -> entropy(u, equations), u) ≈ cons2entropy(u, equations) + @test ForwardDiff.gradient(u -> entropy(u, equations), u) ≈ + cons2entropy(u, equations) end @timed_testset "StepsizeCallback" begin From 693ad25a14ec87204c356d41abcdb5551e6877e0 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Thu, 15 Jan 2026 21:21:57 -0600 Subject: [PATCH 016/360] remove unused pressure derivative code --- src/equations/equations_of_state.jl | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/equations/equations_of_state.jl b/src/equations/equations_of_state.jl index 08e34b1ccdc..9bdcfd65626 100644 --- a/src/equations/equations_of_state.jl +++ b/src/equations/equations_of_state.jl @@ -31,13 +31,6 @@ function gibbs_free_energy(V, T, eos) return h - T * s end -# calculates dpdV_T, dpdT_V -@inline function calc_pressure_derivatives(V, T, eos::AbstractEquationOfState) - dpdV_T = ForwardDiff.derivative(V -> pressure(V, T, eos), V) - dpdT_V = ForwardDiff.derivative(T -> pressure(V, T, eos), T) - return dpdV_T, dpdT_V -end - @inline function heat_capacity_constant_volume(V, T, eos::AbstractEquationOfState) return ForwardDiff.derivative(T -> internal_energy(V, T, eos), T) end From 99799b385b06f5a474fe3a8aadcc80ac07480a54 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Thu, 15 Jan 2026 21:35:28 -0600 Subject: [PATCH 017/360] fix test --- test/test_unit.jl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/test_unit.jl b/test/test_unit.jl index 35af7aeaeb1..ab380af6567 100644 --- a/test/test_unit.jl +++ b/test/test_unit.jl @@ -7,6 +7,8 @@ using LinearAlgebra: norm, dot using SparseArrays using DelimitedFiles: readdlm +using ForwardDiff + # Use Convex and ECOS to load the extension that extends functions for testing # PERK Single p2 Constructors using Convex: Convex From 8b3d70d09638406d380c0237dc4a23f0b3e27671 Mon Sep 17 00:00:00 2001 From: Jesse Chan <1156048+jlchan@users.noreply.github.com> Date: Thu, 15 Jan 2026 23:14:57 -0600 Subject: [PATCH 018/360] Apply suggestion from @jlchan --- src/equations/nonideal_compressible_euler_1d.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/equations/nonideal_compressible_euler_1d.jl b/src/equations/nonideal_compressible_euler_1d.jl index cbd7c9655de..ce0fcaed5a1 100644 --- a/src/equations/nonideal_compressible_euler_1d.jl +++ b/src/equations/nonideal_compressible_euler_1d.jl @@ -24,7 +24,7 @@ The compressible Euler equations 0 \\ 0 \\ 0 \end{pmatrix} ``` -for an ideal gas with ratio of specific heats `gamma` in one space dimension. +for a gas with pressure specified by some equation of state in one space dimension. Here, ``\rho`` is the density, ``v_1`` the velocity, ``E`` the specific total energy, and the pressure in terms of specific volume `V = inv(rho)` and temperature `T` is given by some user-specified equation of state (EOS) From 237529faa4ae3f619dcb3b920bed954b743f973b Mon Sep 17 00:00:00 2001 From: Daniel_Doehring Date: Fri, 16 Jan 2026 10:44:18 +0100 Subject: [PATCH 019/360] docstrings etc --- src/equations/compressible_euler_1d.jl | 7 +++- src/equations/equation_of_state_ideal_gas.jl | 37 +++++++++++++------ src/equations/equation_of_state_vdw.jl | 34 +++++++++++++++-- src/equations/equations_of_state.jl | 24 +++++++----- src/equations/ideal_glm_mhd_1d.jl | 9 ++++- .../nonideal_compressible_euler_1d.jl | 37 +++++++++++-------- src/equations/polytropic_euler_2d.jl | 6 +-- 7 files changed, 108 insertions(+), 46 deletions(-) diff --git a/src/equations/compressible_euler_1d.jl b/src/equations/compressible_euler_1d.jl index bdf953dbf86..63c65a3422d 100644 --- a/src/equations/compressible_euler_1d.jl +++ b/src/equations/compressible_euler_1d.jl @@ -1138,7 +1138,12 @@ end return 0.5f0 * (cons[2]^2) / cons[1] end -# Calculate internal energy for a conservative state `cons` +""" + energy_internal(cons, equations::AbstractCompressibleEulerEquations) + +Calculate internal energy ``e`` for a conservative state `cons` as the difference +of total energy and kinetic energy. +""" @inline function energy_internal(cons, equations::CompressibleEulerEquations1D) return energy_total(cons, equations) - energy_kinetic(cons, equations) end diff --git a/src/equations/equation_of_state_ideal_gas.jl b/src/equations/equation_of_state_ideal_gas.jl index 962be98870a..f7c4160a7e6 100644 --- a/src/equations/equation_of_state_ideal_gas.jl +++ b/src/equations/equation_of_state_ideal_gas.jl @@ -1,9 +1,12 @@ @doc raw""" - This defines the polytropic ideal gas equation of state - given by the pressure and internal energy relations + IdealGas{RealT} <: AbstractEquationOfState + +This defines the polytropic ideal gas equation of state +given by the pressure and internal energy relations ```math -p = rho * R * T, e = cv * T +p = \rho R T, \quad e = c_v T ``` +with ``c_v = \frac{R}{\gamma - 1}``. """ struct IdealGas{RealT} <: AbstractEquationOfState gamma::RealT @@ -11,14 +14,24 @@ struct IdealGas{RealT} <: AbstractEquationOfState cv::RealT end -# If not specified, `R` is taken to be the gas constant for air. However, the -# precise value does not matter since eliminating temperature yields non-dimensional -# formulas in terms of only `gamma`. +""" + IdealGas(gamma = 1.4, R = 287) + +If not specified, `R` is taken to be the gas constant for air. However, the +precise value does not matter since eliminating temperature yields non-dimensional +formulas in terms of only `gamma`. +""" function IdealGas(gamma = 1.4, R = 287) cv = R / (gamma - 1) return IdealGas(promote(gamma, R, cv)...) end +""" + pressure(V, T, eos::IdealGas) + +Computes pressure for an ideal gas from primitive variables (see [`NonIdealCompressibleEulerEquations1D`](@ref)) + specific volume `V` and temperature `T`. +""" function pressure(V, T, eos::IdealGas) (; R) = eos rho = inv(V) @@ -26,7 +39,13 @@ function pressure(V, T, eos::IdealGas) return p end -function internal_energy(V, T, eos::IdealGas) +""" + energy_internal(V, T, eos::IdealGas) + +Computes internal energy for an ideal gas from specific volume `V` and temperature `T` as +``e = c_v T``. +""" +function energy_internal(V, T, eos::IdealGas) (; cv) = eos e = cv * T return e @@ -44,7 +63,3 @@ function speed_of_sound(V, T, eos::IdealGas) c2 = gamma * p * V return sqrt(c2) end - -################################################ -# additional helper functions -################################################ diff --git a/src/equations/equation_of_state_vdw.jl b/src/equations/equation_of_state_vdw.jl index d278776a708..02c1b99f4e1 100644 --- a/src/equations/equation_of_state_vdw.jl +++ b/src/equations/equation_of_state_vdw.jl @@ -5,7 +5,16 @@ @muladd begin #! format: noindent -# assume mass basis for simplicity +@doc raw""" + VanDerWaals{RealT} <: AbstractEquationOfState + +This defines the van der Waals equation of state +given by the pressure and internal energy relations +```math +p = \frac{\rho R T}{1 - \rho b} - a \rho^2, \quad e = c_v T - a \rho +``` +with ``c_v = \frac{R}{\gamma - 1}``. +""" struct VanDerWaals{RealT} <: AbstractEquationOfState a::RealT b::RealT @@ -14,13 +23,24 @@ struct VanDerWaals{RealT} <: AbstractEquationOfState cv::RealT end -# by default, van der Waals parameters are for N2 +""" + VanDerWaals(; a = 174.64049524257663, b = 0.001381308696129041, + gamma = 5 / 3, R = 296.8390795484912) + +By default, van der Waals parameters are for N2. +""" function VanDerWaals(; a = 174.64049524257663, b = 0.001381308696129041, gamma = 5 / 3, R = 296.8390795484912) cv = R / (gamma - 1) return VanDerWaals(promote(a, b, R, gamma, cv)...) end +""" + pressure(V, T, eos::VanDerWaals) + +Computes pressure for a van der Waals gas from specific volume `V` and temperature `T`, +see also [`NonIdealCompressibleEulerEquations1D`](@ref). +""" function pressure(V, T, eos::VanDerWaals) (; a, b, R) = eos rho = inv(V) @@ -28,7 +48,13 @@ function pressure(V, T, eos::VanDerWaals) return p end -function internal_energy(V, T, eos::VanDerWaals) +""" + energy_internal(V, T, eos::VanDerWaals) + +Computes internal energy for a van der Waals gas from specific volume `V` and temperature `T` as +``e = c_v T - a \rho``. +""" +function energy_internal(V, T, eos::VanDerWaals) (; cv, a) = eos rho = inv(V) e = cv * T - a * rho @@ -47,7 +73,7 @@ end function speed_of_sound(V, T, eos::VanDerWaals) (; a, b, gamma) = eos rho = inv(V) - e = internal_energy(V, T, eos) + e = energy_internal(V, T, eos) c2 = gamma * (gamma - 1) * (e + rho * a) / (1 - rho * b)^2 - 2 * a * rho return sqrt(c2) end diff --git a/src/equations/equations_of_state.jl b/src/equations/equations_of_state.jl index 9bdcfd65626..cc5e4722000 100644 --- a/src/equations/equations_of_state.jl +++ b/src/equations/equations_of_state.jl @@ -1,13 +1,16 @@ @doc raw""" - The interface for an `AbstractEquationOfState` requires specifying + AbstractEquationOfState + +The interface for an `AbstractEquationOfState` requires specifying the following four functions: - `pressure(V, T, eos)` -- `internal_energy(V, T, eos)` +- `energy_internal(V, T, eos)` - `specific_entropy(V, T, eos)` - `speed_of_sound(V, T, eos)` -where `eos = equations.equation_of_state`. `specific_entropy` is required to calculate the -mathematical entropy and entropy variables, and `speed_of_sound` is required to calculate -wavespeed estimates for e.g., local Lax-Friedrichs fluxes. + +where `eos = equations.equation_of_state`. +`specific_entropy` is required to calculate the mathematical entropy and entropy variables, +and `speed_of_sound` is required to calculate wavespeed estimates for e.g., [`FluxLaxFriedrichs`](@ref). Additional functions can also be specialized to particular equations of state to improve efficiency. @@ -26,24 +29,25 @@ include("equation_of_state_vdw.jl") function gibbs_free_energy(V, T, eos) s = specific_entropy(V, T, eos) p = pressure(V, T, eos) - e = internal_energy(V, T, eos) + e = energy_internal(V, T, eos) h = e + p * V return h - T * s end +# compute c_v = de/dT @inline function heat_capacity_constant_volume(V, T, eos::AbstractEquationOfState) - return ForwardDiff.derivative(T -> internal_energy(V, T, eos), T) + return ForwardDiff.derivative(T -> energy_internal(V, T, eos), T) end # calculate the temperature as a function of specific volume `V` and internal energy `e` -# by using Newton's method to determine `T` such that `internal_energy(V, T, eos) = e`. +# by using Newton's method to determine `T` such that `energy_internal(V, T, eos) = e`. function temperature(V, e, eos::AbstractEquationOfState; initial_T = 1.0) tol = 10 * eps() T = initial_T - de = internal_energy(V, T, eos) - e + de = energy_internal(V, T, eos) - e iter = 1 while abs(de) / abs(e) > tol && iter < 100 - de = internal_energy(V, T, eos) - e + de = energy_internal(V, T, eos) - e # for thermodynamically admissible states, c_v = de_dT_V > 0, which should # guarantee convergence of this iteration. diff --git a/src/equations/ideal_glm_mhd_1d.jl b/src/equations/ideal_glm_mhd_1d.jl index 8630b2f1a13..78b04bc23b4 100644 --- a/src/equations/ideal_glm_mhd_1d.jl +++ b/src/equations/ideal_glm_mhd_1d.jl @@ -795,7 +795,12 @@ end return 0.5f0 * (cons[6]^2 + cons[7]^2 + cons[8]^2) end -# Calculate internal energy for a conservative state `cons` +""" + energy_internal(cons, equations::AbstractIdealGlmMhdEquations) + +Calculate internal energy for a conservative state `cons` as the difference +between total energy and kinetic + magnetic energies. +""" @inline function energy_internal(cons, equations::IdealGlmMhdEquations1D) return (energy_total(cons, equations) - @@ -804,7 +809,7 @@ end energy_magnetic(cons, equations)) end -# Calculate the cross helicity (\vec{v}⋅\vec{B}) for a conservative state `cons' +# Calculate the cross helicity (\vec{v}⋅\vec{B}) for a conservative state `cons` @inline function cross_helicity(cons, ::IdealGlmMhdEquations1D) return (cons[2] * cons[6] + cons[3] * cons[7] + cons[4] * cons[8]) / cons[1] end diff --git a/src/equations/nonideal_compressible_euler_1d.jl b/src/equations/nonideal_compressible_euler_1d.jl index ce0fcaed5a1..8ad9054cc64 100644 --- a/src/equations/nonideal_compressible_euler_1d.jl +++ b/src/equations/nonideal_compressible_euler_1d.jl @@ -12,34 +12,40 @@ The compressible Euler equations ```math \frac{\partial}{\partial t} \begin{pmatrix} -\rho \\ \rho v_1 \\ \rho E + \rho \\ \rho v_1 \\ \rho E \end{pmatrix} + \frac{\partial}{\partial x} \begin{pmatrix} -\rho v_1 \\ \rho v_1^2 + p \\ (\rho E + p) v_1 + \rho v_1 \\ \rho v_1^2 + p \\ (\rho E + p) v_1 \end{pmatrix} = \begin{pmatrix} -0 \\ 0 \\ 0 + 0 \\ 0 \\ 0 \end{pmatrix} ``` -for a gas with pressure specified by some equation of state in one space dimension. +for a gas with pressure ``p`` specified by some equation of state +(see [`AbstractEquationOfState`](@ref), [`IdealGasEquationOfState`](@ref), [`VanDerWaalsEquationOfState`](@ref)) +in one space dimension. + Here, ``\rho`` is the density, ``v_1`` the velocity, ``E`` the specific total energy, -and the pressure in terms of specific volume `V = inv(rho)` and temperature `T` is given +and the pressure (see [`pressure(V, T, eos::IdealGas)`](@ref), [`pressure(V, T, eos::VanDerWaals)`](@ref)) +is given in terms of specific volume `V = inv(rho)` and temperature `T` by some user-specified equation of state (EOS) ```math -p = p(V, T), +p = p(V, T) ``` -Similarly, the internal energy is specified by `e = e(V, T)`. + +Similarly, the internal energy is specified by `e = energy_internal(V, T, eos)`, see +[`energy_internal(V, T, eos::IdealGas)`](@ref), [`energy_internal(V, T, eos::VanDerWaals)`](@ref). Because of this, the primitive variables are also defined to be `V, v1, T` (instead of `rho, v1, p` for `CompressibleEulerEquations1D`). The implementation also assumes mass basis unless otherwise specified. """ -struct NonIdealCompressibleEulerEquations1D{EoS_T <: AbstractEquationOfState} <: +struct NonIdealCompressibleEulerEquations1D{EoS <: AbstractEquationOfState} <: AbstractCompressibleEulerEquations{1, 3} - equation_of_state::EoS_T + equation_of_state::EoS end function varnames(::typeof(cons2cons), ::NonIdealCompressibleEulerEquations1D) @@ -64,6 +70,7 @@ end """ boundary_condition_slip_wall(u_inner, orientation, direction, x, t, surface_flux_function, equations::NonIdealCompressibleEulerEquations1D) + Determine the boundary numerical surface flux for a slip wall condition. Imposes a zero normal velocity at the wall. Density and pressure are taken from the internal solution state and pressure. @@ -74,7 +81,7 @@ Should be used together with [`TreeMesh`](@ref). surface_flux_function, equations::NonIdealCompressibleEulerEquations1D) # compute the primitive variables - rho_local, v_normal, p_local = cons2prim(u_inner, equations) + _, v_normal, p_local = cons2prim(u_inner, equations) if isodd(direction) # flip sign of normal to make it outward pointing v_normal *= -1 @@ -89,7 +96,7 @@ end equations::NonIdealCompressibleEulerEquations1D) eos = equations.equation_of_state - rho, rho_v1, rho_E = u + _, rho_v1, rho_E = u V, v1, T = cons2prim(u, equations) p = pressure(V, T, eos) @@ -186,7 +193,7 @@ end V, v1, T = prim rho = inv(V) rho_v1 = rho * v1 - e = internal_energy(V, T, eos) + e = energy_internal(V, T, eos) rho_E = rho * e + 0.5 * rho_v1 * v1 return SVector(rho, rho_v1, rho_E) end @@ -202,17 +209,17 @@ where `s` is the specific entropy determined by the equation of state. """ @inline function entropy_math(u, equations::NonIdealCompressibleEulerEquations1D) eos = equations.equation_of_state - V, v1, T = cons2prim(u, equations) + V, _, T = cons2prim(u, equations) rho = u[1] S = -rho * specific_entropy(V, T, eos) return S end """ - entropy(cons, equations::AbstractCompressibleEulerEquations) + entropy(cons, equations::NonIdealCompressibleEulerEquations1D) Default entropy is the mathematical entropy -[`entropy_math(cons, equations::AbstractCompressibleEulerEquations)`](@ref). +[`entropy_math(cons, equations::NonIdealCompressibleEulerEquations1D)`](@ref). """ @inline function entropy(cons, equations::NonIdealCompressibleEulerEquations1D) return entropy_math(cons, equations) diff --git a/src/equations/polytropic_euler_2d.jl b/src/equations/polytropic_euler_2d.jl index 5120daecca3..55aaa17b36e 100644 --- a/src/equations/polytropic_euler_2d.jl +++ b/src/equations/polytropic_euler_2d.jl @@ -410,13 +410,13 @@ end p = pressure(u, equations) # Form of the internal energy depends on gas type if equations.gamma == 1 # isothermal gas - internal_energy = equations.kappa * log(rho) + energy_internal = equations.kappa * log(rho) else # equations.gamma > 1 # polytropic gas - internal_energy = equations.kappa * rho^(equations.gamma - 1) / + energy_internal = equations.kappa * rho^(equations.gamma - 1) / (equations.gamma - 1) end - w1 = internal_energy + p / rho - 0.5f0 * v_square + w1 = energy_internal + p / rho - 0.5f0 * v_square w2 = v1 w3 = v2 From 7984fe9540cf95d00eabf46f79d617490f7e0bbd Mon Sep 17 00:00:00 2001 From: Daniel_Doehring Date: Fri, 16 Jan 2026 10:47:22 +0100 Subject: [PATCH 020/360] typo --- src/equations/ideal_glm_mhd_1d.jl | 2 +- src/equations/ideal_glm_mhd_2d.jl | 4 ++-- src/equations/ideal_glm_mhd_3d.jl | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/equations/ideal_glm_mhd_1d.jl b/src/equations/ideal_glm_mhd_1d.jl index 78b04bc23b4..86fc0925ff9 100644 --- a/src/equations/ideal_glm_mhd_1d.jl +++ b/src/equations/ideal_glm_mhd_1d.jl @@ -789,7 +789,7 @@ Default entropy is the mathematical entropy return 0.5f0 * (cons[2]^2 + cons[3]^2 + cons[4]^2) / cons[1] end -# Calculate the magnetic energy for a conservative state `cons'. +# Calculate the magnetic energy for a conservative state `cons`. # OBS! For non-dinmensional form of the ideal MHD magnetic pressure ≡ magnetic energy @inline function energy_magnetic(cons, ::IdealGlmMhdEquations1D) return 0.5f0 * (cons[6]^2 + cons[7]^2 + cons[8]^2) diff --git a/src/equations/ideal_glm_mhd_2d.jl b/src/equations/ideal_glm_mhd_2d.jl index 3e53eb91634..659ef852fbb 100644 --- a/src/equations/ideal_glm_mhd_2d.jl +++ b/src/equations/ideal_glm_mhd_2d.jl @@ -1963,7 +1963,7 @@ end return 0.5f0 * (cons[2]^2 + cons[3]^2 + cons[4]^2) / cons[1] end -# Calculate the magnetic energy for a conservative state `cons'. +# Calculate the magnetic energy for a conservative state `cons`. # OBS! For non-dinmensional form of the ideal MHD magnetic pressure ≡ magnetic energy @inline function energy_magnetic(cons, ::IdealGlmMhdEquations2D) return 0.5f0 * (cons[6]^2 + cons[7]^2 + cons[8]^2) @@ -1988,7 +1988,7 @@ end return true end -# Calculate the cross helicity (\vec{v}⋅\vec{B}) for a conservative state `cons' +# Calculate the cross helicity (\vec{v}⋅\vec{B}) for a conservative state `cons` @inline function cross_helicity(cons, ::IdealGlmMhdEquations2D) return (cons[2] * cons[6] + cons[3] * cons[7] + cons[4] * cons[8]) / cons[1] end diff --git a/src/equations/ideal_glm_mhd_3d.jl b/src/equations/ideal_glm_mhd_3d.jl index 620c3c1f2a3..2d53dd85504 100644 --- a/src/equations/ideal_glm_mhd_3d.jl +++ b/src/equations/ideal_glm_mhd_3d.jl @@ -1587,7 +1587,7 @@ end return 0.5f0 * (cons[2]^2 + cons[3]^2 + cons[4]^2) / cons[1] end -# Calculate the magnetic energy for a conservative state `cons'. +# Calculate the magnetic energy for a conservative state `cons`. # OBS! For non-dinmensional form of the ideal MHD magnetic pressure ≡ magnetic energy @inline function energy_magnetic(cons, ::IdealGlmMhdEquations3D) return 0.5f0 * (cons[6]^2 + cons[7]^2 + cons[8]^2) @@ -1612,7 +1612,7 @@ end return true end -# Calculate the cross helicity (\vec{v}⋅\vec{B}) for a conservative state `cons' +# Calculate the cross helicity (\vec{v}⋅\vec{B}) for a conservative state `cons` @inline function cross_helicity(cons, ::IdealGlmMhdEquations3D) return (cons[2] * cons[6] + cons[3] * cons[7] + cons[4] * cons[8]) / cons[1] end From 9946308fbfab7f488be67ebc911882f569b48132 Mon Sep 17 00:00:00 2001 From: Daniel_Doehring Date: Fri, 16 Jan 2026 10:51:34 +0100 Subject: [PATCH 021/360] rev --- src/equations/nonideal_compressible_euler_1d.jl | 8 +++----- src/equations/polytropic_euler_2d.jl | 6 +++--- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/equations/nonideal_compressible_euler_1d.jl b/src/equations/nonideal_compressible_euler_1d.jl index 8ad9054cc64..9c64e7791ab 100644 --- a/src/equations/nonideal_compressible_euler_1d.jl +++ b/src/equations/nonideal_compressible_euler_1d.jl @@ -24,14 +24,12 @@ The compressible Euler equations 0 \\ 0 \\ 0 \end{pmatrix} ``` -for a gas with pressure ``p`` specified by some equation of state -(see [`AbstractEquationOfState`](@ref), [`IdealGasEquationOfState`](@ref), [`VanDerWaalsEquationOfState`](@ref)) -in one space dimension. +for a gas with pressure ``p`` specified by some equation of state in one space dimension. Here, ``\rho`` is the density, ``v_1`` the velocity, ``E`` the specific total energy, -and the pressure (see [`pressure(V, T, eos::IdealGas)`](@ref), [`pressure(V, T, eos::VanDerWaals)`](@ref)) -is given in terms of specific volume `V = inv(rho)` and temperature `T` +and the pressure ``p`` is given in terms of specific volume ``V = 1/\rho`` and temperature ``T`` by some user-specified equation of state (EOS) +(see [`pressure(V, T, eos::IdealGas)`](@ref), [`pressure(V, T, eos::VanDerWaals)`](@ref)) as ```math p = p(V, T) ``` diff --git a/src/equations/polytropic_euler_2d.jl b/src/equations/polytropic_euler_2d.jl index 55aaa17b36e..5120daecca3 100644 --- a/src/equations/polytropic_euler_2d.jl +++ b/src/equations/polytropic_euler_2d.jl @@ -410,13 +410,13 @@ end p = pressure(u, equations) # Form of the internal energy depends on gas type if equations.gamma == 1 # isothermal gas - energy_internal = equations.kappa * log(rho) + internal_energy = equations.kappa * log(rho) else # equations.gamma > 1 # polytropic gas - energy_internal = equations.kappa * rho^(equations.gamma - 1) / + internal_energy = equations.kappa * rho^(equations.gamma - 1) / (equations.gamma - 1) end - w1 = energy_internal + p / rho - 0.5f0 * v_square + w1 = internal_energy + p / rho - 0.5f0 * v_square w2 = v1 w3 = v2 From 6356f9b14a11e689e936131cb7e8b36774ebe02c Mon Sep 17 00:00:00 2001 From: Jesse Chan <1156048+jlchan@users.noreply.github.com> Date: Fri, 16 Jan 2026 09:20:21 -0600 Subject: [PATCH 022/360] Update src/equations/nonideal_compressible_euler_1d.jl Co-authored-by: Joshua Lampert <51029046+JoshuaLampert@users.noreply.github.com> --- src/equations/nonideal_compressible_euler_1d.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/equations/nonideal_compressible_euler_1d.jl b/src/equations/nonideal_compressible_euler_1d.jl index 9c64e7791ab..d9908366c22 100644 --- a/src/equations/nonideal_compressible_euler_1d.jl +++ b/src/equations/nonideal_compressible_euler_1d.jl @@ -6,7 +6,7 @@ #! format: noindent @doc raw""" - NonIdealCompressibleEulerEquations1D(gamma) + NonIdealCompressibleEulerEquations1D(equation_of_state) The compressible Euler equations ```math From 09f3b498ec894ba17ea1e9bd0cab816ba8914211 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Fri, 16 Jan 2026 09:43:27 -0600 Subject: [PATCH 023/360] add kwargs and docstring for temperature --- src/equations/equations_of_state.jl | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/src/equations/equations_of_state.jl b/src/equations/equations_of_state.jl index cc5e4722000..39269a5ca16 100644 --- a/src/equations/equations_of_state.jl +++ b/src/equations/equations_of_state.jl @@ -39,14 +39,24 @@ end return ForwardDiff.derivative(T -> energy_internal(V, T, eos), T) end -# calculate the temperature as a function of specific volume `V` and internal energy `e` -# by using Newton's method to determine `T` such that `energy_internal(V, T, eos) = e`. -function temperature(V, e, eos::AbstractEquationOfState; initial_T = 1.0) - tol = 10 * eps() +eos_newton_tol(eos::AbstractEquationOfState) = 10 * eps() + +""" + temperature(V, e, eos::AbstractEquationOfState; initial_T = 1.0, tol = eos_newton_tol(eos), + maxiter = 100) + + +Calculates the temperature as a function of specific volume `V` and internal energy `e` +by using Newton's method to determine `T` such that `energy_internal(V, T, eos) = e`. +Note that the tolerance may need to be adjusted based on the specific equation of state. +""" +function temperature(V, e, eos::AbstractEquationOfState; initial_T = 1.0, + tol = eos_newton_tol(eos), + maxiter = 100) T = initial_T de = energy_internal(V, T, eos) - e iter = 1 - while abs(de) / abs(e) > tol && iter < 100 + while abs(de) / abs(e) > tol && iter < maxiter de = energy_internal(V, T, eos) - e # for thermodynamically admissible states, c_v = de_dT_V > 0, which should @@ -57,7 +67,7 @@ function temperature(V, e, eos::AbstractEquationOfState; initial_T = 1.0) iter += 1 end if iter == 100 - println("Warning: nonlinear solve in `temperature(V, T, eos)` did not converge within 100 iterations. " * + println("Warning: nonlinear solve in `temperature(V, T, eos)` did not converge within $maxiter iterations. " * "Final states: iter = $iter, V, e = $V, $e with de = $de") end return T From c8222a059c6cc22eaeea0a81cd38d4603ba44615 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Fri, 16 Jan 2026 09:43:53 -0600 Subject: [PATCH 024/360] add consistency test with `CompressibleEulerEquations1D` --- .../elixir_euler_nonideal_density_wave.jl | 9 ++-- test/test_tree_1d_euler.jl | 47 ++++++++++++------- 2 files changed, 35 insertions(+), 21 deletions(-) diff --git a/examples/tree_1d_dgsem/elixir_euler_nonideal_density_wave.jl b/examples/tree_1d_dgsem/elixir_euler_nonideal_density_wave.jl index e8e7950ff7d..b86e5fd2444 100644 --- a/examples/tree_1d_dgsem/elixir_euler_nonideal_density_wave.jl +++ b/examples/tree_1d_dgsem/elixir_euler_nonideal_density_wave.jl @@ -8,11 +8,14 @@ using Trixi: ForwardDiff eos = VanDerWaals(; a = 10, b = 1e-2, gamma = 1.4, R = 287) equations = NonIdealCompressibleEulerEquations1D(eos) +# the default amplitude and frequency k are chosen to be consistent with +# initial_condition_density_wave for CompressibleEulerEquations1D function initial_condition_density_wave(x, t, - equations::NonIdealCompressibleEulerEquations1D) + equations::NonIdealCompressibleEulerEquations1D; + amplitude = 0.98, k = 2) v1 = 0.1 - rho = 1 + 0.5 * sin(pi * (x[1] - v1 * t)) - p = 10.0 + rho = 1 + amplitude * sin(k * pi * (x[1] - v1 * t)) + p = 20.0 V = inv(rho) diff --git a/test/test_tree_1d_euler.jl b/test/test_tree_1d_euler.jl index 77cf1241146..974dc466af1 100644 --- a/test/test_tree_1d_euler.jl +++ b/test/test_tree_1d_euler.jl @@ -506,14 +506,14 @@ end "elixir_euler_nonideal_density_wave.jl"), tspan=(0.0, 0.1), l2=[ - 7.286913401825591e-5, - 5.34303887566311e-5, - 0.003571648870380331 + 0.0015588934102678423, + 0.0003742341750148338, + 0.06751527068762786 ], linf=[ - 0.0002452699378829859, - 0.0002907556176190429, - 0.010577510634327325 + 0.002915446030492097, + 0.0007398343306145305, + 0.19630387991431064 ]) # Ensure that we do not have excessive memory allocations @@ -526,14 +526,14 @@ end "elixir_euler_nonideal_density_wave.jl"), surface_flux=FluxHLL(min_max_speed_davis), tspan=(0.0, 0.1), l2=[ - 7.167268496711242e-5, - 5.2305205608634944e-5, - 0.003511214352313455 + 0.001560204102149942, + 0.0003730629606214752, + 0.06761471309660204 ], linf=[ - 0.00025426725892341295, - 0.0002606497395930829, - 0.010007819676602026 + 0.0029288236477780227, + 0.0007326399340998047, + 0.19711518375221715 ]) # Ensure that we do not have excessive memory allocations @@ -547,16 +547,27 @@ end eos=IdealGas(1.4), surface_flux=FluxHLL(min_max_speed_naive), tspan=(0.0, 0.1), l2=[ - 4.5722592525355665e-5, - 4.572259252601982e-6, - 2.2861296102376542e-7 + 0.001422862066298697, + 0.00014228620662995602, + 7.1143103323789825e-6 ], linf=[ - 9.083779053276064e-5, - 9.083779053398189e-6, - 4.54188960219426e-7 + 0.0023347309200370883, + 0.00023347309200390763, + 1.1673654647381682e-5 ]) + # check that the IdealGas EOS recovers the same solution as + # CompressibleEulerEquations1D + sol_nonideal = deepcopy(sol) + trixi_include(joinpath(EXAMPLES_DIR, + "elixir_euler_nonideal_density_wave.jl"), + equations = CompressibleEulerEquations1D(1.4), + initial_condition = Trixi.initial_condition_density_wave, + surface_flux = FluxHLL(min_max_speed_naive), + tspan = (0.0, 0.1)) + @test norm(sol.u[end] - sol_nonideal.u[end]) < 10 * eps() * length(sol.u[end]) + # Ensure that we do not have excessive memory allocations # (e.g., from type instabilities) @test_allocations(Trixi.rhs!, semi, sol, 1000) From d23c81d19c7de0ebdd836de3cf1846447ddd33a3 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Fri, 16 Jan 2026 09:43:59 -0600 Subject: [PATCH 025/360] flux consistency checks --- test/test_unit.jl | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/test_unit.jl b/test/test_unit.jl index ab380af6567..e63fc172917 100644 --- a/test/test_unit.jl +++ b/test/test_unit.jl @@ -766,12 +766,14 @@ end end end -@timed_testset "Test nonideal compressible Euler entropy" begin +@timed_testset "Test consistency (fluxes, entropy/cons2entropy) for NonIdealCompressibleEulerEquations1D" begin eos = VanDerWaals(; a = 10, b = 0.01, R = 287, gamma = 1.4) equations = NonIdealCompressibleEulerEquations1D(eos) u = prim2cons(SVector(2.0, 0.1, 10.0), equations) @test ForwardDiff.gradient(u -> entropy(u, equations), u) ≈ cons2entropy(u, equations) + @test flux_lax_friedrichs(u, u, 1, equations) ≈ flux(u, 1, equations) + @test flux_hll(u, u, 1, equations) ≈ flux(u, 1, equations) end @timed_testset "StepsizeCallback" begin From 0081c7c20575099332bf4f1eb3ac32c74dc05259 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Fri, 16 Jan 2026 09:45:25 -0600 Subject: [PATCH 026/360] comment --- src/equations/equations_of_state.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/equations/equations_of_state.jl b/src/equations/equations_of_state.jl index 39269a5ca16..543d5e5c6a7 100644 --- a/src/equations/equations_of_state.jl +++ b/src/equations/equations_of_state.jl @@ -39,6 +39,7 @@ end return ForwardDiff.derivative(T -> energy_internal(V, T, eos), T) end +# relative tolerance for the Newton solver for temperature eos_newton_tol(eos::AbstractEquationOfState) = 10 * eps() """ @@ -56,7 +57,7 @@ function temperature(V, e, eos::AbstractEquationOfState; initial_T = 1.0, T = initial_T de = energy_internal(V, T, eos) - e iter = 1 - while abs(de) / abs(e) > tol && iter < maxiter + while abs(de) > tol * abs(e) && iter < maxiter de = energy_internal(V, T, eos) - e # for thermodynamically admissible states, c_v = de_dT_V > 0, which should From e1653c05ddec634318bf5220adf9e37593733d1d Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Fri, 16 Jan 2026 09:46:34 -0600 Subject: [PATCH 027/360] format --- test/test_unit.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_unit.jl b/test/test_unit.jl index e63fc172917..6f76a8b9495 100644 --- a/test/test_unit.jl +++ b/test/test_unit.jl @@ -773,7 +773,7 @@ end @test ForwardDiff.gradient(u -> entropy(u, equations), u) ≈ cons2entropy(u, equations) @test flux_lax_friedrichs(u, u, 1, equations) ≈ flux(u, 1, equations) - @test flux_hll(u, u, 1, equations) ≈ flux(u, 1, equations) + @test flux_hll(u, u, 1, equations) ≈ flux(u, 1, equations) end @timed_testset "StepsizeCallback" begin From b2ee0f6014c8251bb550fd4c5d9d132aece98643 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Fri, 16 Jan 2026 09:50:07 -0600 Subject: [PATCH 028/360] rho_E -> rho_e_total --- .../nonideal_compressible_euler_1d.jl | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/equations/nonideal_compressible_euler_1d.jl b/src/equations/nonideal_compressible_euler_1d.jl index d9908366c22..ea33730394a 100644 --- a/src/equations/nonideal_compressible_euler_1d.jl +++ b/src/equations/nonideal_compressible_euler_1d.jl @@ -12,12 +12,12 @@ The compressible Euler equations ```math \frac{\partial}{\partial t} \begin{pmatrix} - \rho \\ \rho v_1 \\ \rho E + \rho \\ \rho v_1 \\ \rho e_total \end{pmatrix} + \frac{\partial}{\partial x} \begin{pmatrix} - \rho v_1 \\ \rho v_1^2 + p \\ (\rho E + p) v_1 + \rho v_1 \\ \rho v_1^2 + p \\ (\rho e_total + p) v_1 \end{pmatrix} = \begin{pmatrix} @@ -26,7 +26,7 @@ The compressible Euler equations ``` for a gas with pressure ``p`` specified by some equation of state in one space dimension. -Here, ``\rho`` is the density, ``v_1`` the velocity, ``E`` the specific total energy, +Here, ``\rho`` is the density, ``v_1`` the velocity, ``e_total`` the specific total energy, and the pressure ``p`` is given in terms of specific volume ``V = 1/\rho`` and temperature ``T`` by some user-specified equation of state (EOS) (see [`pressure(V, T, eos::IdealGas)`](@ref), [`pressure(V, T, eos::VanDerWaals)`](@ref)) as @@ -47,7 +47,7 @@ struct NonIdealCompressibleEulerEquations1D{EoS <: AbstractEquationOfState} <: end function varnames(::typeof(cons2cons), ::NonIdealCompressibleEulerEquations1D) - return ("rho", "rho_v1", "rho_e") + return ("rho", "rho_v1", "rho_e_total") end varnames(::typeof(cons2prim), ::NonIdealCompressibleEulerEquations1D) = ("V", "v1", "T") @@ -94,14 +94,14 @@ end equations::NonIdealCompressibleEulerEquations1D) eos = equations.equation_of_state - _, rho_v1, rho_E = u + _, rho_v1, rho_e_total = u V, v1, T = cons2prim(u, equations) p = pressure(V, T, eos) # Ignore orientation since it is always "1" in 1D f1 = rho_v1 f2 = rho_v1 * v1 + p - f3 = (rho_E + p) * v1 + f3 = (rho_e_total + p) * v1 return SVector(f1, f2, f3) end @@ -167,11 +167,11 @@ end # Convert conservative variables to primitive @inline function cons2prim(u, equations::NonIdealCompressibleEulerEquations1D) eos = equations.equation_of_state - rho, rho_v1, rho_E = u + rho, rho_v1, rho_e_total = u V = inv(rho) v1 = rho_v1 * V - e = (rho_E - 0.5 * rho_v1 * v1) * V + e = (rho_e_total - 0.5 * rho_v1 * v1) * V T = temperature(V, e, eos) return SVector(V, v1, T) @@ -192,8 +192,8 @@ end rho = inv(V) rho_v1 = rho * v1 e = energy_internal(V, T, eos) - rho_E = rho * e + 0.5 * rho_v1 * v1 - return SVector(rho, rho_v1, rho_E) + rho_e_total = rho * e + 0.5 * rho_v1 * v1 + return SVector(rho, rho_v1, rho_e_total) end @doc raw""" From 8a154a1bdc3c24f2701d9e90ae69da3181638b10 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Fri, 16 Jan 2026 09:53:29 -0600 Subject: [PATCH 029/360] add doi/reference for VdW speed of sound --- src/equations/equation_of_state_vdw.jl | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/equations/equation_of_state_vdw.jl b/src/equations/equation_of_state_vdw.jl index 02c1b99f4e1..23d32872cfa 100644 --- a/src/equations/equation_of_state_vdw.jl +++ b/src/equations/equation_of_state_vdw.jl @@ -70,6 +70,10 @@ function specific_entropy(V, T, eos::VanDerWaals) return s end +# this formula is taken from (A.26) in the paper "An oscillation free +# shock-capturing method for compressible van der Waals supercritical +# fluid flows" by Pantano, Saurel, and Schmitt (2017). +# https://doi.org/10.1016/j.jcp.2017.01.057 function speed_of_sound(V, T, eos::VanDerWaals) (; a, b, gamma) = eos rho = inv(V) From d511bcb9c5721951439c99a59b320c949cfefbc9 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Fri, 16 Jan 2026 10:30:23 -0600 Subject: [PATCH 030/360] fix test --- test/test_tree_1d_euler.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/test/test_tree_1d_euler.jl b/test/test_tree_1d_euler.jl index 974dc466af1..601a987c346 100644 --- a/test/test_tree_1d_euler.jl +++ b/test/test_tree_1d_euler.jl @@ -2,6 +2,7 @@ module TestExamples1DEuler using Test using Trixi +using LinearAlgebra: norm include("test_trixi.jl") From 2da42dc9e8c7abf151071a827c0804008d0e17ad Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Fri, 16 Jan 2026 12:01:17 -0600 Subject: [PATCH 031/360] try fixing test again --- test/test_tree_1d_euler.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/test_tree_1d_euler.jl b/test/test_tree_1d_euler.jl index 601a987c346..5f8b05b22eb 100644 --- a/test/test_tree_1d_euler.jl +++ b/test/test_tree_1d_euler.jl @@ -2,7 +2,6 @@ module TestExamples1DEuler using Test using Trixi -using LinearAlgebra: norm include("test_trixi.jl") @@ -567,6 +566,8 @@ end initial_condition = Trixi.initial_condition_density_wave, surface_flux = FluxHLL(min_max_speed_naive), tspan = (0.0, 0.1)) + + using LinearAlgebra: norm @test norm(sol.u[end] - sol_nonideal.u[end]) < 10 * eps() * length(sol.u[end]) # Ensure that we do not have excessive memory allocations From 222109d47a42beefaa8f770db80f58dfe0841604 Mon Sep 17 00:00:00 2001 From: Jesse Chan <1156048+jlchan@users.noreply.github.com> Date: Fri, 16 Jan 2026 14:57:00 -0600 Subject: [PATCH 032/360] Apply suggestions from code review Co-authored-by: Joshua Lampert <51029046+JoshuaLampert@users.noreply.github.com> --- .../elixir_euler_nonideal_density_wave.jl | 11 ++++++----- src/equations/equation_of_state_ideal_gas.jl | 2 +- src/equations/equation_of_state_vdw.jl | 6 +++--- src/equations/equations_of_state.jl | 5 ++--- src/equations/nonideal_compressible_euler_1d.jl | 4 ++-- 5 files changed, 14 insertions(+), 14 deletions(-) diff --git a/examples/tree_1d_dgsem/elixir_euler_nonideal_density_wave.jl b/examples/tree_1d_dgsem/elixir_euler_nonideal_density_wave.jl index b86e5fd2444..2a52b3d4ea6 100644 --- a/examples/tree_1d_dgsem/elixir_euler_nonideal_density_wave.jl +++ b/examples/tree_1d_dgsem/elixir_euler_nonideal_density_wave.jl @@ -13,15 +13,16 @@ equations = NonIdealCompressibleEulerEquations1D(eos) function initial_condition_density_wave(x, t, equations::NonIdealCompressibleEulerEquations1D; amplitude = 0.98, k = 2) - v1 = 0.1 - rho = 1 + amplitude * sin(k * pi * (x[1] - v1 * t)) - p = 20.0 + RealT = eltype(x) + v1 = convert(RealT, 0.1) + rho = 1 + convert(RealT, amplitude) * sinpi(k * (x[1] - v1 * t)) + p = 20 V = inv(rho) # invert for temperature given p, V - T = 1.0 - tol = 100 * eps(T) + T = 1 + tol = 100 * eps(RealT) dp = pressure(V, T, eos) - p iter = 1 while abs(dp) / abs(p) > tol && iter < 100 diff --git a/src/equations/equation_of_state_ideal_gas.jl b/src/equations/equation_of_state_ideal_gas.jl index f7c4160a7e6..4af0a6b8612 100644 --- a/src/equations/equation_of_state_ideal_gas.jl +++ b/src/equations/equation_of_state_ideal_gas.jl @@ -30,7 +30,7 @@ end pressure(V, T, eos::IdealGas) Computes pressure for an ideal gas from primitive variables (see [`NonIdealCompressibleEulerEquations1D`](@ref)) - specific volume `V` and temperature `T`. +specific volume `V` and temperature `T`. """ function pressure(V, T, eos::IdealGas) (; R) = eos diff --git a/src/equations/equation_of_state_vdw.jl b/src/equations/equation_of_state_vdw.jl index 23d32872cfa..98fa94685fc 100644 --- a/src/equations/equation_of_state_vdw.jl +++ b/src/equations/equation_of_state_vdw.jl @@ -18,8 +18,8 @@ with ``c_v = \frac{R}{\gamma - 1}``. struct VanDerWaals{RealT} <: AbstractEquationOfState a::RealT b::RealT - R::RealT gamma::RealT + R::RealT cv::RealT end @@ -32,7 +32,7 @@ By default, van der Waals parameters are for N2. function VanDerWaals(; a = 174.64049524257663, b = 0.001381308696129041, gamma = 5 / 3, R = 296.8390795484912) cv = R / (gamma - 1) - return VanDerWaals(promote(a, b, R, gamma, cv)...) + return VanDerWaals(promote(a, b, gamma, R, cv)...) end """ @@ -70,7 +70,7 @@ function specific_entropy(V, T, eos::VanDerWaals) return s end -# this formula is taken from (A.26) in the paper "An oscillation free +# This formula is taken from (A.26) in the paper "An oscillation free # shock-capturing method for compressible van der Waals supercritical # fluid flows" by Pantano, Saurel, and Schmitt (2017). # https://doi.org/10.1016/j.jcp.2017.01.057 diff --git a/src/equations/equations_of_state.jl b/src/equations/equations_of_state.jl index 543d5e5c6a7..2c3fde7f005 100644 --- a/src/equations/equations_of_state.jl +++ b/src/equations/equations_of_state.jl @@ -9,7 +9,7 @@ the following four functions: - `speed_of_sound(V, T, eos)` where `eos = equations.equation_of_state`. -`specific_entropy` is required to calculate the mathematical entropy and entropy variables, +`specific_entropy` is required to calculate the mathematical entropy and entropy variables, and `speed_of_sound` is required to calculate wavespeed estimates for e.g., [`FluxLaxFriedrichs`](@ref). Additional functions can also be specialized to particular equations of state to improve @@ -52,8 +52,7 @@ by using Newton's method to determine `T` such that `energy_internal(V, T, eos) Note that the tolerance may need to be adjusted based on the specific equation of state. """ function temperature(V, e, eos::AbstractEquationOfState; initial_T = 1.0, - tol = eos_newton_tol(eos), - maxiter = 100) + tol = eos_newton_tol(eos), maxiter = 100) T = initial_T de = energy_internal(V, T, eos) - e iter = 1 diff --git a/src/equations/nonideal_compressible_euler_1d.jl b/src/equations/nonideal_compressible_euler_1d.jl index ea33730394a..b0368e4b17d 100644 --- a/src/equations/nonideal_compressible_euler_1d.jl +++ b/src/equations/nonideal_compressible_euler_1d.jl @@ -61,8 +61,8 @@ function initial_condition_constant(x, t, RealT = eltype(x) rho = 1 rho_v1 = convert(RealT, 0.1) - rho_e = 10 - return SVector(rho, rho_v1, rho_e) + rho_e_total = 10 + return SVector(rho, rho_v1, rho_e_total) end """ From e62e0fc613166c9ed30714ba051e4b7fb3c4009a Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Fri, 16 Jan 2026 15:33:54 -0600 Subject: [PATCH 033/360] remove unused initial_condition_constant --- src/equations/nonideal_compressible_euler_1d.jl | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/src/equations/nonideal_compressible_euler_1d.jl b/src/equations/nonideal_compressible_euler_1d.jl index b0368e4b17d..bca7d4bce01 100644 --- a/src/equations/nonideal_compressible_euler_1d.jl +++ b/src/equations/nonideal_compressible_euler_1d.jl @@ -51,20 +51,6 @@ function varnames(::typeof(cons2cons), ::NonIdealCompressibleEulerEquations1D) end varnames(::typeof(cons2prim), ::NonIdealCompressibleEulerEquations1D) = ("V", "v1", "T") -""" - initial_condition_constant(x, t, equations::NonIdealCompressibleEulerEquations1D) - -A constant initial condition to test free-stream preservation. -""" -function initial_condition_constant(x, t, - equations::NonIdealCompressibleEulerEquations1D) - RealT = eltype(x) - rho = 1 - rho_v1 = convert(RealT, 0.1) - rho_e_total = 10 - return SVector(rho, rho_v1, rho_e_total) -end - """ boundary_condition_slip_wall(u_inner, orientation, direction, x, t, surface_flux_function, equations::NonIdealCompressibleEulerEquations1D) From 4d0b55c0a817c7768e93c0f9cc77a3cdb1674905 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Fri, 16 Jan 2026 15:42:40 -0600 Subject: [PATCH 034/360] add `temperature` specializations --- src/equations/equation_of_state_ideal_gas.jl | 18 ++++++++++++++++++ src/equations/equation_of_state_vdw.jl | 11 +++++++++++ 2 files changed, 29 insertions(+) diff --git a/src/equations/equation_of_state_ideal_gas.jl b/src/equations/equation_of_state_ideal_gas.jl index 4af0a6b8612..7fa9d78245c 100644 --- a/src/equations/equation_of_state_ideal_gas.jl +++ b/src/equations/equation_of_state_ideal_gas.jl @@ -1,3 +1,10 @@ +# By default, Julia/LLVM does not use fused multiply-add operations (FMAs). +# Since these FMAs can increase the performance of many numerical algorithms, +# we need to opt-in explicitly. +# See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. +@muladd begin +#! format: noindent + @doc raw""" IdealGas{RealT} <: AbstractEquationOfState @@ -63,3 +70,14 @@ function speed_of_sound(V, T, eos::IdealGas) c2 = gamma * p * V return sqrt(c2) end + +# This is not a required interface function, but specializing it +# if an explicit function is available can improve performance. +# For general EOS, this is calculated via a Newton solve. +function temperature(V, e, eos::IdealGas) + (; cv) = eos + T = e / cv + return T +end + +end # @muladd \ No newline at end of file diff --git a/src/equations/equation_of_state_vdw.jl b/src/equations/equation_of_state_vdw.jl index 98fa94685fc..29f9e651f3a 100644 --- a/src/equations/equation_of_state_vdw.jl +++ b/src/equations/equation_of_state_vdw.jl @@ -81,4 +81,15 @@ function speed_of_sound(V, T, eos::VanDerWaals) c2 = gamma * (gamma - 1) * (e + rho * a) / (1 - rho * b)^2 - 2 * a * rho return sqrt(c2) end + +# This is not a required interface function, but specializing it +# if an explicit function is available can improve performance. +# For general EOS, this is calculated via a Newton solve. +function temperature(V, e, eos::VanDerWaals) + (; cv, a) = eos + rho = inv(V) + T = (e + a * rho) / cv + return T +end + end # @muladd From ac628a540d8c13e74422c0d63da0d4e1ecc22ab1 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Fri, 16 Jan 2026 16:42:31 -0600 Subject: [PATCH 035/360] fix typo --- src/equations/equations_of_state.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/equations/equations_of_state.jl b/src/equations/equations_of_state.jl index 2c3fde7f005..a131d31b62c 100644 --- a/src/equations/equations_of_state.jl +++ b/src/equations/equations_of_state.jl @@ -66,7 +66,7 @@ function temperature(V, e, eos::AbstractEquationOfState; initial_T = 1.0, T = T - de / de_dT_V iter += 1 end - if iter == 100 + if iter == maxiter println("Warning: nonlinear solve in `temperature(V, T, eos)` did not converge within $maxiter iterations. " * "Final states: iter = $iter, V, e = $V, $e with de = $de") end From 37ae1f1d68bfd1e307b7785810a9f9609f018a97 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Fri, 16 Jan 2026 16:42:46 -0600 Subject: [PATCH 036/360] add vdw reference --- src/equations/equation_of_state_vdw.jl | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/equations/equation_of_state_vdw.jl b/src/equations/equation_of_state_vdw.jl index 29f9e651f3a..3e43272ee4e 100644 --- a/src/equations/equation_of_state_vdw.jl +++ b/src/equations/equation_of_state_vdw.jl @@ -13,7 +13,13 @@ given by the pressure and internal energy relations ```math p = \frac{\rho R T}{1 - \rho b} - a \rho^2, \quad e = c_v T - a \rho ``` -with ``c_v = \frac{R}{\gamma - 1}``. +with ``c_v = \frac{R}{\gamma - 1}``. This corresponds to the "simple +van der Waals" fluid with constant `c_v`, which can be found on p28 of +https://math.berkeley.edu/~evans/entropy.and.PDE.pdf. + +See also "An oscillation free shock-capturing method for compressible van +der Waals supercritical fluid flows" by Pantano, Saurel, and Schmitt (2017). +https://doi.org/10.1016/j.jcp.2017.01.057 """ struct VanDerWaals{RealT} <: AbstractEquationOfState a::RealT @@ -91,5 +97,4 @@ function temperature(V, e, eos::VanDerWaals) T = (e + a * rho) / cv return T end - end # @muladd From ad513ef9c164f3f762e9db311918bd851b7db6f4 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Fri, 16 Jan 2026 16:42:59 -0600 Subject: [PATCH 037/360] add muladd --- src/equations/equations_of_state.jl | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/equations/equations_of_state.jl b/src/equations/equations_of_state.jl index a131d31b62c..ae0c3d66c18 100644 --- a/src/equations/equations_of_state.jl +++ b/src/equations/equations_of_state.jl @@ -1,3 +1,10 @@ +# By default, Julia/LLVM does not use fused multiply-add operations (FMAs). +# Since these FMAs can increase the performance of many numerical algorithms, +# we need to opt-in explicitly. +# See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. +@muladd begin +#! format: noindent + @doc raw""" AbstractEquationOfState @@ -72,3 +79,4 @@ function temperature(V, e, eos::AbstractEquationOfState; initial_T = 1.0, end return T end +end # @muladd From 68742232fc6865eec4095dcce357b653c3364df0 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Fri, 16 Jan 2026 16:43:10 -0600 Subject: [PATCH 038/360] add test for specialized vs fallback `temperature` --- test/test_unit.jl | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/test_unit.jl b/test/test_unit.jl index 6f76a8b9495..58ee5354964 100644 --- a/test/test_unit.jl +++ b/test/test_unit.jl @@ -774,6 +774,13 @@ end cons2entropy(u, equations) @test flux_lax_friedrichs(u, u, 1, equations) ≈ flux(u, 1, equations) @test flux_hll(u, u, 1, equations) ≈ flux(u, 1, equations) + + # check that the fallback temperature and specialized temperature + # return the same value + V, v1, T = cons2prim(u, equations) + e = energy_internal(V, T, eos) + @test temperature(V, e, eos) ≈ + invoke(temperature, Tuple{Any, Any, Trixi.AbstractEquationOfState}, V, e, eos) end @timed_testset "StepsizeCallback" begin From fe0362038d0676332cc2590001119057d6af448b Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Fri, 16 Jan 2026 16:49:07 -0600 Subject: [PATCH 039/360] format --- src/equations/equation_of_state_ideal_gas.jl | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/equations/equation_of_state_ideal_gas.jl b/src/equations/equation_of_state_ideal_gas.jl index 7fa9d78245c..93172ce3c29 100644 --- a/src/equations/equation_of_state_ideal_gas.jl +++ b/src/equations/equation_of_state_ideal_gas.jl @@ -79,5 +79,4 @@ function temperature(V, e, eos::IdealGas) T = e / cv return T end - -end # @muladd \ No newline at end of file +end # @muladd From 5b919e7dcbfc7aef4352826539479f4c85d20352 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Fri, 16 Jan 2026 17:19:00 -0600 Subject: [PATCH 040/360] remove plotting code --- examples/tree_1d_dgsem/elixir_euler_nonideal_density_wave.jl | 3 --- 1 file changed, 3 deletions(-) diff --git a/examples/tree_1d_dgsem/elixir_euler_nonideal_density_wave.jl b/examples/tree_1d_dgsem/elixir_euler_nonideal_density_wave.jl index 2a52b3d4ea6..135a7871edb 100644 --- a/examples/tree_1d_dgsem/elixir_euler_nonideal_density_wave.jl +++ b/examples/tree_1d_dgsem/elixir_euler_nonideal_density_wave.jl @@ -81,6 +81,3 @@ callbacks = CallbackSet(summary_callback, sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false); dt = stepsize_callback(ode), # solve needs some value here but it will be overwritten by the stepsize_callback ode_default_options()..., callback = callbacks); - -# using Plots -# plot(PlotData1D(sol, solution_variables=cons2cons)["rho"]) From 39fb584c5a9fa53610610f2e450642461db98bd8 Mon Sep 17 00:00:00 2001 From: Jesse Chan <1156048+jlchan@users.noreply.github.com> Date: Fri, 16 Jan 2026 17:19:26 -0600 Subject: [PATCH 041/360] Update src/equations/equations_of_state.jl Co-authored-by: Joshua Lampert <51029046+JoshuaLampert@users.noreply.github.com> --- src/equations/equations_of_state.jl | 1 - 1 file changed, 1 deletion(-) diff --git a/src/equations/equations_of_state.jl b/src/equations/equations_of_state.jl index ae0c3d66c18..a60767c79df 100644 --- a/src/equations/equations_of_state.jl +++ b/src/equations/equations_of_state.jl @@ -53,7 +53,6 @@ eos_newton_tol(eos::AbstractEquationOfState) = 10 * eps() temperature(V, e, eos::AbstractEquationOfState; initial_T = 1.0, tol = eos_newton_tol(eos), maxiter = 100) - Calculates the temperature as a function of specific volume `V` and internal energy `e` by using Newton's method to determine `T` such that `energy_internal(V, T, eos) = e`. Note that the tolerance may need to be adjusted based on the specific equation of state. From acb0619134989097e345393f08a9e72686056c84 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Fri, 16 Jan 2026 17:23:45 -0600 Subject: [PATCH 042/360] add flux_terashima and flux_central_terashima for vdw --- src/Trixi.jl | 1 + src/equations/equation_of_state_vdw.jl | 13 +++ src/equations/equations_of_state.jl | 29 +++++- .../nonideal_compressible_euler_1d.jl | 93 +++++++++++++++++++ 4 files changed, 135 insertions(+), 1 deletion(-) diff --git a/src/Trixi.jl b/src/Trixi.jl index ac503926df9..8e198247a4c 100644 --- a/src/Trixi.jl +++ b/src/Trixi.jl @@ -203,6 +203,7 @@ export flux, flux_central, flux_lax_friedrichs, flux_hll, flux_hllc, flux_hlle, flux_fjordholm_etal, flux_nonconservative_fjordholm_etal, flux_wintermeyer_etal, flux_nonconservative_wintermeyer_etal, flux_chan_etal, flux_nonconservative_chan_etal, flux_winters_etal, + flux_terashima_etal, flux_central_terashima_etal, FluxPlusDissipation, DissipationGlobalLaxFriedrichs, DissipationLocalLaxFriedrichs, DissipationLaxFriedrichsEntropyVariables, DissipationMatrixWintersEtal, FluxLaxFriedrichs, max_abs_speed_naive, max_abs_speed, diff --git a/src/equations/equation_of_state_vdw.jl b/src/equations/equation_of_state_vdw.jl index 3e43272ee4e..6aaec43cb3b 100644 --- a/src/equations/equation_of_state_vdw.jl +++ b/src/equations/equation_of_state_vdw.jl @@ -97,4 +97,17 @@ function temperature(V, e, eos::VanDerWaals) T = (e + a * rho) / cv return T end + +# This is not a required interface function, but specializing it +# if an explicit function is available can improve performance. +function calc_pressure_derivatives(V, T, eos::VanDerWaals) + (; a, b, R) = eos + rho = inv(V) + RT = R * T + one_minus_b_rho = (1 - b * rho) + dpdT_V = rho * R / (1 - rho * b) + dpdrho_T = RT / one_minus_b_rho + (RT * b * rho) / (one_minus_b_rho^2) - + 2 * a * rho + return dpdT_V, -dpdrho_T / V^2 +end end # @muladd diff --git a/src/equations/equations_of_state.jl b/src/equations/equations_of_state.jl index ae0c3d66c18..66e8e05dbcb 100644 --- a/src/equations/equations_of_state.jl +++ b/src/equations/equations_of_state.jl @@ -29,7 +29,7 @@ include("equation_of_state_vdw.jl") ####################################################### # -# Some general fallback routines are provided below +# Some EOS-agnostic routines are provided below # ####################################################### @@ -46,6 +46,12 @@ end return ForwardDiff.derivative(T -> energy_internal(V, T, eos), T) end +function calc_pressure_derivatives(model, V, T) + dpdV_T = ForwardDiff.derivative(V -> Clapeyron.VT_pressure(model, V, T), V) + dpdT_V = ForwardDiff.derivative(T -> Clapeyron.VT_pressure(model, V, T), T) + return dpdT_V, dpdV_T +end + # relative tolerance for the Newton solver for temperature eos_newton_tol(eos::AbstractEquationOfState) = 10 * eps() @@ -79,4 +85,25 @@ function temperature(V, e, eos::AbstractEquationOfState; initial_T = 1.0, end return T end + +@inline function internal_energy_density(u, + equations::NonIdealCompressibleEulerEquations1D) + rho, rho_v1, rho_e_total = u + rho_e = rho_e_total - 0.5f0 * rho_v1^2 / rho + return rho_e +end + +# helper function used in [`flux_terashima_etal`](@ref) and [`flux_terashima_etal_central`](@ref) +@inline function drho_e_drho_at_const_p(V, T, eos) + rho = inv(V) + e = energy_internal(V, T, eos) + + dpdT_V, dpdV_T = calc_pressure_derivatives(V, T, eos) + dpdrho_T = dpdV_T * (-V / rho) # V = inv(rho), so dVdrho = -1/rho^2 = -V^2. + dedV_T = T * dpdT_V - pressure(V, T, eos) + drho_e_drho_T = e + rho * dedV_T * (-V / rho) # d(rho_e)/drho_|T = e + rho * dedV|T * dVdrho + + c_v = heat_capacity_constant_volume(V, T, eos) + return ((-rho * c_v) / (dpdT_V) * dpdrho_T + drho_e_drho_T) +end end # @muladd diff --git a/src/equations/nonideal_compressible_euler_1d.jl b/src/equations/nonideal_compressible_euler_1d.jl index bca7d4bce01..0e35317d4e9 100644 --- a/src/equations/nonideal_compressible_euler_1d.jl +++ b/src/equations/nonideal_compressible_euler_1d.jl @@ -91,6 +91,99 @@ end return SVector(f1, f2, f3) end +""" + flux_terashima_etal(u_ll, u_rr, orientation::Int, + equations::NonIdealCompressibleEulerEquations1D) + +Approximately pressure equilibrium conserving (APEC) flux from +"Approximately pressure-equilibrium-preserving scheme for fully conservative +simulations of compressible multi-species and real-fluid interfacial flows" +by Terashima, Ly, Ihme (2025). https://doi.org/10.1016/j.jcp.2024.11370 1 + +""" +function flux_terashima_etal(u_ll, u_rr, orientation::Int, + equations::NonIdealCompressibleEulerEquations1D) + eos = equations.equation_of_state + V_ll, v1_ll, T_ll = cons2prim(u_ll, equations) + V_rr, v1_rr, T_rr = cons2prim(u_rr, equations) + + rho_ll = u_ll[1] + rho_rr = u_rr[1] + rho_e_ll = internal_energy_density(u_ll, equations) + rho_e_rr = internal_energy_density(u_rr, equations) + p_ll = pressure(V_ll, T_ll, eos) + p_rr = pressure(V_rr, T_rr, eos) + + rho_avg = 0.5f0 * (rho_ll + rho_rr) + v1_avg = 0.5f0 * (v1_ll + v1_rr) + p_avg = 0.5f0 * (p_ll + p_rr) + rho_e_avg = 0.5f0 * (rho_e_ll + rho_e_rr) + p_v1_avg = 0.5f0 * (p_ll * v1_rr + p_rr * v1_ll) + + # chain rule from Terashima + drho_e_drho_p_ll = drho_e_drho_at_const_p(V_ll, T_ll, eos) + drho_e_drho_p_rr = drho_e_drho_at_const_p(V_rr, T_rr, eos) + rho_e_v1_avg = (rho_e_avg - + 0.25f0 * (drho_e_drho_p_rr - drho_e_drho_p_ll) * (rho_rr - rho_ll)) * + v1_avg + + # Ignore orientation since it is always "1" in 1D + f_rho = rho_avg * v1_avg + f_rho_v1 = rho_avg * v1_avg * v1_avg + p_avg + f_rho_E = rho_e_v1_avg + rho_avg * 0.5f0 * (v1_ll * v1_rr) * v1_avg + p_v1_avg + + return SVector(f_rho, f_rho_v1, f_rho_E) +end + +""" + flux_central_terashima_etal(u_ll, u_rr, orientation::Int, + equations::NonIdealCompressibleEulerEquations1D) + +A version of the central flux which uses the approximately pressure equilibrium conserving +(APEC) internal energy correction of +"Approximately pressure-equilibrium-preserving scheme for fully conservative +simulations of compressible multi-species and real-fluid interfacial flows" +by Terashima, Ly, Ihme (2025). https://doi.org/10.1016/j.jcp.2024.11370 +""" +function flux_central_terashima_etal(u_ll, u_rr, orientation::Int, + equations::NonIdealCompressibleEulerEquations1D) + eos = equations.equation_of_state + V_ll, v1_ll, T_ll = cons2prim(u_ll, equations) + V_rr, v1_rr, T_rr = cons2prim(u_rr, equations) + + rho_ll, rho_v1_ll, _ = u_ll + rho_rr, rho_v1_rr, _ = u_rr + rho_e_ll = internal_energy_density(u_ll, equations) + rho_e_rr = internal_energy_density(u_rr, equations) + p_ll = pressure(V_ll, T_ll, eos) + p_rr = pressure(V_rr, T_rr, eos) + + v1_avg = 0.5f0 * (v1_ll + v1_rr) + p_avg = 0.5f0 * (p_ll + p_rr) + rho_e_avg = 0.5f0 * (rho_e_ll + rho_e_rr) + + # chain rule from Terashima + drho_e_drho_p_ll = drho_e_drho_at_const_p(V_ll, T_ll, eos) + drho_e_drho_p_rr = drho_e_drho_at_const_p(V_rr, T_rr, eos) + rho_e_v1_avg = (rho_e_avg - + 0.25f0 * (drho_e_drho_p_rr - drho_e_drho_p_ll) * (rho_rr - rho_ll)) * + v1_avg + + # Ignore orientation since it is always "1" in 1D + f_rho = 0.5 * (rho_v1_ll + rho_v1_rr) + f_rho_v1 = 0.5 * (rho_v1_ll * v1_ll + rho_v1_rr * v1_rr) + p_avg + + # calculate internal energy (with APEC correction) and kinetic energy + # contributions separately in energy equation + ke_ll = 0.5 * v1_ll^2 + ke_rr = 0.5 * v1_rr^2 + f_rho_E = rho_e_v1_avg + + 0.5 * (rho_v1_ll * ke_ll + rho_v1_rr * ke_rr) + + 0.5 * (p_ll * v1_ll + p_rr * v1_rr) + + return SVector(f_rho, f_rho_v1, f_rho_E) +end + # Calculate estimates for minimum and maximum wave speeds for HLL-type fluxes @inline function min_max_speed_naive(u_ll, u_rr, orientation::Integer, equations::NonIdealCompressibleEulerEquations1D) From 924f0f2072f77697e8d411dba90df10098bf0245 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Fri, 16 Jan 2026 17:50:47 -0600 Subject: [PATCH 043/360] fix typo --- src/equations/equations_of_state.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/equations/equations_of_state.jl b/src/equations/equations_of_state.jl index 6ec109a6046..39bac538588 100644 --- a/src/equations/equations_of_state.jl +++ b/src/equations/equations_of_state.jl @@ -46,9 +46,9 @@ end return ForwardDiff.derivative(T -> energy_internal(V, T, eos), T) end -function calc_pressure_derivatives(model, V, T) - dpdV_T = ForwardDiff.derivative(V -> Clapeyron.VT_pressure(model, V, T), V) - dpdT_V = ForwardDiff.derivative(T -> Clapeyron.VT_pressure(model, V, T), T) +function calc_pressure_derivatives(V, T, eos) + dpdV_T = ForwardDiff.derivative(V -> pressure(V, T, eos), V) + dpdT_V = ForwardDiff.derivative(T -> pressure(V, T, eos), T) return dpdT_V, dpdV_T end From 05d2d92b7e3976f4e84012dc904a21731f3f2470 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Fri, 16 Jan 2026 17:51:00 -0600 Subject: [PATCH 044/360] add NEWS.md entry --- NEWS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/NEWS.md b/NEWS.md index e10537d208f..376e50f3827 100644 --- a/NEWS.md +++ b/NEWS.md @@ -20,6 +20,7 @@ for human readability. `flux_cons(u_ll, u_rr) + 0.5f0 * flux_noncons(u_ll, u_rr)` and `flux_cons(u_ll, u_rr) + 0.5f0 * flux_noncons(u_rr, u_ll)`. - Added support for `source_terms_parabolic`, which allows users to specify gradient-dependent source terms when solving parabolic equations ([#2721]). +- Added `NonIdealCompressibleEulerEquations1D`, which allows users to specify a non-ideal equation of state. Currently `IdealGas` and `VanDerWaals` are supported ([#2739]). ## Changes when updating to v0.13 from v0.12.x From 170749c1f712e3161c5d4b137a36ce282496b609 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Fri, 16 Jan 2026 21:01:33 -0600 Subject: [PATCH 045/360] remove untested wall BCs --- .../nonideal_compressible_euler_1d.jl | 24 ------------------- 1 file changed, 24 deletions(-) diff --git a/src/equations/nonideal_compressible_euler_1d.jl b/src/equations/nonideal_compressible_euler_1d.jl index bca7d4bce01..c0aebb1036a 100644 --- a/src/equations/nonideal_compressible_euler_1d.jl +++ b/src/equations/nonideal_compressible_euler_1d.jl @@ -51,30 +51,6 @@ function varnames(::typeof(cons2cons), ::NonIdealCompressibleEulerEquations1D) end varnames(::typeof(cons2prim), ::NonIdealCompressibleEulerEquations1D) = ("V", "v1", "T") -""" - boundary_condition_slip_wall(u_inner, orientation, direction, x, t, - surface_flux_function, equations::NonIdealCompressibleEulerEquations1D) - -Determine the boundary numerical surface flux for a slip wall condition. -Imposes a zero normal velocity at the wall. -Density and pressure are taken from the internal solution state and pressure. -Should be used together with [`TreeMesh`](@ref). -""" -@inline function boundary_condition_slip_wall(u_inner, orientation, - direction, x, t, - surface_flux_function, - equations::NonIdealCompressibleEulerEquations1D) - # compute the primitive variables - _, v_normal, p_local = cons2prim(u_inner, equations) - - if isodd(direction) # flip sign of normal to make it outward pointing - v_normal *= -1 - end - - # For the slip wall we directly set the flux as the normal velocity is zero - return SVector(0, p_local, 0) -end - # Calculate 1D flux for a single point @inline function flux(u, orientation::Integer, equations::NonIdealCompressibleEulerEquations1D) From 288e53d6dcf86ec433a565ecfc377287f53bcf55 Mon Sep 17 00:00:00 2001 From: Daniel Doehring Date: Sat, 17 Jan 2026 16:15:08 +0100 Subject: [PATCH 046/360] Apply suggestions from code review --- src/equations/equation_of_state_ideal_gas.jl | 4 ++-- src/equations/equation_of_state_vdw.jl | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/equations/equation_of_state_ideal_gas.jl b/src/equations/equation_of_state_ideal_gas.jl index 93172ce3c29..51cc6b21ada 100644 --- a/src/equations/equation_of_state_ideal_gas.jl +++ b/src/equations/equation_of_state_ideal_gas.jl @@ -6,7 +6,7 @@ #! format: noindent @doc raw""" - IdealGas{RealT} <: AbstractEquationOfState + IdealGas{RealT <: Real} <: AbstractEquationOfState This defines the polytropic ideal gas equation of state given by the pressure and internal energy relations @@ -15,7 +15,7 @@ p = \rho R T, \quad e = c_v T ``` with ``c_v = \frac{R}{\gamma - 1}``. """ -struct IdealGas{RealT} <: AbstractEquationOfState +struct IdealGas{RealT <: Real} <: AbstractEquationOfState gamma::RealT R::RealT cv::RealT diff --git a/src/equations/equation_of_state_vdw.jl b/src/equations/equation_of_state_vdw.jl index 3e43272ee4e..3314d6a4bfb 100644 --- a/src/equations/equation_of_state_vdw.jl +++ b/src/equations/equation_of_state_vdw.jl @@ -6,7 +6,7 @@ #! format: noindent @doc raw""" - VanDerWaals{RealT} <: AbstractEquationOfState + VanDerWaals{RealT <: Real} <: AbstractEquationOfState This defines the van der Waals equation of state given by the pressure and internal energy relations @@ -21,7 +21,7 @@ See also "An oscillation free shock-capturing method for compressible van der Waals supercritical fluid flows" by Pantano, Saurel, and Schmitt (2017). https://doi.org/10.1016/j.jcp.2017.01.057 """ -struct VanDerWaals{RealT} <: AbstractEquationOfState +struct VanDerWaals{RealT <: Real} <: AbstractEquationOfState a::RealT b::RealT gamma::RealT From 3c4557a54919e731013d39e46305951beb9abc4d Mon Sep 17 00:00:00 2001 From: Jesse Chan <1156048+jlchan@users.noreply.github.com> Date: Sun, 18 Jan 2026 16:10:11 -0600 Subject: [PATCH 047/360] Apply suggestions from code review Co-authored-by: Hendrik Ranocha --- src/equations/equation_of_state_vdw.jl | 4 ++-- src/equations/nonideal_compressible_euler_1d.jl | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/equations/equation_of_state_vdw.jl b/src/equations/equation_of_state_vdw.jl index 3314d6a4bfb..638f5d69f66 100644 --- a/src/equations/equation_of_state_vdw.jl +++ b/src/equations/equation_of_state_vdw.jl @@ -15,11 +15,11 @@ p = \frac{\rho R T}{1 - \rho b} - a \rho^2, \quad e = c_v T - a \rho ``` with ``c_v = \frac{R}{\gamma - 1}``. This corresponds to the "simple van der Waals" fluid with constant `c_v`, which can be found on p28 of -https://math.berkeley.edu/~evans/entropy.and.PDE.pdf. +. See also "An oscillation free shock-capturing method for compressible van der Waals supercritical fluid flows" by Pantano, Saurel, and Schmitt (2017). -https://doi.org/10.1016/j.jcp.2017.01.057 + """ struct VanDerWaals{RealT <: Real} <: AbstractEquationOfState a::RealT diff --git a/src/equations/nonideal_compressible_euler_1d.jl b/src/equations/nonideal_compressible_euler_1d.jl index c0aebb1036a..980b25e604c 100644 --- a/src/equations/nonideal_compressible_euler_1d.jl +++ b/src/equations/nonideal_compressible_euler_1d.jl @@ -144,7 +144,7 @@ end V, v1, T = cons2prim(u, equations) eos = equations.equation_of_state gibbs = gibbs_free_energy(V, T, eos) - return inv(T) * SVector(gibbs - 0.5 * v1^2, v1, -1) + return inv(T) * SVector(gibbs - 0.5f0 * v1^2, v1, -1) end # Convert primitive to conservative variables @@ -154,7 +154,7 @@ end rho = inv(V) rho_v1 = rho * v1 e = energy_internal(V, T, eos) - rho_e_total = rho * e + 0.5 * rho_v1 * v1 + rho_e_total = rho * e + 0.5f0 * rho_v1 * v1 return SVector(rho, rho_v1, rho_e_total) end From c27e7eb9a17d9090d903e525fb6c63a36f42c0ce Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Sun, 18 Jan 2026 16:11:19 -0600 Subject: [PATCH 048/360] e_total -> e_{total} --- src/equations/nonideal_compressible_euler_1d.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/equations/nonideal_compressible_euler_1d.jl b/src/equations/nonideal_compressible_euler_1d.jl index 980b25e604c..d6163dbb162 100644 --- a/src/equations/nonideal_compressible_euler_1d.jl +++ b/src/equations/nonideal_compressible_euler_1d.jl @@ -12,12 +12,12 @@ The compressible Euler equations ```math \frac{\partial}{\partial t} \begin{pmatrix} - \rho \\ \rho v_1 \\ \rho e_total + \rho \\ \rho v_1 \\ \rho e_{total} \end{pmatrix} + \frac{\partial}{\partial x} \begin{pmatrix} - \rho v_1 \\ \rho v_1^2 + p \\ (\rho e_total + p) v_1 + \rho v_1 \\ \rho v_1^2 + p \\ (\rho e_{total} + p) v_1 \end{pmatrix} = \begin{pmatrix} @@ -26,7 +26,7 @@ The compressible Euler equations ``` for a gas with pressure ``p`` specified by some equation of state in one space dimension. -Here, ``\rho`` is the density, ``v_1`` the velocity, ``e_total`` the specific total energy, +Here, ``\rho`` is the density, ``v_1`` the velocity, ``e_{total}`` the specific total energy, and the pressure ``p`` is given in terms of specific volume ``V = 1/\rho`` and temperature ``T`` by some user-specified equation of state (EOS) (see [`pressure(V, T, eos::IdealGas)`](@ref), [`pressure(V, T, eos::VanDerWaals)`](@ref)) as From c9d1d5d6e93ffc505557410548222b2e66de54ba Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Sun, 18 Jan 2026 16:11:54 -0600 Subject: [PATCH 049/360] replace specific_entropy -> entropy_specific comments --- src/equations/equation_of_state_ideal_gas.jl | 2 +- src/equations/equation_of_state_vdw.jl | 2 +- src/equations/equations_of_state.jl | 8 ++++---- src/equations/nonideal_compressible_euler_1d.jl | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/equations/equation_of_state_ideal_gas.jl b/src/equations/equation_of_state_ideal_gas.jl index 51cc6b21ada..bc5e74de035 100644 --- a/src/equations/equation_of_state_ideal_gas.jl +++ b/src/equations/equation_of_state_ideal_gas.jl @@ -58,7 +58,7 @@ function energy_internal(V, T, eos::IdealGas) return e end -function specific_entropy(V, T, eos::IdealGas) +function entropy_specific(V, T, eos::IdealGas) (; cv, R) = eos s = cv * log(T) + R * log(V) return s diff --git a/src/equations/equation_of_state_vdw.jl b/src/equations/equation_of_state_vdw.jl index 638f5d69f66..44db0ae0193 100644 --- a/src/equations/equation_of_state_vdw.jl +++ b/src/equations/equation_of_state_vdw.jl @@ -67,7 +67,7 @@ function energy_internal(V, T, eos::VanDerWaals) return e end -function specific_entropy(V, T, eos::VanDerWaals) +function entropy_specific(V, T, eos::VanDerWaals) (; cv, b, R) = eos # The specific entropy is defined up to some reference value. The value diff --git a/src/equations/equations_of_state.jl b/src/equations/equations_of_state.jl index a60767c79df..9bef26fc87c 100644 --- a/src/equations/equations_of_state.jl +++ b/src/equations/equations_of_state.jl @@ -11,12 +11,12 @@ The interface for an `AbstractEquationOfState` requires specifying the following four functions: - `pressure(V, T, eos)` -- `energy_internal(V, T, eos)` -- `specific_entropy(V, T, eos)` +- `energy_internal(V, T, eos)`, the specific internal energy +- `entropy_specific(V, T, eos)`, the specific entropy - `speed_of_sound(V, T, eos)` where `eos = equations.equation_of_state`. -`specific_entropy` is required to calculate the mathematical entropy and entropy variables, +`entropy_specific` is required to calculate the mathematical entropy and entropy variables, and `speed_of_sound` is required to calculate wavespeed estimates for e.g., [`FluxLaxFriedrichs`](@ref). Additional functions can also be specialized to particular equations of state to improve @@ -34,7 +34,7 @@ include("equation_of_state_vdw.jl") ####################################################### function gibbs_free_energy(V, T, eos) - s = specific_entropy(V, T, eos) + s = entropy_specific(V, T, eos) p = pressure(V, T, eos) e = energy_internal(V, T, eos) h = e + p * V diff --git a/src/equations/nonideal_compressible_euler_1d.jl b/src/equations/nonideal_compressible_euler_1d.jl index d6163dbb162..675a1957d9f 100644 --- a/src/equations/nonideal_compressible_euler_1d.jl +++ b/src/equations/nonideal_compressible_euler_1d.jl @@ -171,7 +171,7 @@ where `s` is the specific entropy determined by the equation of state. eos = equations.equation_of_state V, _, T = cons2prim(u, equations) rho = u[1] - S = -rho * specific_entropy(V, T, eos) + S = -rho * entropy_specific(V, T, eos) return S end From 86dc38d1fe04b1600302ada5892498a8b9d099df Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Tue, 20 Jan 2026 11:21:30 -0600 Subject: [PATCH 050/360] update NEWS.md --- NEWS.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index f5dc40463ff..a9204ea121e 100644 --- a/NEWS.md +++ b/NEWS.md @@ -5,6 +5,13 @@ Trixi.jl follows the interpretation of used in the Julia ecosystem. Notable changes will be documented in this file for human readability. +## Changes in the v0.14 lifecycle + +#### Added +- Added `NonIdealCompressibleEulerEquations1D`, which allows users to specify a non-ideal equation of state. Currently `IdealGas` and `VanDerWaals` are supported ([#2739]). + +#### Changed + ## Changes when updating to v0.14 from v0.13.x #### Changed @@ -26,7 +33,6 @@ for human readability. `flux_cons(u_ll, u_rr) + 0.5f0 * flux_noncons(u_ll, u_rr)` and `flux_cons(u_ll, u_rr) + 0.5f0 * flux_noncons(u_rr, u_ll)`. - Added support for `source_terms_parabolic`, which allows users to specify gradient-dependent source terms when solving parabolic equations ([#2721]). -- Added `NonIdealCompressibleEulerEquations1D`, which allows users to specify a non-ideal equation of state. Currently `IdealGas` and `VanDerWaals` are supported ([#2739]). ## Changes when updating to v0.13 from v0.12.x From f4088f792d88859c7bd8cffbd33871119ea2ed07 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Thu, 22 Jan 2026 13:27:58 -0600 Subject: [PATCH 051/360] adding type tests and some auxiliary functions --- .../nonideal_compressible_euler_1d.jl | 57 ++++++++++++++++++- test/test_type.jl | 37 ++++++++++++ 2 files changed, 93 insertions(+), 1 deletion(-) diff --git a/src/equations/nonideal_compressible_euler_1d.jl b/src/equations/nonideal_compressible_euler_1d.jl index 675a1957d9f..a341ff5b073 100644 --- a/src/equations/nonideal_compressible_euler_1d.jl +++ b/src/equations/nonideal_compressible_euler_1d.jl @@ -99,6 +99,22 @@ end return max(v_mag_ll + c_ll, v_mag_rr + c_rr) end +@inline function max_abs_speed_naive(u_ll, u_rr, orientation::Integer, + equations::NonIdealCompressibleEulerEquations1D) + V_ll, v1_ll, T_ll = cons2prim(u_ll, equations) + V_rr, v1_rr, T_rr = cons2prim(u_rr, equations) + + v_mag_ll = abs(v1_ll) + v_mag_rr = abs(v1_rr) + + # Calculate primitive variables and speed of sound + eos = equations.equation_of_state + c_ll = speed_of_sound(V_ll, T_ll, eos) + c_rr = speed_of_sound(V_rr, T_rr, eos) + + return max(v_mag_ll, v_mag_rr) + max(c_ll, c_rr) +end + # More refined estimates for minimum and maximum wave speeds for HLL-type fluxes @inline function min_max_speed_davis(u_ll, u_rr, orientation::Integer, equations::NonIdealCompressibleEulerEquations1D) @@ -133,7 +149,7 @@ end V = inv(rho) v1 = rho_v1 * V - e = (rho_e_total - 0.5 * rho_v1 * v1) * V + e = (rho_e_total - 0.5f0 * rho_v1 * v1) * V T = temperature(V, e, eos) return SVector(V, v1, T) @@ -184,4 +200,43 @@ Default entropy is the mathematical entropy @inline function entropy(cons, equations::NonIdealCompressibleEulerEquations1D) return entropy_math(cons, equations) end + +@inline function density(u, equations::NonIdealCompressibleEulerEquations1D) + rho = u[1] + return rho +end + +@inline function velocity(u, orientation_or_normal, + equations::NonIdealCompressibleEulerEquations1D) + return velocity(u, equations) +end + +@inline function velocity(u, equations::NonIdealCompressibleEulerEquations1D) + rho = u[1] + v1 = u[2] / rho + return v1 +end + +@inline function pressure(u, equations::NonIdealCompressibleEulerEquations1D) + eos = equations.equation_of_state + V, _, T = cons2prim(u, equations) + p = pressure(V, T, eos) + return p +end + +@inline function density_pressure(u, equations::NonIdealCompressibleEulerEquations1D) + eos = equations.equation_of_state + rho = u[1] + V, _, T = cons2prim(u, equations) + p = pressure(V, T, eos) + return rho * p +end + +@inline function energy_internal(u, equations::NonIdealCompressibleEulerEquations1D) + eos = equations.equation_of_state + V, _, T = cons2prim(u, equations) + e = energy_internal(V, T, eos) + return e +end + end # @muladd diff --git a/test/test_type.jl b/test/test_type.jl index 8a1141f7c15..54262ef40f3 100644 --- a/test/test_type.jl +++ b/test/test_type.jl @@ -212,6 +212,43 @@ isdir(outdir) && rm(outdir, recursive = true) end end + @timed_testset "NonIdeal Compressible Euler 1D" begin + for RealT in (Float32, Float64) + # set gamma = 2 for the coupling convergence test + equations = @inferred NonIdealCompressibleEulerEquations1D(IdealGas(RealT(2))) + + x = SVector(zero(RealT)) + t = zero(RealT) + u = u_ll = u_rr = u_inner = cons = SVector(one(RealT), one(RealT), one(RealT)) + orientation = 1 + direction = 1 + + surface_flux_function = flux_lax_friedrichs + + @test eltype(@inferred flux(u, orientation, equations)) == RealT + @test typeof(@inferred max_abs_speed_naive(u_ll, u_rr, orientation, equations)) == + RealT + @test eltype(@inferred min_max_speed_naive(u_ll, u_rr, orientation, equations)) == + RealT + @test eltype(@inferred min_max_speed_davis(u_ll, u_rr, orientation, equations)) == + RealT + + @test eltype(@inferred Trixi.max_abs_speeds(u, equations)) == RealT + @test eltype(@inferred cons2prim(u, equations)) == RealT + @test eltype(@inferred prim2cons(u, equations)) == RealT + @test eltype(@inferred cons2entropy(u, equations)) == RealT + # TODO: if entropy2cons is implemented, add a test + + @test typeof(@inferred density(u, equations)) == RealT + @test typeof(@inferred velocity(u, equations)) == RealT + @test typeof(@inferred velocity(u, orientation, equations)) == RealT + @test typeof(@inferred pressure(u, equations)) == RealT + @test typeof(@inferred density_pressure(u, equations)) == RealT + @test typeof(@inferred entropy(cons, equations)) == RealT + @test typeof(@inferred energy_internal(cons, equations)) == RealT + end + end + @timed_testset "Compressible Euler 2D" begin for RealT in (Float32, Float64) # set gamma = 2 for the coupling convergence test From d96cb204b9586c6ffc1d95a9fd5e2a50d31432e7 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Thu, 22 Jan 2026 13:28:09 -0600 Subject: [PATCH 052/360] adding unit tests for new functions --- test/test_unit.jl | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/test/test_unit.jl b/test/test_unit.jl index 4363aedda80..7eec6ff2189 100644 --- a/test/test_unit.jl +++ b/test/test_unit.jl @@ -769,7 +769,15 @@ end @timed_testset "Test consistency (fluxes, entropy/cons2entropy) for NonIdealCompressibleEulerEquations1D" begin eos = VanDerWaals(; a = 10, b = 0.01, R = 287, gamma = 1.4) equations = NonIdealCompressibleEulerEquations1D(eos) - u = prim2cons(SVector(2.0, 0.1, 10.0), equations) + q = SVector(2.0, 0.1, 10.0) + V, v1, T = q + u = prim2cons(q, equations) + + @test density(u, equations) ≈ 0.5 + @test velocity(u, equations) ≈ 0.1 + @test density_pressure(u, equations) ≈ u[1] * pressure(V, T, eos) + @test energy_internal(u, equations) ≈ energy_internal(V, T, eos) + @test ForwardDiff.gradient(u -> entropy(u, equations), u) ≈ cons2entropy(u, equations) @test flux_lax_friedrichs(u, u, 1, equations) ≈ flux(u, 1, equations) From 14da2a81ba6bc63a06a8a7cbb5809c1c2571bd03 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Thu, 22 Jan 2026 13:31:30 -0600 Subject: [PATCH 053/360] formatting --- src/equations/nonideal_compressible_euler_1d.jl | 1 - test/test_type.jl | 2 +- test/test_unit.jl | 6 +++--- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/equations/nonideal_compressible_euler_1d.jl b/src/equations/nonideal_compressible_euler_1d.jl index a341ff5b073..ee5cb060009 100644 --- a/src/equations/nonideal_compressible_euler_1d.jl +++ b/src/equations/nonideal_compressible_euler_1d.jl @@ -238,5 +238,4 @@ end e = energy_internal(V, T, eos) return e end - end # @muladd diff --git a/test/test_type.jl b/test/test_type.jl index 54262ef40f3..7f0d07a46ea 100644 --- a/test/test_type.jl +++ b/test/test_type.jl @@ -212,7 +212,7 @@ isdir(outdir) && rm(outdir, recursive = true) end end - @timed_testset "NonIdeal Compressible Euler 1D" begin + @timed_testset "NonIdeal Compressible Euler 1D" begin for RealT in (Float32, Float64) # set gamma = 2 for the coupling convergence test equations = @inferred NonIdealCompressibleEulerEquations1D(IdealGas(RealT(2))) diff --git a/test/test_unit.jl b/test/test_unit.jl index 7eec6ff2189..389015fabab 100644 --- a/test/test_unit.jl +++ b/test/test_unit.jl @@ -769,15 +769,15 @@ end @timed_testset "Test consistency (fluxes, entropy/cons2entropy) for NonIdealCompressibleEulerEquations1D" begin eos = VanDerWaals(; a = 10, b = 0.01, R = 287, gamma = 1.4) equations = NonIdealCompressibleEulerEquations1D(eos) - q = SVector(2.0, 0.1, 10.0) + q = SVector(2.0, 0.1, 10.0) V, v1, T = q u = prim2cons(q, equations) @test density(u, equations) ≈ 0.5 - @test velocity(u, equations) ≈ 0.1 + @test velocity(u, equations) ≈ 0.1 @test density_pressure(u, equations) ≈ u[1] * pressure(V, T, eos) @test energy_internal(u, equations) ≈ energy_internal(V, T, eos) - + @test ForwardDiff.gradient(u -> entropy(u, equations), u) ≈ cons2entropy(u, equations) @test flux_lax_friedrichs(u, u, 1, equations) ≈ flux(u, 1, equations) From d77b80ce323f798d22c69112a77e400e43c36de5 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Thu, 22 Jan 2026 14:03:10 -0600 Subject: [PATCH 054/360] testing vdw EOS as well format --- test/test_type.jl | 66 +++++++++++++++++++++++++++-------------------- 1 file changed, 38 insertions(+), 28 deletions(-) diff --git a/test/test_type.jl b/test/test_type.jl index 7f0d07a46ea..d79c98e4e63 100644 --- a/test/test_type.jl +++ b/test/test_type.jl @@ -214,38 +214,48 @@ isdir(outdir) && rm(outdir, recursive = true) @timed_testset "NonIdeal Compressible Euler 1D" begin for RealT in (Float32, Float64) - # set gamma = 2 for the coupling convergence test - equations = @inferred NonIdealCompressibleEulerEquations1D(IdealGas(RealT(2))) - - x = SVector(zero(RealT)) - t = zero(RealT) - u = u_ll = u_rr = u_inner = cons = SVector(one(RealT), one(RealT), one(RealT)) - orientation = 1 - direction = 1 + equations_ideal_gas = @inferred NonIdealCompressibleEulerEquations1D(IdealGas(RealT(2))) + a, b, gamma, R = RealT.((0.0, 0.0, 1.4, 287)) + equations_vdw = @inferred NonIdealCompressibleEulerEquations1D(VanDerWaals(; a, + b, + gamma, + R)) + + for equations in (equations_ideal_gas, equations_vdw) + x = SVector(zero(RealT)) + t = zero(RealT) + u = u_ll = u_rr = u_inner = cons = SVector(one(RealT), one(RealT), + one(RealT)) + orientation = 1 + direction = 1 - surface_flux_function = flux_lax_friedrichs + surface_flux_function = flux_lax_friedrichs - @test eltype(@inferred flux(u, orientation, equations)) == RealT - @test typeof(@inferred max_abs_speed_naive(u_ll, u_rr, orientation, equations)) == - RealT - @test eltype(@inferred min_max_speed_naive(u_ll, u_rr, orientation, equations)) == - RealT - @test eltype(@inferred min_max_speed_davis(u_ll, u_rr, orientation, equations)) == - RealT + @test eltype(@inferred flux(u, orientation, equations)) == RealT + @test typeof(@inferred max_abs_speed_naive(u_ll, u_rr, orientation, + equations)) == + RealT + @test eltype(@inferred min_max_speed_naive(u_ll, u_rr, orientation, + equations)) == + RealT + @test eltype(@inferred min_max_speed_davis(u_ll, u_rr, orientation, + equations)) == + RealT - @test eltype(@inferred Trixi.max_abs_speeds(u, equations)) == RealT - @test eltype(@inferred cons2prim(u, equations)) == RealT - @test eltype(@inferred prim2cons(u, equations)) == RealT - @test eltype(@inferred cons2entropy(u, equations)) == RealT - # TODO: if entropy2cons is implemented, add a test + @test eltype(@inferred Trixi.max_abs_speeds(u, equations)) == RealT + @test eltype(@inferred cons2prim(u, equations)) == RealT + @test eltype(@inferred prim2cons(u, equations)) == RealT + @test eltype(@inferred cons2entropy(u, equations)) == RealT + # TODO: if entropy2cons is implemented, add a test - @test typeof(@inferred density(u, equations)) == RealT - @test typeof(@inferred velocity(u, equations)) == RealT - @test typeof(@inferred velocity(u, orientation, equations)) == RealT - @test typeof(@inferred pressure(u, equations)) == RealT - @test typeof(@inferred density_pressure(u, equations)) == RealT - @test typeof(@inferred entropy(cons, equations)) == RealT - @test typeof(@inferred energy_internal(cons, equations)) == RealT + @test typeof(@inferred density(u, equations)) == RealT + @test typeof(@inferred velocity(u, equations)) == RealT + @test typeof(@inferred velocity(u, orientation, equations)) == RealT + @test typeof(@inferred pressure(u, equations)) == RealT + @test typeof(@inferred density_pressure(u, equations)) == RealT + @test typeof(@inferred entropy(cons, equations)) == RealT + @test typeof(@inferred energy_internal(cons, equations)) == RealT + end end end From acc72f87aaf72510561db8494e4e576596ad091f Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Thu, 22 Jan 2026 14:42:29 -0600 Subject: [PATCH 055/360] adding PR --- src/Trixi.jl | 2 +- .../equation_of_state_peng_robinson.jl | 145 ++++++++++++++++++ src/equations/equations_of_state.jl | 4 +- 3 files changed, 149 insertions(+), 2 deletions(-) create mode 100644 src/equations/equation_of_state_peng_robinson.jl diff --git a/src/Trixi.jl b/src/Trixi.jl index ba6f3451806..653c7ad7272 100644 --- a/src/Trixi.jl +++ b/src/Trixi.jl @@ -182,7 +182,7 @@ export AcousticPerturbationEquations2D, LinearElasticityEquations1D, PassiveTracerEquations -export NonIdealCompressibleEulerEquations1D, IdealGas, VanDerWaals +export NonIdealCompressibleEulerEquations1D, IdealGas, VanDerWaals, PengRobinson export LaplaceDiffusion1D, LaplaceDiffusion2D, LaplaceDiffusion3D, LaplaceDiffusionEntropyVariables1D, LaplaceDiffusionEntropyVariables2D, diff --git a/src/equations/equation_of_state_peng_robinson.jl b/src/equations/equation_of_state_peng_robinson.jl new file mode 100644 index 00000000000..d7356f3ace9 --- /dev/null +++ b/src/equations/equation_of_state_peng_robinson.jl @@ -0,0 +1,145 @@ +# By default, Julia/LLVM does not use fused multiply-add operations (FMAs). +# Since these FMAs can increase the performance of many numerical algorithms, +# we need to opt-in explicitly. +# See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. +@muladd begin +#! format: noindent + +@doc raw""" + PengRobinson{RealT <: Real} <: AbstractEquationOfState + +This defines the Peng-Robinson equation of state +given by the pressure and internal energy relations +```math +p = \frac{R T}{V - b} - \frac{a(T)}{V^2 + 2bV - b^2}, \quad e = c_{v,0} T + K(a(T) - Ta'(T)) +``` +where `V = inv(rho)` and auxiliary expressions for `a(T)` and `K` are given by +```math +a(T) = a_0\left(1 + \kappa \left 1 - \sqrt{\frac{T}{T_0}}\right)\right)^2, \quad +K = \frac{1}{b 2\sqrt{2}} \log\left( \frac{V + (1 - b \sqrt{2})}{V + (1 + b\sqrt{2})}\right). +``` +Moreover, `c_v = c_{v,0} - K T a''(T)`. + +All expressions used here are taken from "An entropy-stable hybrid scheme for simulations +of transcritical real-fluid flows" by Ma, Lv, Ihme (2017). + + +See also "Towards a fully well-balanced and entropy-stable scheme for the Euler equations with +gravity: preserving isentropic steady solutions" by Berthon, Michel-Dansac, and Thomann (2024). + + +""" +struct PengRobinson{RealT <: Real} <: AbstractEquationOfState + R::RealT + a0::RealT + b::RealT + cv0::RealT + kappa::RealT + T0::RealT + inv2sqrt2b::RealT + one_minus_sqrt2_b::RealT + one_plus_sqrt2_b::RealT + function PengRobinson(R, a0, b, cv0, kappa, T0) + inv2sqrt2b = inv(2 * sqrt(2) * b) + one_minus_sqrt2_b = (1 - sqrt(2)) * b + one_plus_sqrt2_b = (1 + sqrt(2)) * b + return new{typeof(R)}(R, a0, b, cv0, kappa, T0, + inv2sqrt2b, one_minus_sqrt2_b, one_plus_sqrt2_b) + end +end + +""" + PengRobinson(; RealT = Float64) + +By default, the Peng-Robinson parameters are in mass basis for N2. +""" +function PengRobinson(; RealT = Float64) + Rgas = 8.31446261815324 + molar_mass_N2 = .02801 * 1000 # kg/m3 + R = Rgas * 1000 / molar_mass_N2 + pc = 3.40e6 + Tc = 126.2 + omega = 0.0372 + cv0 = 743.2 + b = .077796 * R * Tc / pc + a0 = 0.457236 * (R * Tc)^2 / pc + kappa = 0.37464 + 1.54226 * omega - 0.26992 * omega^2 + return PengRobinson(RealT.((R, a0, b, cv0, kappa, Tc))...) +end + +""" + pressure(V, T, eos::PengRobinson) + +Computes pressure for a Peng-Robinson gas from specific volume `V` and temperature `T`, +see also [`NonIdealCompressibleEulerEquations1D`](@ref). +""" +function pressure(V, T, eos::PengRobinson) + (; R, b) = eos + p = R * T / (V - b) - a(T, eos) / (V^2 + 2 * b * V - b^2) + return p +end + +""" + energy_internal(V, T, eos::PengRobinson) + +Computes internal energy for a Peng-Robinson gas from specific volume `V` and temperature `T` as +``e = c_{v,0} T + K_1 (a(T) - T a'(T))``. +""" +function energy_internal(V, T, eos::PengRobinson) + (; cv0) = eos + K1 = calc_K1(V, eos) + e = cv0 * T + K1 * (a(T, eos) - T * da(T, eos)) + return e +end + +@inline function heat_capacity_constant_volume(V, T, eos::PengRobinson) + (; cv0) = eos + K1 = calc_K1(V, eos) + cv = cv0 - K1 * T * d2a(T, eos) + return cv +end + +function entropy_specific(V, T, eos::PengRobinson) + (; cv0, R, b) = eos + + # The specific entropy is defined up to some reference value s0, which is + # arbitrarily set to zero here. + K1 = calc_K1(V, eos) + return cv0 * log(T) + R * log(V - b)- da(T, eos) * K1 +end + +function speed_of_sound(V, T, eos::PengRobinson) + (; cv0, R, b) = eos + + dpdT_V, dpdV_T = calc_pressure_derivatives(V, T, eos) + + # calculate ratio of specific heats + K1 = calc_K1(V, eos) + d2aT = d2a(T, eos) + cp0 = cv0 + R + cv = cv0 - K1 * T * d2aT + cp = cp0 - R - K1 * T * d2aT - T * dpdT_V^2 / dpdV_T + gamma = cp / cv + + # calculate bulk modulus, which should be positive + # for admissible thermodynamic states. + kappa_T = -inv(V * dpdV_T) + c2 = gamma * V / kappa_T + return sqrt(c2) +end + +# The following are auxiliary functions used in calculating the PR EOS +@inline function a(T, eos::PengRobinson) + (; a0, kappa, T0) = eos + return a0 * (1 + kappa * (1 - sqrt(T / T0)))^2 +end +@inline da(T, eos) = ForwardDiff.derivative(T -> a(T, eos), T) +@inline d2a(T, eos) = ForwardDiff.derivative(T -> da(T, eos), T) + +@inline function calc_K1(V, eos::PengRobinson) + (; inv2sqrt2b, one_minus_sqrt2_b, one_plus_sqrt2_b) = eos + K1 = inv2sqrt2b * log((V + one_minus_sqrt2_b) / (V + one_plus_sqrt2_b)) + return K1 +end + +end # @muladd diff --git a/src/equations/equations_of_state.jl b/src/equations/equations_of_state.jl index 9bef26fc87c..bae684ff92d 100644 --- a/src/equations/equations_of_state.jl +++ b/src/equations/equations_of_state.jl @@ -26,6 +26,7 @@ abstract type AbstractEquationOfState end include("equation_of_state_ideal_gas.jl") include("equation_of_state_vdw.jl") +include("equation_of_state_peng_robinson.jl") ####################################################### # @@ -69,7 +70,8 @@ function temperature(V, e, eos::AbstractEquationOfState; initial_T = 1.0, # guarantee convergence of this iteration. de_dT_V = heat_capacity_constant_volume(V, T, eos) - T = T - de / de_dT_V + # guard against negative temperatures + T = max(tol, T - de / de_dT_V) iter += 1 end if iter == maxiter From a3c49336f1b1661227c73dac294a0f9afb0a9285 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Thu, 22 Jan 2026 14:44:21 -0600 Subject: [PATCH 056/360] comments --- src/equations/equations_of_state.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/src/equations/equations_of_state.jl b/src/equations/equations_of_state.jl index efe8f2fea48..9becbc33946 100644 --- a/src/equations/equations_of_state.jl +++ b/src/equations/equations_of_state.jl @@ -47,6 +47,7 @@ end return ForwardDiff.derivative(T -> energy_internal(V, T, eos), T) end +# this is used in [`flux_terashima_etal`](@ref) and [`flux_terashima_etal_central`](@ref) function calc_pressure_derivatives(V, T, eos) dpdV_T = ForwardDiff.derivative(V -> pressure(V, T, eos), V) dpdT_V = ForwardDiff.derivative(T -> pressure(V, T, eos), T) From 9be715e206aedfaf603ddfdfaa436211613c9535 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Thu, 22 Jan 2026 14:44:55 -0600 Subject: [PATCH 057/360] specialize calc_pressure_derivatives --- src/equations/equation_of_state_vdw.jl | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/equations/equation_of_state_vdw.jl b/src/equations/equation_of_state_vdw.jl index 15cb75e978f..7a18693ca4e 100644 --- a/src/equations/equation_of_state_vdw.jl +++ b/src/equations/equation_of_state_vdw.jl @@ -110,4 +110,13 @@ function calc_pressure_derivatives(V, T, eos::VanDerWaals) 2 * a * rho return dpdT_V, -dpdrho_T / V^2 end + +function calc_pressure_derivatives(V, T, eos::VanDerWaals) + (; a, b, R) = model + rho = inv(V) + dpdT_V = rho * R / (1 - rho * b) + dpdrho_T = (R * T) / (1 - b * rho) + (R * T * b * rho) / ((1 - b * rho)^2) - 2 * a * rho + return dpdT_V, -dpdrho_T / V^2 +end + end # @muladd From fc2ff43a54303f83e350d6d05567a1efdc7a0ffc Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Thu, 22 Jan 2026 14:46:12 -0600 Subject: [PATCH 058/360] specialize calc_pressure_derivatives --- src/equations/equation_of_state_peng_robinson.jl | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/equations/equation_of_state_peng_robinson.jl b/src/equations/equation_of_state_peng_robinson.jl index d7356f3ace9..02df31dff1e 100644 --- a/src/equations/equation_of_state_peng_robinson.jl +++ b/src/equations/equation_of_state_peng_robinson.jl @@ -128,6 +128,15 @@ function speed_of_sound(V, T, eos::PengRobinson) return sqrt(c2) end +function calc_pressure_derivatives(V, T, eos::PengRobinson) + (; R, b) = eos + denom = (V^2 + 2 * b * V - b^2) + RdivVb = R / (V - b) + dpdT_V = RdivVb - da(T, eos) / denom + dpdV_T = -RdivVb * T / (V - b) * (1 - 2 * a(T, eos) / (R * T * (V + b) * (denom / (V^2 - b^2))^2)) + return dpdT_V, dpdV_T +end + # The following are auxiliary functions used in calculating the PR EOS @inline function a(T, eos::PengRobinson) (; a0, kappa, T0) = eos From f11f26d5452f7de4f9c8afb9230e3783e206c71b Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Thu, 22 Jan 2026 14:57:11 -0600 Subject: [PATCH 059/360] formatting --- .../equation_of_state_peng_robinson.jl | 20 +++++++++---------- src/equations/equation_of_state_vdw.jl | 4 ++-- src/equations/equations_of_state.jl | 2 +- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/equations/equation_of_state_peng_robinson.jl b/src/equations/equation_of_state_peng_robinson.jl index 02df31dff1e..a90de0aef8a 100644 --- a/src/equations/equation_of_state_peng_robinson.jl +++ b/src/equations/equation_of_state_peng_robinson.jl @@ -43,7 +43,7 @@ struct PengRobinson{RealT <: Real} <: AbstractEquationOfState inv2sqrt2b = inv(2 * sqrt(2) * b) one_minus_sqrt2_b = (1 - sqrt(2)) * b one_plus_sqrt2_b = (1 + sqrt(2)) * b - return new{typeof(R)}(R, a0, b, cv0, kappa, T0, + return new{typeof(R)}(R, a0, b, cv0, kappa, T0, inv2sqrt2b, one_minus_sqrt2_b, one_plus_sqrt2_b) end end @@ -55,13 +55,13 @@ By default, the Peng-Robinson parameters are in mass basis for N2. """ function PengRobinson(; RealT = Float64) Rgas = 8.31446261815324 - molar_mass_N2 = .02801 * 1000 # kg/m3 + molar_mass_N2 = 0.02801 * 1000 # kg/m3 R = Rgas * 1000 / molar_mass_N2 pc = 3.40e6 - Tc = 126.2 + Tc = 126.2 omega = 0.0372 cv0 = 743.2 - b = .077796 * R * Tc / pc + b = 0.077796 * R * Tc / pc a0 = 0.457236 * (R * Tc)^2 / pc kappa = 0.37464 + 1.54226 * omega - 0.26992 * omega^2 return PengRobinson(RealT.((R, a0, b, cv0, kappa, Tc))...) @@ -75,7 +75,7 @@ see also [`NonIdealCompressibleEulerEquations1D`](@ref). """ function pressure(V, T, eos::PengRobinson) (; R, b) = eos - p = R * T / (V - b) - a(T, eos) / (V^2 + 2 * b * V - b^2) + p = R * T / (V - b) - a(T, eos) / (V^2 + 2 * b * V - b^2) return p end @@ -105,12 +105,12 @@ function entropy_specific(V, T, eos::PengRobinson) # The specific entropy is defined up to some reference value s0, which is # arbitrarily set to zero here. K1 = calc_K1(V, eos) - return cv0 * log(T) + R * log(V - b)- da(T, eos) * K1 + return cv0 * log(T) + R * log(V - b) - da(T, eos) * K1 end function speed_of_sound(V, T, eos::PengRobinson) (; cv0, R, b) = eos - + dpdT_V, dpdV_T = calc_pressure_derivatives(V, T, eos) # calculate ratio of specific heats @@ -133,7 +133,8 @@ function calc_pressure_derivatives(V, T, eos::PengRobinson) denom = (V^2 + 2 * b * V - b^2) RdivVb = R / (V - b) dpdT_V = RdivVb - da(T, eos) / denom - dpdV_T = -RdivVb * T / (V - b) * (1 - 2 * a(T, eos) / (R * T * (V + b) * (denom / (V^2 - b^2))^2)) + dpdV_T = -RdivVb * T / (V - b) * + (1 - 2 * a(T, eos) / (R * T * (V + b) * (denom / (V^2 - b^2))^2)) return dpdT_V, dpdV_T end @@ -145,10 +146,9 @@ end @inline da(T, eos) = ForwardDiff.derivative(T -> a(T, eos), T) @inline d2a(T, eos) = ForwardDiff.derivative(T -> da(T, eos), T) -@inline function calc_K1(V, eos::PengRobinson) +@inline function calc_K1(V, eos::PengRobinson) (; inv2sqrt2b, one_minus_sqrt2_b, one_plus_sqrt2_b) = eos K1 = inv2sqrt2b * log((V + one_minus_sqrt2_b) / (V + one_plus_sqrt2_b)) return K1 end - end # @muladd diff --git a/src/equations/equation_of_state_vdw.jl b/src/equations/equation_of_state_vdw.jl index 7a18693ca4e..7e5126c6984 100644 --- a/src/equations/equation_of_state_vdw.jl +++ b/src/equations/equation_of_state_vdw.jl @@ -115,8 +115,8 @@ function calc_pressure_derivatives(V, T, eos::VanDerWaals) (; a, b, R) = model rho = inv(V) dpdT_V = rho * R / (1 - rho * b) - dpdrho_T = (R * T) / (1 - b * rho) + (R * T * b * rho) / ((1 - b * rho)^2) - 2 * a * rho + dpdrho_T = (R * T) / (1 - b * rho) + (R * T * b * rho) / ((1 - b * rho)^2) - + 2 * a * rho return dpdT_V, -dpdrho_T / V^2 end - end # @muladd diff --git a/src/equations/equations_of_state.jl b/src/equations/equations_of_state.jl index 9becbc33946..2610b508598 100644 --- a/src/equations/equations_of_state.jl +++ b/src/equations/equations_of_state.jl @@ -78,7 +78,7 @@ function temperature(V, e, eos::AbstractEquationOfState; initial_T = 1.0, de_dT_V = heat_capacity_constant_volume(V, T, eos) # guard against negative temperatures - T = max(tol, T - de / de_dT_V) + T = max(tol, T - de / de_dT_V) iter += 1 end if iter == maxiter From 2e46ec2b349ea9a310a65b41599e2506df79a616 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Thu, 22 Jan 2026 14:57:23 -0600 Subject: [PATCH 060/360] formatting --- src/equations/equation_of_state_vdw.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/equations/equation_of_state_vdw.jl b/src/equations/equation_of_state_vdw.jl index 7a18693ca4e..7e5126c6984 100644 --- a/src/equations/equation_of_state_vdw.jl +++ b/src/equations/equation_of_state_vdw.jl @@ -115,8 +115,8 @@ function calc_pressure_derivatives(V, T, eos::VanDerWaals) (; a, b, R) = model rho = inv(V) dpdT_V = rho * R / (1 - rho * b) - dpdrho_T = (R * T) / (1 - b * rho) + (R * T * b * rho) / ((1 - b * rho)^2) - 2 * a * rho + dpdrho_T = (R * T) / (1 - b * rho) + (R * T * b * rho) / ((1 - b * rho)^2) - + 2 * a * rho return dpdT_V, -dpdrho_T / V^2 end - end # @muladd From c74415ebb92bafe4e1f6722f13d7d12a5b658da9 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Thu, 22 Jan 2026 15:04:54 -0600 Subject: [PATCH 061/360] remove duplciate function --- src/equations/equation_of_state_vdw.jl | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/equations/equation_of_state_vdw.jl b/src/equations/equation_of_state_vdw.jl index 7e5126c6984..96590bf6084 100644 --- a/src/equations/equation_of_state_vdw.jl +++ b/src/equations/equation_of_state_vdw.jl @@ -111,12 +111,4 @@ function calc_pressure_derivatives(V, T, eos::VanDerWaals) return dpdT_V, -dpdrho_T / V^2 end -function calc_pressure_derivatives(V, T, eos::VanDerWaals) - (; a, b, R) = model - rho = inv(V) - dpdT_V = rho * R / (1 - rho * b) - dpdrho_T = (R * T) / (1 - b * rho) + (R * T * b * rho) / ((1 - b * rho)^2) - - 2 * a * rho - return dpdT_V, -dpdrho_T / V^2 -end end # @muladd From dda979e68e74bdae3f97c1d5ef5297009d042bac Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Thu, 22 Jan 2026 15:05:04 -0600 Subject: [PATCH 062/360] format --- src/equations/equation_of_state_vdw.jl | 1 - 1 file changed, 1 deletion(-) diff --git a/src/equations/equation_of_state_vdw.jl b/src/equations/equation_of_state_vdw.jl index 96590bf6084..15cb75e978f 100644 --- a/src/equations/equation_of_state_vdw.jl +++ b/src/equations/equation_of_state_vdw.jl @@ -110,5 +110,4 @@ function calc_pressure_derivatives(V, T, eos::VanDerWaals) 2 * a * rho return dpdT_V, -dpdrho_T / V^2 end - end # @muladd From 4e7472850ca6642a0046c0fca4db9462fce9cf53 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Thu, 22 Jan 2026 15:09:17 -0600 Subject: [PATCH 063/360] fix spelling --- src/equations/equations_of_state.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/equations/equations_of_state.jl b/src/equations/equations_of_state.jl index 556b301bc92..80cb45c4f46 100644 --- a/src/equations/equations_of_state.jl +++ b/src/equations/equations_of_state.jl @@ -29,7 +29,7 @@ include("equation_of_state_vdw.jl") ####################################################### # -# Some EOS-agnostic routines are provided below +# Some EOS-agnostic routines are provide_d below # ####################################################### @@ -99,8 +99,8 @@ end dpdT_V, dpdV_T = calc_pressure_derivatives(V, T, eos) dpdrho_T = dpdV_T * (-V / rho) # V = inv(rho), so dVdrho = -1/rho^2 = -V^2. - dedV_T = T * dpdT_V - pressure(V, T, eos) - drho_e_drho_T = e + rho * dedV_T * (-V / rho) # d(rho_e)/drho_|T = e + rho * dedV|T * dVdrho + de_dV_T = T * dpdT_V - pressure(V, T, eos) + drho_e_drho_T = e + rho * de_dV_T * (-V / rho) # d(rho_e)/drho_|T = e + rho * de_dV|T * dVdrho c_v = heat_capacity_constant_volume(V, T, eos) return ((-rho * c_v) / (dpdT_V) * dpdrho_T + drho_e_drho_T) From 71c7bd8a631c99a61d0fc7a3bdf67c3f8bc5cb18 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Thu, 22 Jan 2026 15:13:28 -0600 Subject: [PATCH 064/360] add more tests --- test/test_tree_1d_euler.jl | 44 ++++++++++++++++++++++++++++++++++++++ test/test_unit.jl | 2 ++ 2 files changed, 46 insertions(+) diff --git a/test/test_tree_1d_euler.jl b/test/test_tree_1d_euler.jl index 5f8b05b22eb..3af0c38b64b 100644 --- a/test/test_tree_1d_euler.jl +++ b/test/test_tree_1d_euler.jl @@ -576,4 +576,48 @@ end end end +@trixi_testset "elixir_euler_nonideal_density_wave.jl with flux_terashima_etal" begin + @test_trixi_include(joinpath(EXAMPLES_DIR, + "elixir_euler_nonideal_density_wave.jl"), + solver=DGSEM(polydeg = 3, + volume_integral = VolumeIntegralFluxDifferencing(flux_terashima_etal), + surface_flux = flux_lax_friedrichs), tspan=(0.0, 0.1), + l2=[ + 0.00142950049295259, + 0.00015714474261267127, + 0.06074209773082164 + ], + linf=[ + 0.0023392515519630314, + 0.0003073704254786813, + 0.1641094398916465 + ]) + + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + @test_allocations(Trixi.rhs!, semi, sol, 1000) +end + +@trixi_testset "elixir_euler_nonideal_density_wave.jl with flux_terashima_etal_central" begin + @test_trixi_include(joinpath(EXAMPLES_DIR, + "elixir_euler_nonideal_density_wave.jl"), + solver=DGSEM(polydeg = 3, + volume_integral = VolumeIntegralFluxDifferencing(flux_central_terashima_etal), + surface_flux = flux_lax_friedrichs), tspan=(0.0, 0.1), + l2=[ + 0.0014281751347953525, + 0.0001541528829242385, + 0.06071534187096569 + ], + linf=[ + 0.0023366029974749604, + 0.00029085539996634435, + 0.16384597075523288 + ]) + + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + @test_allocations(Trixi.rhs!, semi, sol, 1000) +end + end # module diff --git a/test/test_unit.jl b/test/test_unit.jl index 389015fabab..652f6fff0ca 100644 --- a/test/test_unit.jl +++ b/test/test_unit.jl @@ -782,6 +782,8 @@ end cons2entropy(u, equations) @test flux_lax_friedrichs(u, u, 1, equations) ≈ flux(u, 1, equations) @test flux_hll(u, u, 1, equations) ≈ flux(u, 1, equations) + @test flux_terashima_etal(u, u, 1, equations) ≈ flux(u, 1, equations) + @test flux_central_terashima_etal(u, u, 1, equations) ≈ flux(u, 1, equations) # check that the fallback temperature and specialized temperature # return the same value From c1005a0562cb67f2b5fbc3c6fcf2d1bcaca93926 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Thu, 22 Jan 2026 15:26:09 -0600 Subject: [PATCH 065/360] add NEWS.md entry --- NEWS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/NEWS.md b/NEWS.md index a9204ea121e..29633c2c191 100644 --- a/NEWS.md +++ b/NEWS.md @@ -9,6 +9,7 @@ for human readability. #### Added - Added `NonIdealCompressibleEulerEquations1D`, which allows users to specify a non-ideal equation of state. Currently `IdealGas` and `VanDerWaals` are supported ([#2739]). +- Added the APEC fluxes of `flux_terashima_etal` and `flux_central_terashima_etal` from [Terashima, Ly, Ihme (2025)](https://doi.org/10.1016/j.jcp.2024.113701). #### Changed From 3738950adff2ad6c358951abb9d4a4f94f6ce804 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Thu, 22 Jan 2026 21:50:35 -0600 Subject: [PATCH 066/360] refactoring to avoid CI failure --- src/equations/equations_of_state.jl | 9 +-------- src/equations/nonideal_compressible_euler_1d.jl | 8 ++++++++ 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/equations/equations_of_state.jl b/src/equations/equations_of_state.jl index 5efd09b94c8..7e651d104df 100644 --- a/src/equations/equations_of_state.jl +++ b/src/equations/equations_of_state.jl @@ -85,15 +85,8 @@ function temperature(V, e, eos::AbstractEquationOfState; initial_T = 1.0, return T end -@inline function internal_energy_density(u, - equations::NonIdealCompressibleEulerEquations1D) - rho, rho_v1, rho_e_total = u - rho_e = rho_e_total - 0.5f0 * rho_v1^2 / rho - return rho_e -end - # helper function used in [`flux_terashima_etal`](@ref) and [`flux_terashima_etal_central`](@ref) -@inline function drho_e_drho_at_const_p(V, T, eos) +@inline function drho_e_drho_at_const_p(V, T, ::AbstractEquationOfState) rho = inv(V) e = energy_internal(V, T, eos) diff --git a/src/equations/nonideal_compressible_euler_1d.jl b/src/equations/nonideal_compressible_euler_1d.jl index c3042d5ade2..bb7be9598d4 100644 --- a/src/equations/nonideal_compressible_euler_1d.jl +++ b/src/equations/nonideal_compressible_euler_1d.jl @@ -331,4 +331,12 @@ end e = energy_internal(V, T, eos) return e end + +@inline function internal_energy_density(u, + equations::NonIdealCompressibleEulerEquations1D) + rho, rho_v1, rho_e_total = u + rho_e = rho_e_total - 0.5f0 * rho_v1^2 / rho + return rho_e +end + end # @muladd From 04a1be6f05bbde8b75920e11ca2597c1f9077f84 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Thu, 22 Jan 2026 22:18:19 -0600 Subject: [PATCH 067/360] add dropped "eos" --- src/equations/equations_of_state.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/equations/equations_of_state.jl b/src/equations/equations_of_state.jl index 7e651d104df..3d03ada3174 100644 --- a/src/equations/equations_of_state.jl +++ b/src/equations/equations_of_state.jl @@ -86,7 +86,7 @@ function temperature(V, e, eos::AbstractEquationOfState; initial_T = 1.0, end # helper function used in [`flux_terashima_etal`](@ref) and [`flux_terashima_etal_central`](@ref) -@inline function drho_e_drho_at_const_p(V, T, ::AbstractEquationOfState) +@inline function drho_e_drho_at_const_p(V, T, eos::AbstractEquationOfState) rho = inv(V) e = energy_internal(V, T, eos) From f8389fe017663d6b8fa46252b066a965e83e1ad6 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Thu, 22 Jan 2026 22:18:43 -0600 Subject: [PATCH 068/360] format --- src/equations/nonideal_compressible_euler_1d.jl | 1 - test/test_tree_1d_euler.jl | 1 - 2 files changed, 2 deletions(-) diff --git a/src/equations/nonideal_compressible_euler_1d.jl b/src/equations/nonideal_compressible_euler_1d.jl index bb7be9598d4..80643ac1997 100644 --- a/src/equations/nonideal_compressible_euler_1d.jl +++ b/src/equations/nonideal_compressible_euler_1d.jl @@ -338,5 +338,4 @@ end rho_e = rho_e_total - 0.5f0 * rho_v1^2 / rho return rho_e end - end # @muladd diff --git a/test/test_tree_1d_euler.jl b/test/test_tree_1d_euler.jl index ead5a153db6..3af0c38b64b 100644 --- a/test/test_tree_1d_euler.jl +++ b/test/test_tree_1d_euler.jl @@ -618,7 +618,6 @@ end # Ensure that we do not have excessive memory allocations # (e.g., from type instabilities) @test_allocations(Trixi.rhs!, semi, sol, 1000) - end end # module From 85370b36a9e5e26cc1beb7f3f26916ad6803c618 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Fri, 23 Jan 2026 13:50:15 -0600 Subject: [PATCH 069/360] add another test --- test/test_unit.jl | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/test/test_unit.jl b/test/test_unit.jl index d9021a05c89..9f50c03c2c9 100644 --- a/test/test_unit.jl +++ b/test/test_unit.jl @@ -792,6 +792,14 @@ end e = energy_internal(V, T, eos) @test temperature(V, e, eos) ≈ invoke(temperature, Tuple{Any, Any, Trixi.AbstractEquationOfState}, V, e, eos) + + # check that fallback calc_pressure_derivatives matches specialized routines + @test calc_pressure_derivatives(V, T, eos)[1] ≈ + invoke(calc_pressure_derivatives, + Tuple{Any, Any, Trixi.AbstractEquationOfState}, V, T, eos)[1] + @test calc_pressure_derivatives(V, T, eos)[2] ≈ + invoke(calc_pressure_derivatives, + Tuple{Any, Any, Trixi.AbstractEquationOfState}, V, T, eos)[2] end @timed_testset "StepsizeCallback" begin From 56edbc24e90a0dbf002aa66592aac929630869e4 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Fri, 23 Jan 2026 14:31:19 -0600 Subject: [PATCH 070/360] adjust default tolerance, maxiter, initial guess --- src/equations/equation_of_state_peng_robinson.jl | 5 +++++ src/equations/equations_of_state.jl | 10 +++++++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/equations/equation_of_state_peng_robinson.jl b/src/equations/equation_of_state_peng_robinson.jl index a90de0aef8a..7f4376de42e 100644 --- a/src/equations/equation_of_state_peng_robinson.jl +++ b/src/equations/equation_of_state_peng_robinson.jl @@ -67,6 +67,11 @@ function PengRobinson(; RealT = Float64) return PengRobinson(RealT.((R, a0, b, cv0, kappa, Tc))...) end +# the default tolerance of 10 * eps() does not converge for most Peng-Robinson examples, +# so we choose a looser tolerance here. Researchers at the US Naval Research Lab noted +# that they typically just use 8 fixed Newton iterations for Peng-Robinson. +eos_newton_tol(eos::PengRobinson) = 1e-8 + """ pressure(V, T, eos::PengRobinson) diff --git a/src/equations/equations_of_state.jl b/src/equations/equations_of_state.jl index bbdb22e3f98..c653c8dfc4c 100644 --- a/src/equations/equations_of_state.jl +++ b/src/equations/equations_of_state.jl @@ -54,8 +54,11 @@ function calc_pressure_derivatives(V, T, eos) return dpdT_V, dpdV_T end -# relative tolerance for the Newton solver for temperature +# relative tolerance, initial guess, and maximum number of iterations +# for the Newton solver for temperature. eos_newton_tol(eos::AbstractEquationOfState) = 10 * eps() +eos_initial_temperature(V, e, eos::AbstractEquationOfState) = 1 +eos_newton_maxiter(eos) = 100 """ temperature(V, e, eos::AbstractEquationOfState; initial_T = 1.0, tol = eos_newton_tol(eos), @@ -65,8 +68,9 @@ Calculates the temperature as a function of specific volume `V` and internal ene by using Newton's method to determine `T` such that `energy_internal(V, T, eos) = e`. Note that the tolerance may need to be adjusted based on the specific equation of state. """ -function temperature(V, e, eos::AbstractEquationOfState; initial_T = 1.0, - tol = eos_newton_tol(eos), maxiter = 100) +function temperature(V, e, eos::AbstractEquationOfState; + initial_T = eos_initial_temperature(V, e, eos), + tol = eos_newton_tol(eos), maxiter = eos_newton_maxiter(eos)) T = initial_T de = energy_internal(V, T, eos) - e iter = 1 From f8b64d5aa5af2e6a1bb2525d6a06142106f2e940 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Fri, 23 Jan 2026 14:31:37 -0600 Subject: [PATCH 071/360] add plotting convenience function --- src/equations/nonideal_compressible_euler_1d.jl | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/equations/nonideal_compressible_euler_1d.jl b/src/equations/nonideal_compressible_euler_1d.jl index 80643ac1997..d584d386873 100644 --- a/src/equations/nonideal_compressible_euler_1d.jl +++ b/src/equations/nonideal_compressible_euler_1d.jl @@ -51,6 +51,18 @@ function varnames(::typeof(cons2cons), ::NonIdealCompressibleEulerEquations1D) end varnames(::typeof(cons2prim), ::NonIdealCompressibleEulerEquations1D) = ("V", "v1", "T") +# for plotting with PlotData1D(sol, solution_variables=density_velocity_pressure) +@inline function density_velocity_pressure(u, + equations::NonIdealCompressibleEulerEquations1D) + eos = equations.equation_of_state + rho, rho_v1, rho_e_total = u + V, v1, T = cons2prim(u, equations) + return SVector(rho, v1, pressure(V, T, eos)) +end +varnames(::typeof(density_velocity_pressure), ::NonIdealCompressibleEulerEquations1D) = ("rho", + "v1", + "p") + # Calculate 1D flux for a single point @inline function flux(u, orientation::Integer, equations::NonIdealCompressibleEulerEquations1D) From f65d1d6f6d33b6eb640244cce838220487bfacc4 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Fri, 23 Jan 2026 14:31:54 -0600 Subject: [PATCH 072/360] add transcritical density wave example --- .../elixir_euler_transcritical_wave.jl | 88 +++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 examples/tree_1d_dgsem/elixir_euler_transcritical_wave.jl diff --git a/examples/tree_1d_dgsem/elixir_euler_transcritical_wave.jl b/examples/tree_1d_dgsem/elixir_euler_transcritical_wave.jl new file mode 100644 index 00000000000..8d74e8fde89 --- /dev/null +++ b/examples/tree_1d_dgsem/elixir_euler_transcritical_wave.jl @@ -0,0 +1,88 @@ +using OrdinaryDiffEqLowStorageRK +using Trixi +using Trixi: ForwardDiff + +############################################################################### +# semidiscretization of the compressible Euler equations + +eos = PengRobinson() +equations = NonIdealCompressibleEulerEquations1D(eos) + +# the smooth transcritical wave taken from "An entropy-stable hybrid scheme for simulations of +# transcritical real-fluid flows" by Ma, Ihme (2017) +# +function initial_condition_transcritical_wave(x, t, + equations::NonIdealCompressibleEulerEquations1D{<:PengRobinson}) + RealT = eltype(x) + + rho_min, rho_max = 56.9, 793.1 + rho = 0.5 * (rho_min + rho_max) + 0.5 * (rho_max - rho_min) * sin(2 * pi * x[1]) + v1 = 100 + p = 5e6 + + V = inv(rho) + + # invert for temperature given p, V + T = eos.T0 + tol = 100 * eps(RealT) + dp = pressure(V, T, eos) - p + iter = 1 + while abs(dp) / abs(p) > tol && iter < 100 + dp = pressure(V, T, eos) - p + dpdT_V = ForwardDiff.derivative(T -> pressure(V, T, eos), T) + T = max(tol, T - dp / dpdT_V) + iter += 1 + end + if iter == 100 + println("Warning: solver for temperature(V, p) did not converge") + end + + return prim2cons(SVector(V, v1, T), equations) +end + +initial_condition = initial_condition_transcritical_wave + +volume_flux = flux_terashima_etal +volume_integral = VolumeIntegralFluxDifferencing(volume_flux) +solver = DGSEM(polydeg = 3, volume_integral = volume_integral, + surface_flux = flux_lax_friedrichs) + +coordinates_min = -0.5 +coordinates_max = 0.5 +mesh = TreeMesh(coordinates_min, coordinates_max, + initial_refinement_level = 6, + n_cells_max = 30_000) + +semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) + +############################################################################### +# ODE solvers, callbacks etc. + +tspan = (0.0, 0.01) +ode = semidiscretize(semi, tspan) + +summary_callback = SummaryCallback() + +analysis_interval = 2000 +analysis_callback = AnalysisCallback(semi, interval = analysis_interval) + +alive_callback = AliveCallback(analysis_interval = analysis_interval) + +save_solution = SaveSolutionCallback(interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim) + +stepsize_callback = StepsizeCallback(cfl = 0.5) + +callbacks = CallbackSet(summary_callback, + analysis_callback, alive_callback, + save_solution, + stepsize_callback) + +############################################################################### +# run the simulation + +sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false); + dt = stepsize_callback(ode), # solve needs some value here but it will be overwritten by the stepsize_callback + ode_default_options()..., callback = callbacks); From 3853dd9101bdf14a93bfe4606e845229a4fa1d17 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Fri, 23 Jan 2026 14:36:10 -0600 Subject: [PATCH 073/360] rename elixir and add test --- ...ixir_euler_nonideal_transcritical_wave.jl} | 3 ++- test/test_tree_1d_euler.jl | 20 +++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) rename examples/tree_1d_dgsem/{elixir_euler_transcritical_wave.jl => elixir_euler_nonideal_transcritical_wave.jl} (96%) diff --git a/examples/tree_1d_dgsem/elixir_euler_transcritical_wave.jl b/examples/tree_1d_dgsem/elixir_euler_nonideal_transcritical_wave.jl similarity index 96% rename from examples/tree_1d_dgsem/elixir_euler_transcritical_wave.jl rename to examples/tree_1d_dgsem/elixir_euler_nonideal_transcritical_wave.jl index 8d74e8fde89..2bfa01dc9dc 100644 --- a/examples/tree_1d_dgsem/elixir_euler_transcritical_wave.jl +++ b/examples/tree_1d_dgsem/elixir_euler_nonideal_transcritical_wave.jl @@ -16,8 +16,9 @@ function initial_condition_transcritical_wave(x, t, RealT = eltype(x) rho_min, rho_max = 56.9, 793.1 - rho = 0.5 * (rho_min + rho_max) + 0.5 * (rho_max - rho_min) * sin(2 * pi * x[1]) v1 = 100 + rho = 0.5 * (rho_min + rho_max) + + 0.5 * (rho_max - rho_min) * sin(2 * pi * (x[1] - v1 * t)) p = 5e6 V = inv(rho) diff --git a/test/test_tree_1d_euler.jl b/test/test_tree_1d_euler.jl index 3af0c38b64b..d5b87924d23 100644 --- a/test/test_tree_1d_euler.jl +++ b/test/test_tree_1d_euler.jl @@ -620,4 +620,24 @@ end @test_allocations(Trixi.rhs!, semi, sol, 1000) end +@trixi_testset "elixir_euler_nonideal_transcritical_wave.jl with Peng Robinson" begin + @test_trixi_include(joinpath(EXAMPLES_DIR, + "elixir_euler_nonideal_transcritical_wave.jl"), + solver=DGSEM(polydeg = 3, + volume_integral = VolumeIntegralFluxDifferencing(flux_terashima_etal), + surface_flux = flux_lax_friedrichs), + tspan=(0.0, 0.001), + # note that rho_e_total errors are large because pressure is 5e6 + l2=[2.5190796911050598e-5, 0.0013782564599067785, 7.045132422388037], + linf=[ + 0.00014865356020266063, + 0.00764166860608384, + 27.349332988262177 + ]) + + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + @test_allocations(Trixi.rhs!, semi, sol, 1000) +end + end # module From 10a01171f7ce92f0eb4328e4bdc4e618c5b99de6 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Fri, 23 Jan 2026 14:36:57 -0600 Subject: [PATCH 074/360] fix test --- test/test_unit.jl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/test_unit.jl b/test/test_unit.jl index d6e15df05c8..41734a10831 100644 --- a/test/test_unit.jl +++ b/test/test_unit.jl @@ -808,11 +808,11 @@ end invoke(temperature, Tuple{Any, Any, Trixi.AbstractEquationOfState}, V, e, eos) # check that fallback calc_pressure_derivatives matches specialized routines - @test calc_pressure_derivatives(V, T, eos)[1] ≈ - invoke(calc_pressure_derivatives, + @test Trixi.calc_pressure_derivatives(V, T, eos)[1] ≈ + invoke(Trixi.calc_pressure_derivatives, Tuple{Any, Any, Trixi.AbstractEquationOfState}, V, T, eos)[1] - @test calc_pressure_derivatives(V, T, eos)[2] ≈ - invoke(calc_pressure_derivatives, + @test Trixi.calc_pressure_derivatives(V, T, eos)[2] ≈ + invoke(Trixi.calc_pressure_derivatives, Tuple{Any, Any, Trixi.AbstractEquationOfState}, V, T, eos)[2] end From ed9647c42b35c17e9f215abe546b205d56af64a2 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Fri, 23 Jan 2026 22:46:06 -0600 Subject: [PATCH 075/360] add abstract type AbstractNonIdealCompressibleEuler --- src/equations/equations.jl | 5 +++ .../nonideal_compressible_euler_1d.jl | 34 ++++++++++++------- 2 files changed, 26 insertions(+), 13 deletions(-) diff --git a/src/equations/equations.jl b/src/equations/equations.jl index 322e411606b..469e40ac6d6 100644 --- a/src/equations/equations.jl +++ b/src/equations/equations.jl @@ -646,8 +646,13 @@ include("compressible_euler_1d.jl") include("compressible_euler_2d.jl") include("compressible_euler_3d.jl") include("compressible_euler_quasi_1d.jl") + +# Non-ideal compressibleEulerEquations +abstract type AbstractNonIdealCompressibleEulerEquations{NDIMS, NVARS} <: + AbstractCompressibleEulerEquations{NDIMS, NVARS} end include("equations_of_state.jl") include("nonideal_compressible_euler_1d.jl") +include("nonideal_compressible_euler_2d.jl") # CompressibleEulerMulticomponentEquations abstract type AbstractCompressibleEulerMulticomponentEquations{NDIMS, NVARS, NCOMP} <: diff --git a/src/equations/nonideal_compressible_euler_1d.jl b/src/equations/nonideal_compressible_euler_1d.jl index d584d386873..a302e30e205 100644 --- a/src/equations/nonideal_compressible_euler_1d.jl +++ b/src/equations/nonideal_compressible_euler_1d.jl @@ -42,7 +42,7 @@ Because of this, the primitive variables are also defined to be `V, v1, T` (inst mass basis unless otherwise specified. """ struct NonIdealCompressibleEulerEquations1D{EoS <: AbstractEquationOfState} <: - AbstractCompressibleEulerEquations{1, 3} + AbstractNonIdealCompressibleEulerEquations{1, 3} equation_of_state::EoS end @@ -288,25 +288,27 @@ S = -\rho s ``` where `s` is the specific entropy determined by the equation of state. """ -@inline function entropy_math(u, equations::NonIdealCompressibleEulerEquations1D) +@inline function entropy_math(u, equations::AbstractNonIdealCompressibleEulerEquations) eos = equations.equation_of_state - V, _, T = cons2prim(u, equations) + q = cons2prim(u, equations) + V = first(q) + T = last(q) rho = u[1] S = -rho * entropy_specific(V, T, eos) return S end """ - entropy(cons, equations::NonIdealCompressibleEulerEquations1D) + entropy(cons, equations::AbstractNonIdealEulerEquations) Default entropy is the mathematical entropy -[`entropy_math(cons, equations::NonIdealCompressibleEulerEquations1D)`](@ref). +[`entropy_math(cons, equations::AbstractNonIdealEulerEquations)`](@ref). """ -@inline function entropy(cons, equations::NonIdealCompressibleEulerEquations1D) +@inline function entropy(cons, equations::AbstractNonIdealCompressibleEulerEquations) return entropy_math(cons, equations) end -@inline function density(u, equations::NonIdealCompressibleEulerEquations1D) +@inline function density(u, equations::AbstractNonIdealCompressibleEulerEquations) rho = u[1] return rho end @@ -322,24 +324,30 @@ end return v1 end -@inline function pressure(u, equations::NonIdealCompressibleEulerEquations1D) +@inline function pressure(u, equations::AbstractNonIdealCompressibleEulerEquations) eos = equations.equation_of_state - V, _, T = cons2prim(u, equations) + q = cons2prim(u, equations) + V = first(q) + T = last(q) p = pressure(V, T, eos) return p end -@inline function density_pressure(u, equations::NonIdealCompressibleEulerEquations1D) +@inline function density_pressure(u, equations::AbstractNonIdealCompressibleEulerEquations) eos = equations.equation_of_state rho = u[1] - V, _, T = cons2prim(u, equations) + q = cons2prim(u, equations) + V = first(q) + T = last(q) p = pressure(V, T, eos) return rho * p end -@inline function energy_internal(u, equations::NonIdealCompressibleEulerEquations1D) +@inline function energy_internal(u, equations::AbstractNonIdealCompressibleEulerEquations) eos = equations.equation_of_state - V, _, T = cons2prim(u, equations) + q = cons2prim(u, equations) + V = first(q) + T = last(q) e = energy_internal(V, T, eos) return e end From 409e4a90c28509364648266b93aba2ff4979fd58 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Fri, 23 Jan 2026 22:46:38 -0600 Subject: [PATCH 076/360] add 2D non-ideal euler equations --- src/Trixi.jl | 3 +- .../nonideal_compressible_euler_2d.jl | 519 ++++++++++++++++++ 2 files changed, 521 insertions(+), 1 deletion(-) create mode 100644 src/equations/nonideal_compressible_euler_2d.jl diff --git a/src/Trixi.jl b/src/Trixi.jl index d611fb89c00..6bdf1ab5f85 100644 --- a/src/Trixi.jl +++ b/src/Trixi.jl @@ -182,7 +182,8 @@ export AcousticPerturbationEquations2D, LinearElasticityEquations1D, PassiveTracerEquations -export NonIdealCompressibleEulerEquations1D, IdealGas, VanDerWaals, PengRobinson +export NonIdealCompressibleEulerEquations1D, NonIdealCompressibleEulerEquations2D +export IdealGas, VanDerWaals, PengRobinson export LaplaceDiffusion1D, LaplaceDiffusion2D, LaplaceDiffusion3D, LaplaceDiffusionEntropyVariables1D, LaplaceDiffusionEntropyVariables2D, diff --git a/src/equations/nonideal_compressible_euler_2d.jl b/src/equations/nonideal_compressible_euler_2d.jl new file mode 100644 index 00000000000..1d257327c0b --- /dev/null +++ b/src/equations/nonideal_compressible_euler_2d.jl @@ -0,0 +1,519 @@ +# By default, Julia/LLVM does not use fused multiply-add operations (FMAs). +# Since these FMAs can increase the performance of many numerical algorithms, +# we need to opt-in explicitly. +# See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. +@muladd begin +#! format: noindent + +@doc raw""" + NonIdealCompressibleEulerEquations2D(equation_of_state) + +The compressible Euler equations +```math +\frac{\partial}{\partial t} +\begin{pmatrix} + \rho \\ \rho v_1 \\ \rho v_2 \\ \rho e_{total} +\end{pmatrix} ++ +\frac{\partial}{\partial x} +\begin{pmatrix} + \rho v_1 \\ \rho v_1^2 + p \\ \rho v_1 v_2 \\ (\rho e_{total} + p) v_1 +\end{pmatrix} ++ +\frac{\partial}{\partial y} +\begin{pmatrix} +\rho v_2 \\ \rho v_1 v_2 \\ \rho v_2^2 + p \\ (\rho e_{total} + p) v_2 +\end{pmatrix} += +\begin{pmatrix} + 0 \\ 0 \\ 0 +\end{pmatrix} +``` +for a gas with pressure ``p`` specified by some equation of state in one space dimension. + +Here, ``\rho`` is the density, ``v_1`` the x-velocity, ``v_2`` is the y-velocity, ``e_{total}`` +the specific total energy, and the pressure ``p`` is given in terms of specific volume ``V = 1/\rho`` +and temperature ``T`` by some user-specified equation of state (EOS) (see [`pressure(V, T, eos::IdealGas)`](@ref), +[`pressure(V, T, eos::VanDerWaals)`](@ref)) as +```math +p = p(V, T) +``` + +Similarly, the internal energy is specified by `e = energy_internal(V, T, eos)`, see +[`energy_internal(V, T, eos::IdealGas)`](@ref), [`energy_internal(V, T, eos::VanDerWaals)`](@ref). + +Because of this, the primitive variables are also defined to be `V, v1, v2, T` (instead of +`rho, v1, v2, p` for `CompressibleEulerEquations2D`). The implementation also assumes +mass basis unless otherwise specified. +""" +struct NonIdealCompressibleEulerEquations2D{EoS <: AbstractEquationOfState} <: + AbstractCompressibleEulerEquations{2, 4} + equation_of_state::EoS +end + +function varnames(::typeof(cons2cons), ::NonIdealCompressibleEulerEquations2D) + return ("rho", "rho_v1", "rho_v2", "rho_e_total") +end +varnames(::typeof(cons2prim), ::NonIdealCompressibleEulerEquations2D) = ("V", "v1", + "v2", "T") + +# for plotting with PlotData1D(sol, solution_variables=density_velocity_pressure) +@inline function density_velocity_pressure(u, + equations::NonIdealCompressibleEulerEquations2D) + eos = equations.equation_of_state + rho = u[1] + V, v1, v2, T = cons2prim(u, equations) + return SVector(rho, v1, v2, pressure(V, T, eos)) +end +varnames(::typeof(density_velocity_pressure), ::NonIdealCompressibleEulerEquations2D) = ("rho", + "v1", + "v2", + "p") + +# Calculate flux for a single point +@inline function flux(u, orientation::Integer, + equations::NonIdealCompressibleEulerEquations2D) + eos = equations.equation_of_state + + _, rho_v1, rho_v2, rho_e_total = u + V, v1, v2, T = cons2prim(u, equations) + p = pressure(V, T, eos) + + if orientation == 1 + f1 = rho_v1 + f2 = rho_v1 * v1 + p + f3 = rho_v1 * v2 + f4 = (rho_e_total + p) * v1 + else + f1 = rho_v2 + f2 = rho_v2 * v1 + f3 = rho_v2 * v2 + p + f4 = (rho_e_total + p) * v2 + end + return SVector(f1, f2, f3, f4) +end + +# Calculate 1D flux for a single point +@inline function flux(u, normal_direction::AbstractVector, + equations::NonIdealCompressibleEulerEquations2D) + eos = equations.equation_of_state + + rho_e_total = last(u) + V, v1, v2, T = cons2prim(u, equations) + p = pressure(V, T, eos) + + v_normal = v1 * normal_direction[1] + v2 * normal_direction[2] + rho_v_normal = rho * v_normal + + f1 = rho_v_normal + f2 = rho_v_normal * v1 + p * normal_direction[1] + f3 = rho_v_normal * v2 + p * normal_direction[2] + f4 = (rho_e_total + p) * v_normal + return SVector(f1, f2, f3, f4) +end + +""" + boundary_condition_slip_wall(u_inner, orientation, direction, x, t, + surface_flux_function, equations::NonIdealCompressibleEulerEquations2D) + +Should be used together with [`TreeMesh`](@ref). +""" +@inline function boundary_condition_slip_wall(u_inner, orientation, + direction, x, t, + surface_flux_function, + equations::NonIdealCompressibleEulerEquations2D) + # get the appropriate normal vector from the orientation + RealT = eltype(u_inner) + if orientation == 1 + normal_direction = SVector(one(RealT), zero(RealT)) + else # orientation == 2 + normal_direction = SVector(zero(RealT), one(RealT)) + end + + # compute and return the flux using `boundary_condition_slip_wall` routine below + return boundary_condition_slip_wall(u_inner, normal_direction, direction, + x, t, surface_flux_function, equations) +end + +""" + boundary_condition_slip_wall(u_inner, normal_direction, x, t, surface_flux_function, + equations::NonIdealCompressibleEulerEquations2D) + +Determine the boundary numerical surface flux for a slip wall condition. +Imposes a zero normal velocity at the wall. +Density is taken from the internal solution state, + +Should be used together with [`UnstructuredMesh2D`](@ref), [`P4estMesh`](@ref), or [`T8codeMesh`](@ref). +""" +@inline function boundary_condition_slip_wall(u_inner, normal_direction::AbstractVector, + x, t, surface_flux_function, + equations::NonIdealCompressibleEulerEquations2D) + norm_ = norm(normal_direction) + # Normalize the vector without using `normalize` since we need to multiply by the `norm_` later + normal = normal_direction / norm_ + + p = pressure(u_inner, equations) + + # For the slip wall we directly set the flux as the normal velocity is zero + return SVector(0, + p * normal[1], + p * normal[2], + 0) * norm_ +end + +""" + flux_terashima_etal(u_ll, u_rr, orientation::Int, + equations::NonIdealCompressibleEulerEquations1D) + +Approximately pressure equilibrium conserving (APEC) flux from +"Approximately pressure-equilibrium-preserving scheme for fully conservative +simulations of compressible multi-species and real-fluid interfacial flows" +by Terashima, Ly, Ihme (2025). https://doi.org/10.1016/j.jcp.2024.11370 1 + +""" +function flux_terashima_etal(u_ll, u_rr, orientation::Int, + equations::NonIdealCompressibleEulerEquations2D) + eos = equations.equation_of_state + V_ll, v1_ll, v2_ll, T_ll = cons2prim(u_ll, equations) + V_rr, v1_rr, v2_rr, T_rr = cons2prim(u_rr, equations) + + rho_ll = u_ll[1] + rho_rr = u_rr[1] + rho_e_ll = internal_energy_density(u_ll, equations) + rho_e_rr = internal_energy_density(u_rr, equations) + p_ll = pressure(V_ll, T_ll, eos) + p_rr = pressure(V_rr, T_rr, eos) + + rho_avg = 0.5f0 * (rho_ll + rho_rr) + v1_avg = 0.5f0 * (v1_ll + v1_rr) + v2_avg = 0.5f0 * (v2_ll + v2_rr) + p_avg = 0.5f0 * (p_ll + p_rr) + rho_e_avg = 0.5f0 * (rho_e_ll + rho_e_rr) + p_v1_avg = 0.5f0 * (p_ll * v1_rr + p_rr * v1_ll) + p_v2_avg = 0.5f0 * (p_ll * v2_rr + p_rr * v2_ll) + + # chain rule from Terashima + drho_e_drho_p_ll = drho_e_drho_at_const_p(V_ll, T_ll, eos) + drho_e_drho_p_rr = drho_e_drho_at_const_p(V_rr, T_rr, eos) + rho_e_avg_corrected = (rho_e_avg - + 0.25f0 * (drho_e_drho_p_rr - drho_e_drho_p_ll) * + (rho_rr - rho_ll)) + ke_avg = 0.5f0 * ((v1_ll * v1_rr) + (v2_ll * v2_rr)) + + if orientation == 1 + f_rho = rho_avg * v1_avg + f_rho_v1 = f_rho * v1_avg + p_avg + f_rho_v2 = f_rho * v2_avg + f_rho_E = (rho_e_avg_corrected + rho_avg * ke_avg) * v1_avg + p_v1_avg + else # if orientation == 2 + f_rho = rho_avg * v2_avg + f_rho_v1 = f_rho * v1_avg + f_rho_v2 = f_rho * v2_avg + p_avg + f_rho_E = (rho_e_avg_corrected + rho_avg * ke_avg) * v2_avg + p_v2_avg + end + + return SVector(f_rho, f_rho_v1, f_rho_v2, f_rho_E) +end + +function flux_terashima_etal(u_ll, u_rr, normal_direction::AbstractVector, + equations::NonIdealCompressibleEulerEquations2D) + eos = equations.equation_of_state + V_ll, v1_ll, v2_ll, T_ll = cons2prim(u_ll, equations) + V_rr, v1_rr, v2_rr, T_rr = cons2prim(u_rr, equations) + + rho_ll = u_ll[1] + rho_rr = u_rr[1] + rho_e_ll = internal_energy_density(u_ll, equations) + rho_e_rr = internal_energy_density(u_rr, equations) + p_ll = pressure(V_ll, T_ll, eos) + p_rr = pressure(V_rr, T_rr, eos) + + v_dot_n_ll = v1_ll * normal_direction[1] + v2_ll * normal_direction[2] + v_dot_n_rr = v1_rr * normal_direction[1] + v2_rr * normal_direction[2] + v_dot_n_avg = 0.5f0 * (v_dot_n_ll + v_dot_n_rr) + + rho_avg = 0.5f0 * (rho_ll + rho_rr) + v1_avg = 0.5f0 * (v1_ll + v1_rr) + v2_avg = 0.5f0 * (v2_ll + v2_rr) + p_avg = 0.5f0 * (p_ll + p_rr) + rho_e_avg = 0.5f0 * (rho_e_ll + rho_e_rr) + p_v_dot_n_avg = 0.5f0 * (p_ll * v_dot_n_rr + p_rr * v_dot_n_ll) + + # chain rule from Terashima + drho_e_drho_p_ll = drho_e_drho_at_const_p(V_ll, T_ll, eos) + drho_e_drho_p_rr = drho_e_drho_at_const_p(V_rr, T_rr, eos) + rho_e_avg_corrected = (rho_e_avg - + 0.25f0 * (drho_e_drho_p_rr - drho_e_drho_p_ll) * + (rho_rr - rho_ll)) + + f_rho = rho_avg * v_dot_n_avg + f_rho_v1 = f_rho * v1_avg + p_avg * normal_direction[1] + f_rho_v2 = f_rho * v2_avg + p_avg * normal_direction[2] + f_rho_E = (rho_e_avg_corrected + rho_avg * ke_avg) * v_dot_n_avg + p_v_dot_n_avg + return SVector(f_rho, f_rho_v1, f_rho_v2, f_rho_E) +end + +""" + flux_central_terashima_etal(u_ll, u_rr, orientation::Int, + equations::NonIdealCompressibleEulerEquations1D) + +A version of the central flux which uses the approximately pressure equilibrium conserving +(APEC) internal energy correction of +"Approximately pressure-equilibrium-preserving scheme for fully conservative +simulations of compressible multi-species and real-fluid interfacial flows" +by Terashima, Ly, Ihme (2025). https://doi.org/10.1016/j.jcp.2024.11370 +""" +function flux_central_terashima_etal(u_ll, u_rr, orientation::Int, + equations::NonIdealCompressibleEulerEquations2D) + eos = equations.equation_of_state + V_ll, v1_ll, v2_ll, T_ll = cons2prim(u_ll, equations) + V_rr, v1_rr, v2_rr, T_rr = cons2prim(u_rr, equations) + + rho_ll, rho_v1_ll, rho_v2_ll, rho_e_total_ll = u_ll + rho_rr, rho_v1_rr, rho_v2_rr, rho_e_total_rr = u_rr + rho_e_ll = internal_energy_density(u_ll, equations) + rho_e_rr = internal_energy_density(u_rr, equations) + p_ll = pressure(V_ll, T_ll, eos) + p_rr = pressure(V_rr, T_rr, eos) + + v1_avg = 0.5f0 * (v1_ll + v1_rr) + v2_avg = 0.5f0 * (v2_ll + v2_rr) + p_avg = 0.5f0 * (p_ll + p_rr) + rho_e_avg = 0.5f0 * (rho_e_ll + rho_e_rr) + + # chain rule from Terashima + drho_e_drho_p_ll = drho_e_drho_at_const_p(V_ll, T_ll, eos) + drho_e_drho_p_rr = drho_e_drho_at_const_p(V_rr, T_rr, eos) + rho_e_avg_corrected = (rho_e_avg - + 0.25f0 * (drho_e_drho_p_rr - drho_e_drho_p_ll) * + (rho_rr - rho_ll)) + + # calculate internal energy (with APEC correction) and kinetic energy + # contributions separately in energy equation + ke_ll = 0.5f0 * (v1_ll^2 + v2_ll^2) + ke_rr = 0.5f0 * (v1_rr^2 + v2_rr^2) + + if orientation == 1 + f_rho = 0.5f0 * (rho_v1_ll + rho_v1_rr) + f_rho_v1 = 0.5f0 * (rho_v1_ll * v1_ll + rho_v1_rr * v1_rr) + p_avg + f_rho_v2 = 0.5f0 * (rho_v1_ll * v2_ll + rho_v1_rr * v2_rr) + f_rho_E = rho_e_avg_corrected * v1_avg + + 0.5f0 * (rho_v1_ll * ke_ll + rho_v1_rr * ke_rr) + + 0.5f0 * (p_ll * v1_ll + p_rr * v1_rr) + else # if orientation == 2 + f_rho = 0.5f0 * (rho_v2_ll + rho_v2_rr) + f_rho_v1 = 0.5f0 * (rho_v1_ll * v2_ll + rho_v1_rr * v2_rr) + f_rho_v2 = 0.5f0 * (rho_v2_ll * v2_ll + rho_v2_rr * v2_rr) + p_avg + f_rho_E = rho_e_avg_corrected * v2_avg + + 0.5f0 * (rho_v2_ll * ke_ll + rho_v2_rr * ke_rr) + + 0.5f0 * (p_ll * v2_ll + p_rr * v2_rr) + end + + return SVector(f_rho, f_rho_v1, f_rho_v2, f_rho_E) +end + +# Calculate estimates for minimum and maximum wave speeds for HLL-type fluxes +@inline function min_max_speed_naive(u_ll, u_rr, orientation::Integer, + equations::NonIdealCompressibleEulerEquations2D) + V_ll, v1_ll, v2_ll, T_ll = cons2prim(u_ll, equations) + V_rr, v1_rr, v2_rr, T_rr = cons2prim(u_rr, equations) + + eos = equations.equation_of_state + c_ll = speed_of_sound(V_ll, T_ll, eos) + c_rr = speed_of_sound(V_rr, T_rr, eos) + + if orientation == 1 # x-direction + λ_min = v1_ll - c_ll + λ_max = v1_rr + c_rr + else # y-direction + λ_min = v2_ll - c_ll + λ_max = v2_rr + c_rr + end + + return λ_min, λ_max +end + +@inline function min_max_speed_naive(u_ll, u_rr, normal_direction::AbstractVector, + equations::NonIdealCompressibleEulerEquations2D) + V_ll, v1_ll, v2_ll, T_ll = cons2prim(u_ll, equations) + V_rr, v1_rr, v2_rr, T_rr = cons2prim(u_rr, equations) + + v_normal_ll = v1_ll * normal_direction[1] + v2_ll * normal_direction[2] + v_normal_rr = v1_rr * normal_direction[1] + v2_rr * normal_direction[2] + + eos = equations.equation_of_state + c_ll = speed_of_sound(V_ll, T_ll, eos) + c_rr = speed_of_sound(V_rr, T_rr, eos) + + norm_ = norm(normal_direction) + # The v_normals are already scaled by the norm + λ_min = v_normal_ll - c_ll * norm_ + λ_max = v_normal_rr + c_rr * norm_ + + return λ_min, λ_max +end + +# Less "cautious", i.e., less overestimating `λ_max` compared to `max_abs_speed_naive` +@inline function max_abs_speed(u_ll, u_rr, orientation::Integer, + equations::NonIdealCompressibleEulerEquations2D) + V_ll, v1_ll, v2_ll, T_ll = cons2prim(u_ll, equations) + V_rr, v1_rr, v2_rr, T_rr = cons2prim(u_rr, equations) + + # Get the velocity value in the appropriate direction + if orientation == 1 + v_ll = v1_ll + v_rr = v1_rr + else # orientation == 2 + v_ll = v2_ll + v_rr = v2_rr + end + v_mag_ll = abs(v_ll) + v_mag_rr = abs(v_rr) + + # Calculate primitive variables and speed of sound + eos = equations.equation_of_state + c_ll = speed_of_sound(V_ll, T_ll, eos) + c_rr = speed_of_sound(V_rr, T_rr, eos) + + return max(v_mag_ll + c_ll, v_mag_rr + c_rr) +end + +@inline function max_abs_speed_naive(u_ll, u_rr, orientation::Integer, + equations::NonIdealCompressibleEulerEquations2D) + V_ll, v1_ll, v2_ll, T_ll = cons2prim(u_ll, equations) + V_rr, v1_rr, v2_rr, T_rr = cons2prim(u_rr, equations) + + # Get the velocity value in the appropriate direction + if orientation == 1 + v_ll = v1_ll + v_rr = v1_rr + else # orientation == 2 + v_ll = v2_ll + v_rr = v2_rr + end + + v_mag_ll = abs(v_ll) + v_mag_rr = abs(v_rr) + + # Calculate primitive variables and speed of sound + eos = equations.equation_of_state + c_ll = speed_of_sound(V_ll, T_ll, eos) + c_rr = speed_of_sound(V_rr, T_rr, eos) + + return max(v_mag_ll, v_mag_rr) + max(c_ll, c_rr) +end + +# More refined estimates for minimum and maximum wave speeds for HLL-type fluxes +@inline function min_max_speed_davis(u_ll, u_rr, orientation::Integer, + equations::NonIdealCompressibleEulerEquations2D) + V_ll, v1_ll, v2_ll, T_ll = cons2prim(u_ll, equations) + V_rr, v1_rr, v2_rr, T_rr = cons2prim(u_rr, equations) + + # Calculate primitive variables and speed of sound + eos = equations.equation_of_state + c_ll = speed_of_sound(V_ll, T_ll, eos) + c_rr = speed_of_sound(V_rr, T_rr, eos) + + if orientation == 1 # x-direction + λ_min = min(v1_ll - c_ll, v1_rr - c_rr) + λ_max = max(v1_ll + c_ll, v1_rr + c_rr) + else # y-direction + λ_min = min(v2_ll - c_ll, v2_rr - c_rr) + λ_max = max(v2_ll + c_ll, v2_rr + c_rr) + end + + return λ_min, λ_max +end + +# More refined estimates for minimum and maximum wave speeds for HLL-type fluxes +@inline function min_max_speed_davis(u_ll, u_rr, normal_direction::AbstractVector, + equations::NonIdealCompressibleEulerEquations2D) + V_ll, v1_ll, v2_ll, T_ll = cons2prim(u_ll, equations) + V_rr, v1_rr, v2_rr, T_rr = cons2prim(u_rr, equations) + + norm_ = norm(normal_direction) + + # Calculate primitive variables and speed of sound + eos = equations.equation_of_state + c_ll = speed_of_sound(V_ll, T_ll, eos) * norm_ + c_rr = speed_of_sound(V_rr, T_rr, eos) * norm_ + + v_normal_ll = v1_ll * normal_direction[1] + v2_ll * normal_direction[2] + v_normal_rr = v1_rr * normal_direction[1] + v2_rr * normal_direction[2] + + # The v_normals are already scaled by the norm + λ_min = min(v_normal_ll - c_ll, v_normal_rr - c_rr) + λ_max = max(v_normal_ll + c_ll, v_normal_rr + c_rr) + + return λ_min, λ_max +end + +@inline function max_abs_speeds(u, equations::NonIdealCompressibleEulerEquations2D) + V, v1, v2, T = cons2prim(u, equations) + + # Calculate primitive variables and speed of sound + eos = equations.equation_of_state + c = speed_of_sound(V, T, eos) + + return (abs(v1) + c, abs(v2) + c) +end + +# Convert conservative variables to primitive +@inline function cons2prim(u, equations::NonIdealCompressibleEulerEquations2D) + eos = equations.equation_of_state + rho, rho_v1, rho_v2, rho_e_total = u + + V = inv(rho) + v1 = rho_v1 * V + v2 = rho_v2 * V + e = internal_energy_density(u, equations) * V + T = temperature(V, e, eos) + + return SVector(V, v1, v2, T) +end + +# Convert conservative variables to entropy +@inline function cons2entropy(u, equations::NonIdealCompressibleEulerEquations2D) + V, v1, v2, T = cons2prim(u, equations) + eos = equations.equation_of_state + gibbs = gibbs_free_energy(V, T, eos) + return inv(T) * SVector(gibbs - 0.5f0 * (v1^2 + v2^2), v1, v2, -1) +end + +# Convert primitive to conservative variables +@inline function prim2cons(prim, equations::NonIdealCompressibleEulerEquations2D) + eos = equations.equation_of_state + V, v1, v2, T = prim + rho = inv(V) + rho_v1 = rho * v1 + rho_v2 = rho * v2 + e = energy_internal(V, T, eos) + rho_e_total = rho * e + 0.5f0 * (rho_v1 * v1 + rho_v2 * v2) + return SVector(rho, rho_v1, rho_v2, rho_e_total) +end + +@inline function velocity(u, orientation::Int, + equations::NonIdealCompressibleEulerEquations2D) + if orientation == 1 + v1 = u[2] / rho + return v1 + else # if orientation == 2 + v2 = u[3] / rho + return v2 + end +end + +@inline function velocity(u, equations::NonIdealCompressibleEulerEquations2D) + rho = u[1] + v1 = u[2] / rho + v2 = u[3] / rho + return SVector(v1, v2) +end + +@inline function internal_energy_density(u, + equations::NonIdealCompressibleEulerEquations2D) + rho, rho_v1, rho_v2, rho_e_total = u + rho_e = rho_e_total - 0.5f0 * (rho_v1^2 + rho_v2^2) / rho + return rho_e +end +end # @muladd From b4042bb729d028a89acd4ab368f84c3d8e019ef5 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Fri, 23 Jan 2026 22:46:54 -0600 Subject: [PATCH 077/360] add "safeguard" to Newton solve --- examples/tree_1d_dgsem/elixir_euler_nonideal_density_wave.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/tree_1d_dgsem/elixir_euler_nonideal_density_wave.jl b/examples/tree_1d_dgsem/elixir_euler_nonideal_density_wave.jl index 135a7871edb..29424c657c9 100644 --- a/examples/tree_1d_dgsem/elixir_euler_nonideal_density_wave.jl +++ b/examples/tree_1d_dgsem/elixir_euler_nonideal_density_wave.jl @@ -28,7 +28,7 @@ function initial_condition_density_wave(x, t, while abs(dp) / abs(p) > tol && iter < 100 dp = pressure(V, T, eos) - p dpdT_V = ForwardDiff.derivative(T -> pressure(V, T, eos), T) - T = T - dp / dpdT_V + T = max(tol, T - dp / dpdT_V) iter += 1 end if iter == 100 From 998d2861f3c75cb06e0c1383c700359cd7ff2077 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Fri, 23 Jan 2026 22:49:27 -0600 Subject: [PATCH 078/360] add 2D density wave --- .../elixir_euler_nonideal_density_wave.jl | 82 +++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 examples/tree_2d_dgsem/elixir_euler_nonideal_density_wave.jl diff --git a/examples/tree_2d_dgsem/elixir_euler_nonideal_density_wave.jl b/examples/tree_2d_dgsem/elixir_euler_nonideal_density_wave.jl new file mode 100644 index 00000000000..c930a7a8f8e --- /dev/null +++ b/examples/tree_2d_dgsem/elixir_euler_nonideal_density_wave.jl @@ -0,0 +1,82 @@ +using OrdinaryDiffEqLowStorageRK +using Trixi +using Trixi: ForwardDiff + +############################################################################### +# semidiscretization of the compressible Euler equations + +eos = VanDerWaals(; a = 10, b = 1e-2, gamma = 1.4, R = 287) +equations = NonIdealCompressibleEulerEquations2D(eos) + +# the default amplitude and frequency k are chosen to be consistent with +# initial_condition_density_wave for CompressibleEulerEquations1D +function initial_condition_density_wave(x, t, + equations::NonIdealCompressibleEulerEquations2D; + amplitude = 0.98, k = 2) + RealT = eltype(x) + v1 = convert(RealT, 0.1) + v2 = convert(RealT, 0.2) + rho = 1 + convert(RealT, amplitude) * sinpi(k * (x[1] + x[2] - t * (v1 + v2))) + p = 20 + + V = inv(rho) + + # invert for temperature given p, V + T = 1 + tol = 100 * eps(RealT) + dp = pressure(V, T, eos) - p + iter = 1 + while abs(dp) / abs(p) > tol && iter < 100 + dp = pressure(V, T, eos) - p + dpdT_V = ForwardDiff.derivative(T -> pressure(V, T, eos), T) + T = max(tol, T - dp / dpdT_V) + iter += 1 + end + if iter == 100 + println("Warning: solver for temperature(V, p) did not converge") + end + + return prim2cons(SVector(V, v1, v2, T), equations) +end + +initial_condition = initial_condition_density_wave + +volume_flux = flux_central_terashima_etal +# volume_flux = flux_terashima_etal +volume_integral = VolumeIntegralFluxDifferencing(volume_flux) +solver = DGSEM(polydeg = 3, volume_integral = volume_integral, + surface_flux = flux_lax_friedrichs) + +coordinates_min = (-1.0, -1.0) +coordinates_max = (1.0, 1.0) +mesh = TreeMesh(coordinates_min, coordinates_max, + initial_refinement_level = 3, + n_cells_max = 30_000) + +semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) + +############################################################################### +# ODE solvers, callbacks etc. + +tspan = (0.0, 2.0) +ode = semidiscretize(semi, tspan) + +summary_callback = SummaryCallback() + +analysis_interval = 2000 +analysis_callback = AnalysisCallback(semi, interval = analysis_interval) + +alive_callback = AliveCallback(analysis_interval = analysis_interval) + +stepsize_callback = StepsizeCallback(cfl = 0.75) + +callbacks = CallbackSet(summary_callback, + analysis_callback, alive_callback, + stepsize_callback) + +############################################################################### +# run the simulation + +sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false); + dt = stepsize_callback(ode), # solve needs some value here but it will be overwritten by the stepsize_callback + ode_default_options()..., callback = callbacks); From d127dad7379dee0c8ba8b6bda16acef3d09ecabc Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Sat, 24 Jan 2026 09:42:59 -0600 Subject: [PATCH 079/360] formatting --- src/equations/nonideal_compressible_euler_1d.jl | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/equations/nonideal_compressible_euler_1d.jl b/src/equations/nonideal_compressible_euler_1d.jl index a302e30e205..026c30879c8 100644 --- a/src/equations/nonideal_compressible_euler_1d.jl +++ b/src/equations/nonideal_compressible_euler_1d.jl @@ -333,7 +333,8 @@ end return p end -@inline function density_pressure(u, equations::AbstractNonIdealCompressibleEulerEquations) +@inline function density_pressure(u, + equations::AbstractNonIdealCompressibleEulerEquations) eos = equations.equation_of_state rho = u[1] q = cons2prim(u, equations) @@ -343,7 +344,8 @@ end return rho * p end -@inline function energy_internal(u, equations::AbstractNonIdealCompressibleEulerEquations) +@inline function energy_internal(u, + equations::AbstractNonIdealCompressibleEulerEquations) eos = equations.equation_of_state q = cons2prim(u, equations) V = first(q) From 77593cab842b8a317a343211d2f6e97ec04e1cdd Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Sat, 24 Jan 2026 09:43:28 -0600 Subject: [PATCH 080/360] move density wave initial conditions out of elixir into Trixi namespace --- .../elixir_euler_nonideal_density_wave.jl | 30 --------- .../nonideal_compressible_euler_1d.jl | 64 +++++++++++++++++++ test/test_tree_1d_euler.jl | 3 +- 3 files changed, 66 insertions(+), 31 deletions(-) diff --git a/examples/tree_1d_dgsem/elixir_euler_nonideal_density_wave.jl b/examples/tree_1d_dgsem/elixir_euler_nonideal_density_wave.jl index 29424c657c9..97cbaa8995d 100644 --- a/examples/tree_1d_dgsem/elixir_euler_nonideal_density_wave.jl +++ b/examples/tree_1d_dgsem/elixir_euler_nonideal_density_wave.jl @@ -8,36 +8,6 @@ using Trixi: ForwardDiff eos = VanDerWaals(; a = 10, b = 1e-2, gamma = 1.4, R = 287) equations = NonIdealCompressibleEulerEquations1D(eos) -# the default amplitude and frequency k are chosen to be consistent with -# initial_condition_density_wave for CompressibleEulerEquations1D -function initial_condition_density_wave(x, t, - equations::NonIdealCompressibleEulerEquations1D; - amplitude = 0.98, k = 2) - RealT = eltype(x) - v1 = convert(RealT, 0.1) - rho = 1 + convert(RealT, amplitude) * sinpi(k * (x[1] - v1 * t)) - p = 20 - - V = inv(rho) - - # invert for temperature given p, V - T = 1 - tol = 100 * eps(RealT) - dp = pressure(V, T, eos) - p - iter = 1 - while abs(dp) / abs(p) > tol && iter < 100 - dp = pressure(V, T, eos) - p - dpdT_V = ForwardDiff.derivative(T -> pressure(V, T, eos), T) - T = max(tol, T - dp / dpdT_V) - iter += 1 - end - if iter == 100 - println("Warning: solver for temperature(V, p) did not converge") - end - - return prim2cons(SVector(V, v1, T), equations) -end - initial_condition = initial_condition_density_wave solver = DGSEM(polydeg = 3, surface_flux = flux_lax_friedrichs) diff --git a/src/equations/nonideal_compressible_euler_1d.jl b/src/equations/nonideal_compressible_euler_1d.jl index 026c30879c8..e59825431d1 100644 --- a/src/equations/nonideal_compressible_euler_1d.jl +++ b/src/equations/nonideal_compressible_euler_1d.jl @@ -360,4 +360,68 @@ end rho_e = rho_e_total - 0.5f0 * rho_v1^2 / rho return rho_e end + +# The default amplitude and frequency k are consistent with initial_condition_density_wave +# for CompressibleEulerEquations1D. Note that this initial condition may not define admissible +# solution states for all non-ideal equations of state! +function initial_condition_density_wave(x, t, + equations::NonIdealCompressibleEulerEquations1D; + amplitude = 0.98, k = 2) + RealT = eltype(x) + v1 = convert(RealT, 0.1) + rho = 1 + convert(RealT, amplitude) * sinpi(k * (x[1] - v1 * t)) + p = 20 + + V = inv(rho) + + # invert for temperature given p, V + T = 1 + tol = 100 * eps(RealT) + dp = pressure(V, T, eos) - p + iter = 1 + while abs(dp) / abs(p) > tol && iter < 100 + dp = pressure(V, T, eos) - p + dpdT_V = ForwardDiff.derivative(T -> pressure(V, T, eos), T) + T = max(tol, T - dp / dpdT_V) + iter += 1 + end + if iter == 100 + println("Warning: solver for temperature(V, p) did not converge") + end + + return prim2cons(SVector(V, v1, T), equations) +end + +# the smooth Peng-Robinson N2 transcritical wave taken from "An entropy-stable hybrid scheme +# for simulations of transcritical real-fluid flows" by Ma, Ihme (2017). +# +function initial_condition_transcritical_wave(x, t, + equations::NonIdealCompressibleEulerEquations1D{<:PengRobinson}) + RealT = eltype(x) + + rho_min, rho_max = 56.9, 793.1 + v1 = 100 + rho = 0.5 * (rho_min + rho_max) + + 0.5 * (rho_max - rho_min) * sin(2 * pi * (x[1] - v1 * t)) + p = 5e6 + + V = inv(rho) + + # invert for temperature given p, V + T = eos.T0 + tol = 100 * eps(RealT) + dp = pressure(V, T, eos) - p + iter = 1 + while abs(dp) / abs(p) > tol && iter < 100 + dp = pressure(V, T, eos) - p + dpdT_V = ForwardDiff.derivative(T -> pressure(V, T, eos), T) + T = max(tol, T - dp / dpdT_V) + iter += 1 + end + if iter == 100 + println("Warning: solver for temperature(V, p) did not converge") + end + + return prim2cons(SVector(V, v1, T), equations) +end end # @muladd diff --git a/test/test_tree_1d_euler.jl b/test/test_tree_1d_euler.jl index d5b87924d23..b980f7c543d 100644 --- a/test/test_tree_1d_euler.jl +++ b/test/test_tree_1d_euler.jl @@ -622,10 +622,11 @@ end @trixi_testset "elixir_euler_nonideal_transcritical_wave.jl with Peng Robinson" begin @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_euler_nonideal_transcritical_wave.jl"), + "elixir_euler_nonideal_density_wave.jl"), solver=DGSEM(polydeg = 3, volume_integral = VolumeIntegralFluxDifferencing(flux_terashima_etal), surface_flux = flux_lax_friedrichs), + initial_condition = initial_condition_transcritical_wave, tspan=(0.0, 0.001), # note that rho_e_total errors are large because pressure is 5e6 l2=[2.5190796911050598e-5, 0.0013782564599067785, 7.045132422388037], From 020e733b8aa97f17c442981bec8566ecab32b6f2 Mon Sep 17 00:00:00 2001 From: Jesse Chan <1156048+jlchan@users.noreply.github.com> Date: Sat, 24 Jan 2026 20:56:52 -0600 Subject: [PATCH 081/360] Apply suggestions from code review Co-authored-by: Hendrik Ranocha --- NEWS.md | 2 +- src/equations/equation_of_state_vdw.jl | 6 ++-- .../nonideal_compressible_euler_1d.jl | 30 +++++++++---------- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/NEWS.md b/NEWS.md index 15a13c76a50..3159cdcfaa5 100644 --- a/NEWS.md +++ b/NEWS.md @@ -9,7 +9,7 @@ for human readability. #### Added - Added `NonIdealCompressibleEulerEquations1D`, which allows users to specify a non-ideal equation of state. Currently `IdealGas` and `VanDerWaals` are supported ([#2739]). -- Added the APEC fluxes of `flux_terashima_etal` and `flux_central_terashima_etal` from [Terashima, Ly, Ihme (2025)](https://doi.org/10.1016/j.jcp.2024.113701). +- Added the APEC (approximate pressure equilibrium preserving with conservation) fluxes of `flux_terashima_etal` and `flux_central_terashima_etal` from [Terashima, Ly, Ihme (2025)](https://doi.org/10.1016/j.jcp.2024.113701) ([#2756]). - Support for second-order finite volume subcell volume integral (`VolumeIntegralPureLGLFiniteVolumeO2`) and stabilized DG-FV blending volume integral (`VolumeIntegralShockCapturingRRG`) on 3D meshes for conservative systems ([#2734], [#2755]). diff --git a/src/equations/equation_of_state_vdw.jl b/src/equations/equation_of_state_vdw.jl index 15cb75e978f..4d00cb1361a 100644 --- a/src/equations/equation_of_state_vdw.jl +++ b/src/equations/equation_of_state_vdw.jl @@ -105,9 +105,9 @@ function calc_pressure_derivatives(V, T, eos::VanDerWaals) rho = inv(V) RT = R * T one_minus_b_rho = (1 - b * rho) - dpdT_V = rho * R / (1 - rho * b) - dpdrho_T = RT / one_minus_b_rho + (RT * b * rho) / (one_minus_b_rho^2) - - 2 * a * rho + dpdT_V = rho * R / one_minus_b_rho + dpdrho_T = (RT / one_minus_b_rho + (RT * b * rho) / (one_minus_b_rho^2) - + 2 * a * rho) return dpdT_V, -dpdrho_T / V^2 end end # @muladd diff --git a/src/equations/nonideal_compressible_euler_1d.jl b/src/equations/nonideal_compressible_euler_1d.jl index 80643ac1997..908de1004ea 100644 --- a/src/equations/nonideal_compressible_euler_1d.jl +++ b/src/equations/nonideal_compressible_euler_1d.jl @@ -71,14 +71,14 @@ end flux_terashima_etal(u_ll, u_rr, orientation::Int, equations::NonIdealCompressibleEulerEquations1D) -Approximately pressure equilibrium conserving (APEC) flux from +Approximately pressure equilibrium preserving with conservation (APEC) flux from "Approximately pressure-equilibrium-preserving scheme for fully conservative simulations of compressible multi-species and real-fluid interfacial flows" -by Terashima, Ly, Ihme (2025). https://doi.org/10.1016/j.jcp.2024.11370 1 +by Terashima, Ly, Ihme (2025). """ -function flux_terashima_etal(u_ll, u_rr, orientation::Int, - equations::NonIdealCompressibleEulerEquations1D) +@inline function flux_terashima_etal(u_ll, u_rr, orientation::Int, + equations::NonIdealCompressibleEulerEquations1D) eos = equations.equation_of_state V_ll, v1_ll, T_ll = cons2prim(u_ll, equations) V_rr, v1_rr, T_rr = cons2prim(u_rr, equations) @@ -115,14 +115,14 @@ end flux_central_terashima_etal(u_ll, u_rr, orientation::Int, equations::NonIdealCompressibleEulerEquations1D) -A version of the central flux which uses the approximately pressure equilibrium conserving +A version of the central flux which uses the pressure equilibrium preserving with conservation (APEC) internal energy correction of "Approximately pressure-equilibrium-preserving scheme for fully conservative simulations of compressible multi-species and real-fluid interfacial flows" -by Terashima, Ly, Ihme (2025). https://doi.org/10.1016/j.jcp.2024.11370 +by Terashima, Ly, Ihme (2025). """ -function flux_central_terashima_etal(u_ll, u_rr, orientation::Int, - equations::NonIdealCompressibleEulerEquations1D) +@inline function flux_central_terashima_etal(u_ll, u_rr, orientation::Int, + equations::NonIdealCompressibleEulerEquations1D) eos = equations.equation_of_state V_ll, v1_ll, T_ll = cons2prim(u_ll, equations) V_rr, v1_rr, T_rr = cons2prim(u_rr, equations) @@ -146,16 +146,16 @@ function flux_central_terashima_etal(u_ll, u_rr, orientation::Int, v1_avg # Ignore orientation since it is always "1" in 1D - f_rho = 0.5 * (rho_v1_ll + rho_v1_rr) - f_rho_v1 = 0.5 * (rho_v1_ll * v1_ll + rho_v1_rr * v1_rr) + p_avg + f_rho = 0.5f0 * (rho_v1_ll + rho_v1_rr) + f_rho_v1 = 0.5f0 * (rho_v1_ll * v1_ll + rho_v1_rr * v1_rr) + p_avg # calculate internal energy (with APEC correction) and kinetic energy - # contributions separately in energy equation - ke_ll = 0.5 * v1_ll^2 - ke_rr = 0.5 * v1_rr^2 + # contributions separately in the energy equation + ke_ll = 0.5f0 * v1_ll^2 + ke_rr = 0.5f0 * v1_rr^2 f_rho_E = rho_e_v1_avg + - 0.5 * (rho_v1_ll * ke_ll + rho_v1_rr * ke_rr) + - 0.5 * (p_ll * v1_ll + p_rr * v1_rr) + 0.5f0 * (rho_v1_ll * ke_ll + rho_v1_rr * ke_rr) + + 0.5f0 * (p_ll * v1_ll + p_rr * v1_rr) return SVector(f_rho, f_rho_v1, f_rho_E) end From 86e164271e416465876b7b55ac44394661d6c76f Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Sat, 24 Jan 2026 20:58:53 -0600 Subject: [PATCH 082/360] add type tests --- test/test_type.jl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/test_type.jl b/test/test_type.jl index d79c98e4e63..df901bceee3 100644 --- a/test/test_type.jl +++ b/test/test_type.jl @@ -232,6 +232,8 @@ isdir(outdir) && rm(outdir, recursive = true) surface_flux_function = flux_lax_friedrichs @test eltype(@inferred flux(u, orientation, equations)) == RealT + @test eltype(@inferred flux_terashima_etal(u, u, orientation, equations)) == RealT + @test eltype(@inferred flux_central_terashima_etal(u, u, orientation, equations)) == RealT @test typeof(@inferred max_abs_speed_naive(u_ll, u_rr, orientation, equations)) == RealT From 8a68a7c440710ea7b9d9addf27125e34c30d183c Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Sat, 24 Jan 2026 21:02:16 -0600 Subject: [PATCH 083/360] formatting --- src/equations/equation_of_state_vdw.jl | 2 +- test/test_type.jl | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/equations/equation_of_state_vdw.jl b/src/equations/equation_of_state_vdw.jl index 4d00cb1361a..18af6a7b220 100644 --- a/src/equations/equation_of_state_vdw.jl +++ b/src/equations/equation_of_state_vdw.jl @@ -107,7 +107,7 @@ function calc_pressure_derivatives(V, T, eos::VanDerWaals) one_minus_b_rho = (1 - b * rho) dpdT_V = rho * R / one_minus_b_rho dpdrho_T = (RT / one_minus_b_rho + (RT * b * rho) / (one_minus_b_rho^2) - - 2 * a * rho) + 2 * a * rho) return dpdT_V, -dpdrho_T / V^2 end end # @muladd diff --git a/test/test_type.jl b/test/test_type.jl index df901bceee3..6330fecd4d2 100644 --- a/test/test_type.jl +++ b/test/test_type.jl @@ -232,8 +232,10 @@ isdir(outdir) && rm(outdir, recursive = true) surface_flux_function = flux_lax_friedrichs @test eltype(@inferred flux(u, orientation, equations)) == RealT - @test eltype(@inferred flux_terashima_etal(u, u, orientation, equations)) == RealT - @test eltype(@inferred flux_central_terashima_etal(u, u, orientation, equations)) == RealT + @test eltype(@inferred flux_terashima_etal(u, u, orientation, equations)) == + RealT + @test eltype(@inferred flux_central_terashima_etal(u, u, orientation, + equations)) == RealT @test typeof(@inferred max_abs_speed_naive(u_ll, u_rr, orientation, equations)) == RealT From e0d9aee667ddc8359dbbff444932de258b25eef9 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Sat, 24 Jan 2026 21:33:30 -0600 Subject: [PATCH 084/360] use abstract NonIdealCompressibleEulerEquations2D --- src/equations/nonideal_compressible_euler_2d.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/equations/nonideal_compressible_euler_2d.jl b/src/equations/nonideal_compressible_euler_2d.jl index 1d257327c0b..0822922c38f 100644 --- a/src/equations/nonideal_compressible_euler_2d.jl +++ b/src/equations/nonideal_compressible_euler_2d.jl @@ -47,7 +47,7 @@ Because of this, the primitive variables are also defined to be `V, v1, v2, T` ( mass basis unless otherwise specified. """ struct NonIdealCompressibleEulerEquations2D{EoS <: AbstractEquationOfState} <: - AbstractCompressibleEulerEquations{2, 4} + AbstractNonIdealCompressibleEulerEquations{2, 4} equation_of_state::EoS end From 2c5ca0a57b2cf1ead91909544323b0e4739bd561 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Sat, 24 Jan 2026 21:33:41 -0600 Subject: [PATCH 085/360] cleanup --- src/equations/nonideal_compressible_euler_2d.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/equations/nonideal_compressible_euler_2d.jl b/src/equations/nonideal_compressible_euler_2d.jl index 0822922c38f..5347e7f93ab 100644 --- a/src/equations/nonideal_compressible_euler_2d.jl +++ b/src/equations/nonideal_compressible_euler_2d.jl @@ -93,11 +93,12 @@ varnames(::typeof(density_velocity_pressure), ::NonIdealCompressibleEulerEquatio return SVector(f1, f2, f3, f4) end -# Calculate 1D flux for a single point +# Calculate 2D flux for a single point @inline function flux(u, normal_direction::AbstractVector, equations::NonIdealCompressibleEulerEquations2D) eos = equations.equation_of_state + rho = first(u) rho_e_total = last(u) V, v1, v2, T = cons2prim(u, equations) p = pressure(V, T, eos) From 2b84fba67de4466b85d88056e241e696995c5465 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Sat, 24 Jan 2026 21:33:55 -0600 Subject: [PATCH 086/360] fix wall bcs --- src/equations/nonideal_compressible_euler_2d.jl | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/equations/nonideal_compressible_euler_2d.jl b/src/equations/nonideal_compressible_euler_2d.jl index 5347e7f93ab..4b93aaf00bf 100644 --- a/src/equations/nonideal_compressible_euler_2d.jl +++ b/src/equations/nonideal_compressible_euler_2d.jl @@ -132,7 +132,7 @@ Should be used together with [`TreeMesh`](@ref). end # compute and return the flux using `boundary_condition_slip_wall` routine below - return boundary_condition_slip_wall(u_inner, normal_direction, direction, + return boundary_condition_slip_wall(u_inner, normal_direction, x, t, surface_flux_function, equations) end @@ -149,17 +149,13 @@ Should be used together with [`UnstructuredMesh2D`](@ref), [`P4estMesh`](@ref), @inline function boundary_condition_slip_wall(u_inner, normal_direction::AbstractVector, x, t, surface_flux_function, equations::NonIdealCompressibleEulerEquations2D) - norm_ = norm(normal_direction) - # Normalize the vector without using `normalize` since we need to multiply by the `norm_` later - normal = normal_direction / norm_ - p = pressure(u_inner, equations) # For the slip wall we directly set the flux as the normal velocity is zero return SVector(0, - p * normal[1], - p * normal[2], - 0) * norm_ + p * normal_direction[1], + p * normal_direction[2], + 0) end """ From 493d31e9ac43c96c8127b13ffffd1cda3fad9d1d Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Sat, 24 Jan 2026 21:34:03 -0600 Subject: [PATCH 087/360] add 2D density wave IC --- .../nonideal_compressible_euler_2d.jl | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/src/equations/nonideal_compressible_euler_2d.jl b/src/equations/nonideal_compressible_euler_2d.jl index 4b93aaf00bf..81220ca1220 100644 --- a/src/equations/nonideal_compressible_euler_2d.jl +++ b/src/equations/nonideal_compressible_euler_2d.jl @@ -113,6 +113,40 @@ end return SVector(f1, f2, f3, f4) end +# the default amplitude and frequency k are chosen to be consistent with +# initial_condition_density_wave for CompressibleEulerEquations1D +function initial_condition_density_wave(x, t, + equations::NonIdealCompressibleEulerEquations2D; + amplitude = 0.98, k = 2) + RealT = eltype(x) + + eos = equations.equation_of_state + + v1 = convert(RealT, 0.1) + v2 = convert(RealT, 0.2) + rho = 1 + convert(RealT, amplitude) * sinpi(k * (x[1] + x[2] - t * (v1 + v2))) + p = 20 + + V = inv(rho) + + # invert for temperature given p, V + T = 1 + tol = 100 * eps(RealT) + dp = pressure(V, T, eos) - p + iter = 1 + while abs(dp) / abs(p) > tol && iter < 100 + dp = pressure(V, T, eos) - p + dpdT_V = ForwardDiff.derivative(T -> pressure(V, T, eos), T) + T = max(tol, T - dp / dpdT_V) + iter += 1 + end + if iter == 100 + println("Warning: solver for temperature(V, p) did not converge") + end + + return prim2cons(SVector(V, v1, v2, T), equations) +end + """ boundary_condition_slip_wall(u_inner, orientation, direction, x, t, surface_flux_function, equations::NonIdealCompressibleEulerEquations2D) From dfdaa37b570eef24d484cf4186cbd811e7ef80cc Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Sat, 24 Jan 2026 21:34:23 -0600 Subject: [PATCH 088/360] add AbstractVector specialized flux and max abs speed functions --- .../nonideal_compressible_euler_2d.jl | 96 +++++++++++++++++++ 1 file changed, 96 insertions(+) diff --git a/src/equations/nonideal_compressible_euler_2d.jl b/src/equations/nonideal_compressible_euler_2d.jl index 81220ca1220..faff70fe41c 100644 --- a/src/equations/nonideal_compressible_euler_2d.jl +++ b/src/equations/nonideal_compressible_euler_2d.jl @@ -277,6 +277,8 @@ function flux_terashima_etal(u_ll, u_rr, normal_direction::AbstractVector, 0.25f0 * (drho_e_drho_p_rr - drho_e_drho_p_ll) * (rho_rr - rho_ll)) + ke_avg = 0.5f0 * ((v1_ll * v1_rr) + (v2_ll * v2_rr)) + f_rho = rho_avg * v_dot_n_avg f_rho_v1 = f_rho * v1_avg + p_avg * normal_direction[1] f_rho_v2 = f_rho * v2_avg + p_avg * normal_direction[2] @@ -343,6 +345,54 @@ function flux_central_terashima_etal(u_ll, u_rr, orientation::Int, return SVector(f_rho, f_rho_v1, f_rho_v2, f_rho_E) end +function flux_central_terashima_etal(u_ll, u_rr, normal_direction::AbstractVector, + equations::NonIdealCompressibleEulerEquations2D) + eos = equations.equation_of_state + V_ll, v1_ll, v2_ll, T_ll = cons2prim(u_ll, equations) + V_rr, v1_rr, v2_rr, T_rr = cons2prim(u_rr, equations) + + rho_ll, rho_v1_ll, rho_v2_ll, rho_e_total_ll = u_ll + rho_rr, rho_v1_rr, rho_v2_rr, rho_e_total_rr = u_rr + rho_e_ll = internal_energy_density(u_ll, equations) + rho_e_rr = internal_energy_density(u_rr, equations) + p_ll = pressure(V_ll, T_ll, eos) + p_rr = pressure(V_rr, T_rr, eos) + + v_dot_n_ll = v1_ll * normal_direction[1] + v2_ll * normal_direction[2] + v_dot_n_rr = v1_rr * normal_direction[1] + v2_rr * normal_direction[2] + v_dot_n_avg = 0.5f0 * (v_dot_n_ll + v_dot_n_rr) + + rho_avg = 0.5f0 * (rho_ll + rho_rr) + v1_avg = 0.5f0 * (v1_ll + v1_rr) + v2_avg = 0.5f0 * (v2_ll + v2_rr) + p_avg = 0.5f0 * (p_ll + p_rr) + rho_e_avg = 0.5f0 * (rho_e_ll + rho_e_rr) + p_v_dot_n_avg = 0.5f0 * (p_ll * v_dot_n_rr + p_rr * v_dot_n_ll) + + # chain rule from Terashima + drho_e_drho_p_ll = drho_e_drho_at_const_p(V_ll, T_ll, eos) + drho_e_drho_p_rr = drho_e_drho_at_const_p(V_rr, T_rr, eos) + rho_e_avg_corrected = (rho_e_avg - + 0.25f0 * (drho_e_drho_p_rr - drho_e_drho_p_ll) * + (rho_rr - rho_ll)) + + # calculate internal energy (with APEC correction) and kinetic energy + # contributions separately in energy equation + ke_ll = 0.5f0 * (v1_ll^2 + v2_ll^2) + ke_rr = 0.5f0 * (v1_rr^2 + v2_rr^2) + + rho_v_dot_n_ll = rho_ll * v_dot_n_ll + rho_v_dot_n_rr = rho_rr * v_dot_n_rr + f_rho = 0.5f0 * (rho_v_dot_n_ll + rho_v_dot_n_rr) + f_rho_v1 = 0.5f0 * (rho_v_dot_n_ll * v1_ll + rho_v_dot_n_rr * v1_rr) + p_avg * normal_direction[1] + f_rho_v2 = 0.5f0 * (rho_v_dot_n_ll * v2_ll + rho_v_dot_n_rr * v2_rr) + p_avg * normal_direction[2] + f_rho_E = rho_e_avg_corrected * v_dot_n_avg + + 0.5f0 * (rho_v_dot_n_ll * ke_ll + rho_v_dot_n_rr * ke_rr) + + 0.5f0 * (p_ll * v_dot_n_ll + p_rr * v_dot_n_rr) + return SVector(f_rho, f_rho_v1, f_rho_v2, f_rho_E) +end + + # Calculate estimates for minimum and maximum wave speeds for HLL-type fluxes @inline function min_max_speed_naive(u_ll, u_rr, orientation::Integer, equations::NonIdealCompressibleEulerEquations2D) @@ -409,6 +459,32 @@ end return max(v_mag_ll + c_ll, v_mag_rr + c_rr) end +@inline function max_abs_speed(u_ll, u_rr, normal_direction::AbstractVector, + equations::NonIdealCompressibleEulerEquations2D) + V_ll, v1_ll, v2_ll, T_ll = cons2prim(u_ll, equations) + V_rr, v1_rr, v2_rr, T_rr = cons2prim(u_rr, equations) + + # Calculate normal velocities and sound speeds + # left + v_ll = (v1_ll * normal_direction[1] + + + v2_ll * normal_direction[2]) + + # right + v_rr = (v1_rr * normal_direction[1] + + + v2_rr * normal_direction[2]) + + # Calculate primitive variables and speed of sound + eos = equations.equation_of_state + c_ll = speed_of_sound(V_ll, T_ll, eos) + c_rr = speed_of_sound(V_rr, T_rr, eos) + + norm_ = norm(normal_direction) + return max(abs(v_ll) + c_ll * norm_, + abs(v_rr) + c_rr * norm_) +end + @inline function max_abs_speed_naive(u_ll, u_rr, orientation::Integer, equations::NonIdealCompressibleEulerEquations2D) V_ll, v1_ll, v2_ll, T_ll = cons2prim(u_ll, equations) @@ -434,6 +510,26 @@ end return max(v_mag_ll, v_mag_rr) + max(c_ll, c_rr) end +@inline function max_abs_speed_naive(u_ll, u_rr, normal_direction::AbstractVector, + equations::NonIdealCompressibleEulerEquations2D) + V_ll, v1_ll, v2_ll, T_ll = cons2prim(u_ll, equations) + V_rr, v1_rr, v2_rr, T_rr = cons2prim(u_rr, equations) + + # Get the velocity value in the appropriate direction + v_dot_n_ll = v1_ll * normal_direction[1] + v2_ll * normal_direction[2] + v_dot_n_rr = v1_rr * normal_direction[1] + v2_rr * normal_direction[2] + + v_mag_ll = abs(v_dot_n_ll) + v_mag_rr = abs(v_dot_n_rr) + + # Calculate primitive variables and speed of sound + eos = equations.equation_of_state + c_ll = speed_of_sound(V_ll, T_ll, eos) + c_rr = speed_of_sound(V_rr, T_rr, eos) + + return max(v_mag_ll, v_mag_rr) + max(c_ll, c_rr) +end + # More refined estimates for minimum and maximum wave speeds for HLL-type fluxes @inline function min_max_speed_davis(u_ll, u_rr, orientation::Integer, equations::NonIdealCompressibleEulerEquations2D) From e41f336b8748660308204dd081208cff0bad2536 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Sat, 24 Jan 2026 21:35:10 -0600 Subject: [PATCH 089/360] remove 2D nonideal density wave definition from elixir --- .../elixir_euler_nonideal_density_wave.jl | 34 +------------------ 1 file changed, 1 insertion(+), 33 deletions(-) diff --git a/examples/tree_2d_dgsem/elixir_euler_nonideal_density_wave.jl b/examples/tree_2d_dgsem/elixir_euler_nonideal_density_wave.jl index c930a7a8f8e..8f7454c60c0 100644 --- a/examples/tree_2d_dgsem/elixir_euler_nonideal_density_wave.jl +++ b/examples/tree_2d_dgsem/elixir_euler_nonideal_density_wave.jl @@ -8,41 +8,9 @@ using Trixi: ForwardDiff eos = VanDerWaals(; a = 10, b = 1e-2, gamma = 1.4, R = 287) equations = NonIdealCompressibleEulerEquations2D(eos) -# the default amplitude and frequency k are chosen to be consistent with -# initial_condition_density_wave for CompressibleEulerEquations1D -function initial_condition_density_wave(x, t, - equations::NonIdealCompressibleEulerEquations2D; - amplitude = 0.98, k = 2) - RealT = eltype(x) - v1 = convert(RealT, 0.1) - v2 = convert(RealT, 0.2) - rho = 1 + convert(RealT, amplitude) * sinpi(k * (x[1] + x[2] - t * (v1 + v2))) - p = 20 - - V = inv(rho) - - # invert for temperature given p, V - T = 1 - tol = 100 * eps(RealT) - dp = pressure(V, T, eos) - p - iter = 1 - while abs(dp) / abs(p) > tol && iter < 100 - dp = pressure(V, T, eos) - p - dpdT_V = ForwardDiff.derivative(T -> pressure(V, T, eos), T) - T = max(tol, T - dp / dpdT_V) - iter += 1 - end - if iter == 100 - println("Warning: solver for temperature(V, p) did not converge") - end - - return prim2cons(SVector(V, v1, v2, T), equations) -end - initial_condition = initial_condition_density_wave -volume_flux = flux_central_terashima_etal -# volume_flux = flux_terashima_etal +volume_flux = flux_terashima_etal volume_integral = VolumeIntegralFluxDifferencing(volume_flux) solver = DGSEM(polydeg = 3, volume_integral = volume_integral, surface_flux = flux_lax_friedrichs) From 62748143163cfb25ddeff4838dd5775486e165f4 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Sat, 24 Jan 2026 21:35:18 -0600 Subject: [PATCH 090/360] draft of transcritical mixing layer --- ...lixir_euler_nonideal_transcritical_wave.jl | 89 -------------- ...uler_peng_robinson_transcritical_mixing.jl | 114 ++++++++++++++++++ 2 files changed, 114 insertions(+), 89 deletions(-) delete mode 100644 examples/tree_1d_dgsem/elixir_euler_nonideal_transcritical_wave.jl create mode 100644 examples/tree_2d_dgsem/elixir_euler_peng_robinson_transcritical_mixing.jl diff --git a/examples/tree_1d_dgsem/elixir_euler_nonideal_transcritical_wave.jl b/examples/tree_1d_dgsem/elixir_euler_nonideal_transcritical_wave.jl deleted file mode 100644 index 2bfa01dc9dc..00000000000 --- a/examples/tree_1d_dgsem/elixir_euler_nonideal_transcritical_wave.jl +++ /dev/null @@ -1,89 +0,0 @@ -using OrdinaryDiffEqLowStorageRK -using Trixi -using Trixi: ForwardDiff - -############################################################################### -# semidiscretization of the compressible Euler equations - -eos = PengRobinson() -equations = NonIdealCompressibleEulerEquations1D(eos) - -# the smooth transcritical wave taken from "An entropy-stable hybrid scheme for simulations of -# transcritical real-fluid flows" by Ma, Ihme (2017) -# -function initial_condition_transcritical_wave(x, t, - equations::NonIdealCompressibleEulerEquations1D{<:PengRobinson}) - RealT = eltype(x) - - rho_min, rho_max = 56.9, 793.1 - v1 = 100 - rho = 0.5 * (rho_min + rho_max) + - 0.5 * (rho_max - rho_min) * sin(2 * pi * (x[1] - v1 * t)) - p = 5e6 - - V = inv(rho) - - # invert for temperature given p, V - T = eos.T0 - tol = 100 * eps(RealT) - dp = pressure(V, T, eos) - p - iter = 1 - while abs(dp) / abs(p) > tol && iter < 100 - dp = pressure(V, T, eos) - p - dpdT_V = ForwardDiff.derivative(T -> pressure(V, T, eos), T) - T = max(tol, T - dp / dpdT_V) - iter += 1 - end - if iter == 100 - println("Warning: solver for temperature(V, p) did not converge") - end - - return prim2cons(SVector(V, v1, T), equations) -end - -initial_condition = initial_condition_transcritical_wave - -volume_flux = flux_terashima_etal -volume_integral = VolumeIntegralFluxDifferencing(volume_flux) -solver = DGSEM(polydeg = 3, volume_integral = volume_integral, - surface_flux = flux_lax_friedrichs) - -coordinates_min = -0.5 -coordinates_max = 0.5 -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 6, - n_cells_max = 30_000) - -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) - -############################################################################### -# ODE solvers, callbacks etc. - -tspan = (0.0, 0.01) -ode = semidiscretize(semi, tspan) - -summary_callback = SummaryCallback() - -analysis_interval = 2000 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval) - -alive_callback = AliveCallback(analysis_interval = analysis_interval) - -save_solution = SaveSolutionCallback(interval = 100, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) - -stepsize_callback = StepsizeCallback(cfl = 0.5) - -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution, - stepsize_callback) - -############################################################################### -# run the simulation - -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false); - dt = stepsize_callback(ode), # solve needs some value here but it will be overwritten by the stepsize_callback - ode_default_options()..., callback = callbacks); diff --git a/examples/tree_2d_dgsem/elixir_euler_peng_robinson_transcritical_mixing.jl b/examples/tree_2d_dgsem/elixir_euler_peng_robinson_transcritical_mixing.jl new file mode 100644 index 00000000000..cdab2e65671 --- /dev/null +++ b/examples/tree_2d_dgsem/elixir_euler_peng_robinson_transcritical_mixing.jl @@ -0,0 +1,114 @@ +using OrdinaryDiffEqLowStorageRK +using OrdinaryDiffEqSSPRK +using Trixi +using Trixi: ForwardDiff + +############################################################################### +# semidiscretization of the compressible Euler equations + +eos = PengRobinson() +equations = NonIdealCompressibleEulerEquations2D(eos) + +# A transcritical mixing layer adapted from "Kinetic-energy- and pressure-equilibrium-preserving +# schemes for real-gas turbulence in the transcritical regime" by Bernades, Jofre, Capuano (2023). +# +function initial_condition_transcritical_mixing(x, t, + equations::NonIdealCompressibleEulerEquations2D) + eos = equations.equation_of_state + + RealT = eltype(x) + + x, y = x + + pc = 3.4e6 # critical pressure for N2 + p = 2 * pc + + # from Bernades et al + epsilon, delta, A = 1.0, 1 / 20, 3 / 8 + u0 = 25 # m/s + Tc = eos.T0 # this value is 126.2 for N2 + T = Tc * (3 * A - A * tanh(y / delta)) + + # B = (y > 0) ? 3/8 : -3/8 + # epsilon = 0.1 + # delta = 1 / 15 + # u0 = 20 # m/s + # T0 = 110 # K + # T = T0 * (1 + B * tanh(y / delta)) # from Coppola + + tol = Trixi.eos_newton_tol(eos) + + # invert for V given p, T. Initialize V so that the denominator + # (V - b) in Peng-Robinson is positive. + V = eos.b + tol + dp = pressure(V, T, eos) - p + iter = 1 + while abs(dp) > tol * abs(p) && iter < 100 + dp = pressure(V, T, eos) - p + dpdV_T = ForwardDiff.derivative(V -> pressure(V, T, eos), V) + V = max(eos.b + tol, V - dp / dpdV_T) + iter += 1 + end + if iter == 100 + println("Warning: solver for temperature(V, p) did not converge") + end + + k = 6 + dv = epsilon * sin(k * pi * x) * (tanh(100 * (y + 0.1)) - tanh(100 * (y - 0.1))) / 2 + v1 = u0 * (1 + 0.2 * tanh(y / delta)) + dv + v2 = dv + + return prim2cons(SVector(V, v1, v2, T), equations) +end + +initial_condition = initial_condition_transcritical_mixing + +volume_flux = flux_terashima_etal +volume_integral = VolumeIntegralFluxDifferencing(volume_flux) +solver = DGSEM(polydeg = 3, volume_integral = volume_integral, + surface_flux = flux_lax_friedrichs) + +cells_per_dimension = (32, 16) +coordinates_min = (-0.5, -0.25) +coordinates_max = (0.5, 0.25) +mesh = StructuredMesh(cells_per_dimension, + coordinates_min, + coordinates_max, + periodicity = (true, false)) + +boundary_conditions = (x_neg = boundary_condition_periodic, + x_pos = boundary_condition_periodic, + y_neg = boundary_condition_slip_wall, + y_pos = boundary_condition_slip_wall) + +semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, + boundary_conditions = boundary_conditions) + +############################################################################### +# ODE solvers, callbacks etc. + +tspan = (0, 0.033 * 2) + +ode = semidiscretize(semi, tspan) + +summary_callback = SummaryCallback() + +analysis_interval = 2000 +analysis_callback = AnalysisCallback(semi, interval = analysis_interval) + +alive_callback = AliveCallback(analysis_interval = analysis_interval) + +stepsize_callback = StepsizeCallback(cfl = 0.25) + +callbacks = CallbackSet(summary_callback, + analysis_callback, alive_callback) + +############################################################################### +# run the simulation + +solver = CarpenterKennedy2N54(williamson_condition = false) +solver = SSPRK43() +sol = solve(ode, solver; + dt = 1e-7, abstol = 1e-6, reltol = 1e-4, + # dt = stepsize_callback(ode), # solve needs some value here but it will be overwritten by the stepsize_callback + ode_default_options()..., callback = callbacks); From 84a97e7fafe67c67302de4559af34730824af95e Mon Sep 17 00:00:00 2001 From: Jesse Chan <1156048+jlchan@users.noreply.github.com> Date: Sun, 25 Jan 2026 20:51:38 -0600 Subject: [PATCH 091/360] Apply suggestions from code review Co-authored-by: Hendrik Ranocha --- src/equations/nonideal_compressible_euler_1d.jl | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/equations/nonideal_compressible_euler_1d.jl b/src/equations/nonideal_compressible_euler_1d.jl index 908de1004ea..9add0ee7971 100644 --- a/src/equations/nonideal_compressible_euler_1d.jl +++ b/src/equations/nonideal_compressible_euler_1d.jl @@ -96,7 +96,13 @@ by Terashima, Ly, Ihme (2025). rho_e_avg = 0.5f0 * (rho_e_ll + rho_e_rr) p_v1_avg = 0.5f0 * (p_ll * v1_rr + p_rr * v1_ll) - # chain rule from Terashima + # chain rule from Terashima + # Note that `drho_e_drho_p`, i.e., the derivative of the + # internal energy density with respect to the density at + # constant pressure is zero for an ideal gas EOS. Thus, + # the following mean value reduces to + # rho_e_v1_avg = rho_e_avg * v1_avg + # for an ideal gas EOS. drho_e_drho_p_ll = drho_e_drho_at_const_p(V_ll, T_ll, eos) drho_e_drho_p_rr = drho_e_drho_at_const_p(V_rr, T_rr, eos) rho_e_v1_avg = (rho_e_avg - @@ -106,6 +112,7 @@ by Terashima, Ly, Ihme (2025). # Ignore orientation since it is always "1" in 1D f_rho = rho_avg * v1_avg f_rho_v1 = rho_avg * v1_avg * v1_avg + p_avg + # Note that the additional "average" is a product and not v1_avg f_rho_E = rho_e_v1_avg + rho_avg * 0.5f0 * (v1_ll * v1_rr) * v1_avg + p_v1_avg return SVector(f_rho, f_rho_v1, f_rho_E) @@ -138,7 +145,13 @@ by Terashima, Ly, Ihme (2025). p_avg = 0.5f0 * (p_ll + p_rr) rho_e_avg = 0.5f0 * (rho_e_ll + rho_e_rr) - # chain rule from Terashima + # chain rule from Terashima + # Note that `drho_e_drho_p`, i.e., the derivative of the + # internal energy density with respect to the density at + # constant pressure is zero for an ideal gas EOS. Thus, + # the following mean value reduces to + # rho_e_v1_avg = rho_e_avg * v1_avg + # for an ideal gas EOS. drho_e_drho_p_ll = drho_e_drho_at_const_p(V_ll, T_ll, eos) drho_e_drho_p_rr = drho_e_drho_at_const_p(V_rr, T_rr, eos) rho_e_v1_avg = (rho_e_avg - From 8bc02b1dd984c13213492095830ae128e7602e09 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Mon, 26 Jan 2026 11:09:30 -0600 Subject: [PATCH 092/360] fix PR initial conditions --- .../nonideal_compressible_euler_1d.jl | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/src/equations/nonideal_compressible_euler_1d.jl b/src/equations/nonideal_compressible_euler_1d.jl index e59825431d1..c75441f37b5 100644 --- a/src/equations/nonideal_compressible_euler_1d.jl +++ b/src/equations/nonideal_compressible_euler_1d.jl @@ -398,6 +398,7 @@ end function initial_condition_transcritical_wave(x, t, equations::NonIdealCompressibleEulerEquations1D{<:PengRobinson}) RealT = eltype(x) + eos = equations.equation_of_state rho_min, rho_max = 56.9, 793.1 v1 = 100 @@ -424,4 +425,37 @@ function initial_condition_transcritical_wave(x, t, return prim2cons(SVector(V, v1, T), equations) end + +# the Peng-Robinson N2 transcritical shock taken from "An entropy-stable hybrid scheme +# for simulations of transcritical real-fluid flows" by Ma, Ihme (2017). +# +function initial_condition_transcritical_shock(x, t, + equations::NonIdealCompressibleEulerEquations1D{<:PengRobinson}) + RealT = eltype(x) + + if x[1] < 0 + rho, v1, p = SVector(800, 0, 60e6) + else + rho, v1, p = SVector(80, 0, 6e6) + end + + V = inv(rho) + + # invert for temperature given p, V + T = eos.T0 + tol = 100 * eps(RealT) + dp = pressure(V, T, eos) - p + iter = 1 + while abs(dp) / abs(p) > tol && iter < 100 + dp = pressure(V, T, eos) - p + dpdT_V = ForwardDiff.derivative(T -> pressure(V, T, eos), T) + T = max(tol, T - dp / dpdT_V) + iter += 1 + end + if iter == 100 + println("Warning: solver for temperature(V, p) did not converge") + end + + return prim2cons(SVector(V, v1, T), equations) +end end # @muladd From 4078b5aefe7319709262c1553af476c69f47a33e Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Mon, 26 Jan 2026 11:32:59 -0600 Subject: [PATCH 093/360] adding PR transcritical density wave (crashes for EPEC) --- .../elixir_euler_nonideal_flux_epec.jl | 116 ++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 examples/tree_1d_dgsem/elixir_euler_nonideal_flux_epec.jl diff --git a/examples/tree_1d_dgsem/elixir_euler_nonideal_flux_epec.jl b/examples/tree_1d_dgsem/elixir_euler_nonideal_flux_epec.jl new file mode 100644 index 00000000000..7825d9dcc6c --- /dev/null +++ b/examples/tree_1d_dgsem/elixir_euler_nonideal_flux_epec.jl @@ -0,0 +1,116 @@ +using OrdinaryDiffEqLowStorageRK +using Trixi +using Trixi: ForwardDiff + +############################################################################### +# semidiscretization of the compressible Euler equations + +eos = PengRobinson() +equations = NonIdealCompressibleEulerEquations1D(eos) + +@inline function drho_e_dp_at_const_rho(V, T, eos::Trixi.AbstractEquationOfState) + rho = inv(V) + dpdT_V, _dpdV_T = Trixi.calc_pressure_derivatives(V, T, eos) + c_v = Trixi.heat_capacity_constant_volume(V, T, eos) + + # (∂(ρe)/∂p)|ρ = ρ c_v / (∂p/∂T)|V + return (rho * c_v) / dpdT_V +end + +@inline function flux_epec(u_ll, u_rr, orientation::Int, + equations::NonIdealCompressibleEulerEquations1D) + eos = equations.equation_of_state + V_ll, v1_ll, T_ll = cons2prim(u_ll, equations) + V_rr, v1_rr, T_rr = cons2prim(u_rr, equations) + + rho_ll = u_ll[1] + rho_rr = u_rr[1] + rho_e_ll = Trixi.internal_energy_density(u_ll, equations) + rho_e_rr = Trixi.internal_energy_density(u_rr, equations) + p_ll = pressure(V_ll, T_ll, eos) + p_rr = pressure(V_rr, T_rr, eos) + + rho_avg = 0.5f0 * (rho_ll + rho_rr) + v1_avg = 0.5f0 * (v1_ll + v1_rr) + p_avg = 0.5f0 * (p_ll + p_rr) + rho_e_avg = 0.5f0 * (rho_e_ll + rho_e_rr) + p_v1_avg = 0.5f0 * (p_ll * v1_rr + p_rr * v1_ll) + + # chain rule from Terashima + drho_e_drho_p_ll = Trixi.drho_e_drho_at_const_p(V_ll, T_ll, eos) + drho_e_drho_p_rr = Trixi.drho_e_drho_at_const_p(V_rr, T_rr, eos) + drho_e_drho_p_avg = 0.5f0 * (drho_e_drho_p_ll + drho_e_drho_p_rr) + drho_e_drho_p_rho_avg = 0.5f0 * (drho_e_drho_p_ll * rho_ll + drho_e_drho_p_rr * rho_rr) + + # @variables aL bL aR bR + # drho_e_drho_p_avg * rho_avg - drho_e_drho_p_rho_avg + # {a}{b} - {ab} = (aL + aR)(bL + bR) - (aL*bL + aR * bR) + # (aL*bL + aL * bR + aR * bL + a + + rho_e_jump = rho_e_rr - rho_e_ll + rho_jump = rho_rr - rho_ll + p_jump = p_rr - p_ll + + drho_e_dp_at_const_rho_ll = drho_e_dp_at_const_rho(V_ll, T_ll, eos) + drho_e_dp_at_const_rho_rr = drho_e_dp_at_const_rho(V_rr, T_rr, eos) + drho_e_dp_at_const_rho_avg = 0.5f0 * (drho_e_dp_at_const_rho_ll + + drho_e_dp_at_const_rho_rr) + num = (rho_e_jump - drho_e_drho_p_avg * rho_jump - drho_e_dp_at_const_rho_avg * p_jump) + den = drho_e_drho_p_rr - drho_e_drho_p_ll + rho_avg = rho_avg - num * den / (den^2 + eps(typeof(den))) + rho_e_avg = (rho_e_avg + drho_e_drho_p_avg * rho_avg - drho_e_drho_p_rho_avg) + + # Ignore orientation since it is always "1" in 1D + f_rho = rho_avg * v1_avg + f_rho_v1 = rho_avg * v1_avg * v1_avg + p_avg + f_rho_E = rho_e_avg * v1_avg + rho_avg * 0.5f0 * (v1_ll * v1_rr) * v1_avg + p_v1_avg + + return SVector(f_rho, f_rho_v1, f_rho_E) +end + +initial_condition = Trixi.initial_condition_transcritical_wave + +volume_flux = flux_epec +volume_flux = flux_terashima_etal +solver = DGSEM(polydeg = 3, volume_integral = VolumeIntegralFluxDifferencing(volume_flux), + surface_flux = flux_lax_friedrichs) + +coordinates_min = -0.5 +coordinates_max = 0.5 +mesh = TreeMesh(coordinates_min, coordinates_max, + initial_refinement_level = 3, + n_cells_max = 30_000) + +semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) + +############################################################################### +# ODE solvers, callbacks etc. + +tspan = (0.0, 5e-4) +ode = semidiscretize(semi, tspan) + +summary_callback = SummaryCallback() + +analysis_interval = 2000 +analysis_callback = AnalysisCallback(semi, interval = analysis_interval) + +alive_callback = AliveCallback(analysis_interval = analysis_interval) + +save_solution = SaveSolutionCallback(interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim) + +stepsize_callback = StepsizeCallback(cfl = 0.5) + +callbacks = CallbackSet(summary_callback, + analysis_callback, alive_callback, + save_solution, + stepsize_callback) + +############################################################################### +# run the simulation + +sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false); + dt = stepsize_callback(ode), # solve needs some value here but it will be overwritten by the stepsize_callback + ode_default_options()..., callback = callbacks); From c5bc20298f09d1aad5e6609aff38770cb75de7fd Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Mon, 26 Jan 2026 12:25:13 -0600 Subject: [PATCH 094/360] add transcritical shock elixir --- ...ixir_euler_nonideal_transcritical_shock.jl | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 examples/tree_1d_dgsem/elixir_euler_nonideal_transcritical_shock.jl diff --git a/examples/tree_1d_dgsem/elixir_euler_nonideal_transcritical_shock.jl b/examples/tree_1d_dgsem/elixir_euler_nonideal_transcritical_shock.jl new file mode 100644 index 00000000000..3fe25a26589 --- /dev/null +++ b/examples/tree_1d_dgsem/elixir_euler_nonideal_transcritical_shock.jl @@ -0,0 +1,59 @@ +using OrdinaryDiffEqLowStorageRK +using Trixi + +############################################################################### +# semidiscretization of the compressible Euler equations + +eos = PengRobinson() +equations = NonIdealCompressibleEulerEquations1D(eos) + +initial_condition = initial_condition_transcritical_shock + +volume_flux = flux_central_terashima_etal +solver = DGSEM(polydeg = 3, volume_integral = VolumeIntegralFluxDifferencing(volume_flux), + surface_flux = flux_lax_friedrichs) + +coordinates_min = -0.5 +coordinates_max = 0.5 +mesh = TreeMesh(coordinates_min, coordinates_max, + initial_refinement_level = 6, + n_cells_max = 30_000, + periodicity = false) + +boundary_conditions = (x_neg = BoundaryConditionDirichlet(initial_condition), + x_pos = BoundaryConditionDirichlet(initial_condition)) + +semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, + boundary_conditions = boundary_conditions) + +############################################################################### +# ODE solvers, callbacks etc. + +tspan = (0.0, 5.0e-4) +ode = semidiscretize(semi, tspan) + +summary_callback = SummaryCallback() + +analysis_interval = 2000 +analysis_callback = AnalysisCallback(semi, interval = analysis_interval) + +alive_callback = AliveCallback(analysis_interval = analysis_interval) + +save_solution = SaveSolutionCallback(interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim) + +stepsize_callback = StepsizeCallback(cfl = 0.25) + +callbacks = CallbackSet(summary_callback, + analysis_callback, alive_callback, + save_solution, + stepsize_callback) + +############################################################################### +# run the simulation + +sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false); + dt = stepsize_callback(ode), # solve needs some value here but it will be overwritten by the stepsize_callback + ode_default_options()..., callback = callbacks); From 3d1161c23f6f2a7b2a4ea07f556e654597118380 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Mon, 26 Jan 2026 12:26:12 -0600 Subject: [PATCH 095/360] initialize eos properly in initial conditions --- src/equations/nonideal_compressible_euler_1d.jl | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/equations/nonideal_compressible_euler_1d.jl b/src/equations/nonideal_compressible_euler_1d.jl index 709317caf36..bac0d5d91c3 100644 --- a/src/equations/nonideal_compressible_euler_1d.jl +++ b/src/equations/nonideal_compressible_euler_1d.jl @@ -381,6 +381,8 @@ function initial_condition_density_wave(x, t, equations::NonIdealCompressibleEulerEquations1D; amplitude = 0.98, k = 2) RealT = eltype(x) + eos = equations.equation_of_state + v1 = convert(RealT, 0.1) rho = 1 + convert(RealT, amplitude) * sinpi(k * (x[1] - v1 * t)) p = 20 @@ -443,15 +445,16 @@ end # for simulations of transcritical real-fluid flows" by Ma, Ihme (2017). # function initial_condition_transcritical_shock(x, t, - equations::NonIdealCompressibleEulerEquations1D{<:PengRobinson}) + equations::NonIdealCompressibleEulerEquations1D{<:PengRobinson}) RealT = eltype(x) + eos = equations.equation_of_state if x[1] < 0 rho, v1, p = SVector(800, 0, 60e6) else - rho, v1, p = SVector(80, 0, 6e6) + rho, v1, p = SVector(80, 0, 6e6) end - + V = inv(rho) # invert for temperature given p, V From c7cbb05a0c3b6ccadbde93df8478eaaed527a5b1 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Mon, 26 Jan 2026 12:26:27 -0600 Subject: [PATCH 096/360] add transcritical tests --- test/test_tree_1d_euler.jl | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/test/test_tree_1d_euler.jl b/test/test_tree_1d_euler.jl index b980f7c543d..c475ceab92c 100644 --- a/test/test_tree_1d_euler.jl +++ b/test/test_tree_1d_euler.jl @@ -620,13 +620,13 @@ end @test_allocations(Trixi.rhs!, semi, sol, 1000) end -@trixi_testset "elixir_euler_nonideal_transcritical_wave.jl with Peng Robinson" begin +@trixi_testset "elixir_euler_nonideal_density_wave.jl (transcritical wave) with Peng Robinson" begin @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_nonideal_density_wave.jl"), solver=DGSEM(polydeg = 3, volume_integral = VolumeIntegralFluxDifferencing(flux_terashima_etal), surface_flux = flux_lax_friedrichs), - initial_condition = initial_condition_transcritical_wave, + initial_condition=initial_condition_transcritical_wave, tspan=(0.0, 0.001), # note that rho_e_total errors are large because pressure is 5e6 l2=[2.5190796911050598e-5, 0.0013782564599067785, 7.045132422388037], @@ -641,4 +641,22 @@ end @test_allocations(Trixi.rhs!, semi, sol, 1000) end +@trixi_testset "elixir_euler_nonideal_transcritical_shock.jl with Peng Robinson" begin + @test_trixi_include(joinpath(EXAMPLES_DIR, + "elixir_euler_nonideal_transcritical_shock.jl"), + initial_condition=initial_condition_transcritical_wave, + tspan=(0.0, 5e-5), + # note that rho_e_total errors are large because pressure is 5e6 + l2=[3.205713931725666e-5, 0.0024557000949388375, 7.814075349831272], + linf=[ + 0.00015527262569037248, + 0.006962752362596802, + 25.366882726550102 + ]) + + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + @test_allocations(Trixi.rhs!, semi, sol, 1000) +end + end # module From 2d82c539c4f09de794c8d92d55176d0c89e6b998 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Mon, 26 Jan 2026 12:26:45 -0600 Subject: [PATCH 097/360] export transcritical shock initial condition --- src/Trixi.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Trixi.jl b/src/Trixi.jl index 6bdf1ab5f85..492d61f309e 100644 --- a/src/Trixi.jl +++ b/src/Trixi.jl @@ -184,6 +184,7 @@ export AcousticPerturbationEquations2D, export NonIdealCompressibleEulerEquations1D, NonIdealCompressibleEulerEquations2D export IdealGas, VanDerWaals, PengRobinson +export initial_condition_transcritical_shock export LaplaceDiffusion1D, LaplaceDiffusion2D, LaplaceDiffusion3D, LaplaceDiffusionEntropyVariables1D, LaplaceDiffusionEntropyVariables2D, From 71795714e445c7a5efab50fd3e1120da3b5fee21 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Mon, 26 Jan 2026 12:49:26 -0600 Subject: [PATCH 098/360] add 2D tests --- test/test_unit.jl | 54 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/test/test_unit.jl b/test/test_unit.jl index 41734a10831..80f1a18cbe7 100644 --- a/test/test_unit.jl +++ b/test/test_unit.jl @@ -816,6 +816,60 @@ end Tuple{Any, Any, Trixi.AbstractEquationOfState}, V, T, eos)[2] end +@timed_testset "Test consistency (fluxes, entropy/cons2entropy) for NonIdealCompressibleEulerEquations1D" begin + eos = VanDerWaals(; a = 10, b = 0.01, R = 287, gamma = 1.4) + equations = NonIdealCompressibleEulerEquations2D(eos) + q = SVector(2.0, 0.1, 0.2, 10.0) + V, v1, v2, T = q + u = prim2cons(q, equations) + + @test density(u, equations) ≈ 0.5 + @test velocity(u, equations) ≈ SVector(0.1, 0.2) + @test density_pressure(u, equations) ≈ u[1] * pressure(V, T, eos) + @test energy_internal(u, equations) ≈ energy_internal(V, T, eos) + + @test ForwardDiff.gradient(u -> entropy(u, equations), u) ≈ + cons2entropy(u, equations) + for orientation in (1, 2) + @test flux_lax_friedrichs(u, u, orientation, equations) ≈ flux(u, orientation, equations) + @test flux_hll(u, u, orientation, equations) ≈ flux(u, orientation, equations) + + @test flux_terashima_etal(u, u, orientation, equations) ≈ flux(u, orientation, equations) + @test flux_central_terashima_etal(u, u, orientation, equations) ≈ flux(u, orientation, equations) + end + + normal_direction = SVector(1, 2) / norm(SVector(1, 2)) + @test flux(u, normal_direction, equations) ≈ flux(u, 1, equations) * normal_direction[1] + + flux(u, 2, equations) * normal_direction[2] + + u_ll = u + u_rr = prim2cons(SVector(2.5, 0.2, 0.1, 8.0), equations) + @test flux_terashima_etal(u_ll, u_rr, normal_direction, equations) ≈ + flux_terashima_etal(u_ll, u_rr, 1, equations) * normal_direction[1] + + flux_terashima_etal(u_ll, u_rr, 2, equations) * normal_direction[2] + @test flux_central_terashima_etal(u_ll, u_rr, normal_direction, equations) ≈ + flux_central_terashima_etal(u_ll, u_rr, 1, equations) * normal_direction[1] + + flux_central_terashima_etal(u_ll, u_rr, 2, equations) * normal_direction[2] + + @test flux_lax_friedrichs(u_ll, u_rr, 1, equations) ≈ flux_lax_friedrichs(u_ll, u_rr, SVector(1, 0), equations) + @test flux_lax_friedrichs(u_ll, u_rr, 2, equations) ≈ flux_lax_friedrichs(u_ll, u_rr, SVector(0, 1), equations) + + # check that the fallback temperature and specialized temperature + # return the same value + V, v1, v2, T = cons2prim(u, equations) + e = energy_internal(V, T, eos) + @test temperature(V, e, eos) ≈ + invoke(temperature, Tuple{Any, Any, Trixi.AbstractEquationOfState}, V, e, eos) + + # check that fallback calc_pressure_derivatives matches specialized routines + @test Trixi.calc_pressure_derivatives(V, T, eos)[1] ≈ + invoke(Trixi.calc_pressure_derivatives, + Tuple{Any, Any, Trixi.AbstractEquationOfState}, V, T, eos)[1] + @test Trixi.calc_pressure_derivatives(V, T, eos)[2] ≈ + invoke(Trixi.calc_pressure_derivatives, + Tuple{Any, Any, Trixi.AbstractEquationOfState}, V, T, eos)[2] +end + @timed_testset "StepsizeCallback" begin # Ensure a proper error is thrown if used with adaptive time integration schemes @test_trixi_include(joinpath(examples_dir(), "tree_2d_dgsem", From 59807288cad3e87240b75f1f3776b67ce88b8c7c Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Mon, 26 Jan 2026 13:13:26 -0600 Subject: [PATCH 099/360] formatting format --- .../nonideal_compressible_euler_2d.jl | 15 ++++---- test/test_unit.jl | 34 +++++++++++-------- 2 files changed, 28 insertions(+), 21 deletions(-) diff --git a/src/equations/nonideal_compressible_euler_2d.jl b/src/equations/nonideal_compressible_euler_2d.jl index faff70fe41c..0f954f30da5 100644 --- a/src/equations/nonideal_compressible_euler_2d.jl +++ b/src/equations/nonideal_compressible_euler_2d.jl @@ -382,17 +382,18 @@ function flux_central_terashima_etal(u_ll, u_rr, normal_direction::AbstractVecto ke_rr = 0.5f0 * (v1_rr^2 + v2_rr^2) rho_v_dot_n_ll = rho_ll * v_dot_n_ll - rho_v_dot_n_rr = rho_rr * v_dot_n_rr + rho_v_dot_n_rr = rho_rr * v_dot_n_rr f_rho = 0.5f0 * (rho_v_dot_n_ll + rho_v_dot_n_rr) - f_rho_v1 = 0.5f0 * (rho_v_dot_n_ll * v1_ll + rho_v_dot_n_rr * v1_rr) + p_avg * normal_direction[1] - f_rho_v2 = 0.5f0 * (rho_v_dot_n_ll * v2_ll + rho_v_dot_n_rr * v2_rr) + p_avg * normal_direction[2] + f_rho_v1 = 0.5f0 * (rho_v_dot_n_ll * v1_ll + rho_v_dot_n_rr * v1_rr) + + p_avg * normal_direction[1] + f_rho_v2 = 0.5f0 * (rho_v_dot_n_ll * v2_ll + rho_v_dot_n_rr * v2_rr) + + p_avg * normal_direction[2] f_rho_E = rho_e_avg_corrected * v_dot_n_avg + - 0.5f0 * (rho_v_dot_n_ll * ke_ll + rho_v_dot_n_rr * ke_rr) + - 0.5f0 * (p_ll * v_dot_n_ll + p_rr * v_dot_n_rr) + 0.5f0 * (rho_v_dot_n_ll * ke_ll + rho_v_dot_n_rr * ke_rr) + + 0.5f0 * (p_ll * v_dot_n_ll + p_rr * v_dot_n_rr) return SVector(f_rho, f_rho_v1, f_rho_v2, f_rho_E) end - # Calculate estimates for minimum and maximum wave speeds for HLL-type fluxes @inline function min_max_speed_naive(u_ll, u_rr, orientation::Integer, equations::NonIdealCompressibleEulerEquations2D) @@ -479,7 +480,7 @@ end eos = equations.equation_of_state c_ll = speed_of_sound(V_ll, T_ll, eos) c_rr = speed_of_sound(V_rr, T_rr, eos) - + norm_ = norm(normal_direction) return max(abs(v_ll) + c_ll * norm_, abs(v_rr) + c_rr * norm_) diff --git a/test/test_unit.jl b/test/test_unit.jl index 80f1a18cbe7..749b9a9c9d3 100644 --- a/test/test_unit.jl +++ b/test/test_unit.jl @@ -831,28 +831,34 @@ end @test ForwardDiff.gradient(u -> entropy(u, equations), u) ≈ cons2entropy(u, equations) for orientation in (1, 2) - @test flux_lax_friedrichs(u, u, orientation, equations) ≈ flux(u, orientation, equations) + @test flux_lax_friedrichs(u, u, orientation, equations) ≈ + flux(u, orientation, equations) @test flux_hll(u, u, orientation, equations) ≈ flux(u, orientation, equations) - @test flux_terashima_etal(u, u, orientation, equations) ≈ flux(u, orientation, equations) - @test flux_central_terashima_etal(u, u, orientation, equations) ≈ flux(u, orientation, equations) + @test flux_terashima_etal(u, u, orientation, equations) ≈ + flux(u, orientation, equations) + @test flux_central_terashima_etal(u, u, orientation, equations) ≈ + flux(u, orientation, equations) end normal_direction = SVector(1, 2) / norm(SVector(1, 2)) - @test flux(u, normal_direction, equations) ≈ flux(u, 1, equations) * normal_direction[1] + - flux(u, 2, equations) * normal_direction[2] + @test flux(u, normal_direction, equations) ≈ + flux(u, 1, equations) * normal_direction[1] + + flux(u, 2, equations) * normal_direction[2] u_ll = u u_rr = prim2cons(SVector(2.5, 0.2, 0.1, 8.0), equations) - @test flux_terashima_etal(u_ll, u_rr, normal_direction, equations) ≈ - flux_terashima_etal(u_ll, u_rr, 1, equations) * normal_direction[1] + - flux_terashima_etal(u_ll, u_rr, 2, equations) * normal_direction[2] - @test flux_central_terashima_etal(u_ll, u_rr, normal_direction, equations) ≈ - flux_central_terashima_etal(u_ll, u_rr, 1, equations) * normal_direction[1] + - flux_central_terashima_etal(u_ll, u_rr, 2, equations) * normal_direction[2] - - @test flux_lax_friedrichs(u_ll, u_rr, 1, equations) ≈ flux_lax_friedrichs(u_ll, u_rr, SVector(1, 0), equations) - @test flux_lax_friedrichs(u_ll, u_rr, 2, equations) ≈ flux_lax_friedrichs(u_ll, u_rr, SVector(0, 1), equations) + @test flux_terashima_etal(u_ll, u_rr, normal_direction, equations) ≈ + flux_terashima_etal(u_ll, u_rr, 1, equations) * normal_direction[1] + + flux_terashima_etal(u_ll, u_rr, 2, equations) * normal_direction[2] + @test flux_central_terashima_etal(u_ll, u_rr, normal_direction, equations) ≈ + flux_central_terashima_etal(u_ll, u_rr, 1, equations) * normal_direction[1] + + flux_central_terashima_etal(u_ll, u_rr, 2, equations) * normal_direction[2] + + @test flux_lax_friedrichs(u_ll, u_rr, 1, equations) ≈ + flux_lax_friedrichs(u_ll, u_rr, SVector(1, 0), equations) + @test flux_lax_friedrichs(u_ll, u_rr, 2, equations) ≈ + flux_lax_friedrichs(u_ll, u_rr, SVector(0, 1), equations) # check that the fallback temperature and specialized temperature # return the same value From c1ab7e62a08b0a8e8defcdf3767ea3d9590d92c2 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Mon, 26 Jan 2026 13:14:19 -0600 Subject: [PATCH 100/360] add StructuredMesh boundary condition --- .../nonideal_compressible_euler_2d.jl | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/equations/nonideal_compressible_euler_2d.jl b/src/equations/nonideal_compressible_euler_2d.jl index 0f954f30da5..391e93ada83 100644 --- a/src/equations/nonideal_compressible_euler_2d.jl +++ b/src/equations/nonideal_compressible_euler_2d.jl @@ -192,6 +192,31 @@ Should be used together with [`UnstructuredMesh2D`](@ref), [`P4estMesh`](@ref), 0) end +""" + boundary_condition_slip_wall(u_inner, normal_direction, direction, x, t, + surface_flux_function, equations::CompressibleEulerEquations2D) + +Should be used together with [`StructuredMesh`](@ref). +""" +@inline function boundary_condition_slip_wall(u_inner, normal_direction::AbstractVector, + direction, x, t, + surface_flux_function, + equations::NonIdealCompressibleEulerEquations2D) + # flip sign of normal to make it outward pointing, then flip the sign of the normal flux back + # to be inward pointing on the -x and -y sides due to the orientation convention used by StructuredMesh + if isodd(direction) + boundary_flux = -boundary_condition_slip_wall(u_inner, -normal_direction, + x, t, surface_flux_function, + equations) + else + boundary_flux = boundary_condition_slip_wall(u_inner, normal_direction, + x, t, surface_flux_function, + equations) + end + + return boundary_flux +end + """ flux_terashima_etal(u_ll, u_rr, orientation::Int, equations::NonIdealCompressibleEulerEquations1D) From fd5922393ddcdc4a6a2d2defd71fb6f52613ba86 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Mon, 26 Jan 2026 13:15:00 -0600 Subject: [PATCH 101/360] add transcritical mixing elixir --- ..._euler_peng_robinson_transcritical_mixing.jl | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/examples/tree_2d_dgsem/elixir_euler_peng_robinson_transcritical_mixing.jl b/examples/tree_2d_dgsem/elixir_euler_peng_robinson_transcritical_mixing.jl index cdab2e65671..1c938c8208a 100644 --- a/examples/tree_2d_dgsem/elixir_euler_peng_robinson_transcritical_mixing.jl +++ b/examples/tree_2d_dgsem/elixir_euler_peng_robinson_transcritical_mixing.jl @@ -29,13 +29,6 @@ function initial_condition_transcritical_mixing(x, t, Tc = eos.T0 # this value is 126.2 for N2 T = Tc * (3 * A - A * tanh(y / delta)) - # B = (y > 0) ? 3/8 : -3/8 - # epsilon = 0.1 - # delta = 1 / 15 - # u0 = 20 # m/s - # T0 = 110 # K - # T = T0 * (1 + B * tanh(y / delta)) # from Coppola - tol = Trixi.eos_newton_tol(eos) # invert for V given p, T. Initialize V so that the denominator @@ -72,8 +65,7 @@ cells_per_dimension = (32, 16) coordinates_min = (-0.5, -0.25) coordinates_max = (0.5, 0.25) mesh = StructuredMesh(cells_per_dimension, - coordinates_min, - coordinates_max, + coordinates_min, coordinates_max, periodicity = (true, false)) boundary_conditions = (x_neg = boundary_condition_periodic, @@ -101,14 +93,13 @@ alive_callback = AliveCallback(analysis_interval = analysis_interval) stepsize_callback = StepsizeCallback(cfl = 0.25) callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback) + analysis_callback, alive_callback, + stepsize_callback) ############################################################################### # run the simulation solver = CarpenterKennedy2N54(williamson_condition = false) -solver = SSPRK43() sol = solve(ode, solver; - dt = 1e-7, abstol = 1e-6, reltol = 1e-4, - # dt = stepsize_callback(ode), # solve needs some value here but it will be overwritten by the stepsize_callback + dt = stepsize_callback(ode), # solve needs some value here but it will be overwritten by the stepsize_callback ode_default_options()..., callback = callbacks); From 2aac986aa4303153a4aeb4af476758233a9f6f2f Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Mon, 26 Jan 2026 13:27:07 -0600 Subject: [PATCH 102/360] rename nonideal EOS energy_internal -> energy_internal_specific --- src/equations/equation_of_state_ideal_gas.jl | 4 ++-- src/equations/equation_of_state_vdw.jl | 6 +++--- src/equations/equations_of_state.jl | 14 +++++++------- src/equations/nonideal_compressible_euler_1d.jl | 10 +++++----- 4 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/equations/equation_of_state_ideal_gas.jl b/src/equations/equation_of_state_ideal_gas.jl index bc5e74de035..09efd6ae038 100644 --- a/src/equations/equation_of_state_ideal_gas.jl +++ b/src/equations/equation_of_state_ideal_gas.jl @@ -47,12 +47,12 @@ function pressure(V, T, eos::IdealGas) end """ - energy_internal(V, T, eos::IdealGas) + energy_internal_specific(V, T, eos::IdealGas) Computes internal energy for an ideal gas from specific volume `V` and temperature `T` as ``e = c_v T``. """ -function energy_internal(V, T, eos::IdealGas) +function energy_internal_specific(V, T, eos::IdealGas) (; cv) = eos e = cv * T return e diff --git a/src/equations/equation_of_state_vdw.jl b/src/equations/equation_of_state_vdw.jl index 18af6a7b220..8546a55d1ce 100644 --- a/src/equations/equation_of_state_vdw.jl +++ b/src/equations/equation_of_state_vdw.jl @@ -55,12 +55,12 @@ function pressure(V, T, eos::VanDerWaals) end """ - energy_internal(V, T, eos::VanDerWaals) + energy_internal_specific(V, T, eos::VanDerWaals) Computes internal energy for a van der Waals gas from specific volume `V` and temperature `T` as ``e = c_v T - a \rho``. """ -function energy_internal(V, T, eos::VanDerWaals) +function energy_internal_specific(V, T, eos::VanDerWaals) (; cv, a) = eos rho = inv(V) e = cv * T - a * rho @@ -83,7 +83,7 @@ end function speed_of_sound(V, T, eos::VanDerWaals) (; a, b, gamma) = eos rho = inv(V) - e = energy_internal(V, T, eos) + e = energy_internal_specific(V, T, eos) c2 = gamma * (gamma - 1) * (e + rho * a) / (1 - rho * b)^2 - 2 * a * rho return sqrt(c2) end diff --git a/src/equations/equations_of_state.jl b/src/equations/equations_of_state.jl index 3d03ada3174..caf022ace92 100644 --- a/src/equations/equations_of_state.jl +++ b/src/equations/equations_of_state.jl @@ -11,7 +11,7 @@ The interface for an `AbstractEquationOfState` requires specifying the following four functions: - `pressure(V, T, eos)` -- `energy_internal(V, T, eos)`, the specific internal energy +- `energy_internal_specific(V, T, eos)`, the specific internal energy - `entropy_specific(V, T, eos)`, the specific entropy - `speed_of_sound(V, T, eos)` @@ -36,14 +36,14 @@ include("equation_of_state_vdw.jl") function gibbs_free_energy(V, T, eos) s = entropy_specific(V, T, eos) p = pressure(V, T, eos) - e = energy_internal(V, T, eos) + e = energy_internal_specific(V, T, eos) h = e + p * V return h - T * s end # compute c_v = de/dT @inline function heat_capacity_constant_volume(V, T, eos::AbstractEquationOfState) - return ForwardDiff.derivative(T -> energy_internal(V, T, eos), T) + return ForwardDiff.derivative(T -> energy_internal_specific(V, T, eos), T) end function calc_pressure_derivatives(V, T, eos) @@ -60,16 +60,16 @@ eos_newton_tol(eos::AbstractEquationOfState) = 10 * eps() maxiter = 100) Calculates the temperature as a function of specific volume `V` and internal energy `e` -by using Newton's method to determine `T` such that `energy_internal(V, T, eos) = e`. +by using Newton's method to determine `T` such that `energy_internal_specific(V, T, eos) = e`. Note that the tolerance may need to be adjusted based on the specific equation of state. """ function temperature(V, e, eos::AbstractEquationOfState; initial_T = 1.0, tol = eos_newton_tol(eos), maxiter = 100) T = initial_T - de = energy_internal(V, T, eos) - e + de = energy_internal_specific(V, T, eos) - e iter = 1 while abs(de) > tol * abs(e) && iter < maxiter - de = energy_internal(V, T, eos) - e + de = energy_internal_specific(V, T, eos) - e # for thermodynamically admissible states, c_v = de_dT_V > 0, which should # guarantee convergence of this iteration. @@ -88,7 +88,7 @@ end # helper function used in [`flux_terashima_etal`](@ref) and [`flux_terashima_etal_central`](@ref) @inline function drho_e_drho_at_const_p(V, T, eos::AbstractEquationOfState) rho = inv(V) - e = energy_internal(V, T, eos) + e = energy_internal_specific(V, T, eos) dpdT_V, dpdV_T = calc_pressure_derivatives(V, T, eos) dpdrho_T = dpdV_T * (-V / rho) # V = inv(rho), so dVdrho = -1/rho^2 = -V^2. diff --git a/src/equations/nonideal_compressible_euler_1d.jl b/src/equations/nonideal_compressible_euler_1d.jl index 9add0ee7971..440c52aed64 100644 --- a/src/equations/nonideal_compressible_euler_1d.jl +++ b/src/equations/nonideal_compressible_euler_1d.jl @@ -34,8 +34,8 @@ by some user-specified equation of state (EOS) p = p(V, T) ``` -Similarly, the internal energy is specified by `e = energy_internal(V, T, eos)`, see -[`energy_internal(V, T, eos::IdealGas)`](@ref), [`energy_internal(V, T, eos::VanDerWaals)`](@ref). +Similarly, the internal energy is specified by `e = energy_internal_specific(V, T, eos)`, see +[`energy_internal_specific(V, T, eos::IdealGas)`](@ref), [`energy_internal_specific(V, T, eos::VanDerWaals)`](@ref). Because of this, the primitive variables are also defined to be `V, v1, T` (instead of `rho, v1, p` for `CompressibleEulerEquations1D`). The implementation also assumes @@ -275,7 +275,7 @@ end V, v1, T = prim rho = inv(V) rho_v1 = rho * v1 - e = energy_internal(V, T, eos) + e = energy_internal_specific(V, T, eos) rho_e_total = rho * e + 0.5f0 * rho_v1 * v1 return SVector(rho, rho_v1, rho_e_total) end @@ -338,10 +338,10 @@ end return rho * p end -@inline function energy_internal(u, equations::NonIdealCompressibleEulerEquations1D) +@inline function energy_internal_specific(u, equations::NonIdealCompressibleEulerEquations1D) eos = equations.equation_of_state V, _, T = cons2prim(u, equations) - e = energy_internal(V, T, eos) + e = energy_internal_specific(V, T, eos) return e end From 396a79ea9d04a7edc4da0efbbfe05bd18f4b53d1 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Mon, 26 Jan 2026 13:41:06 -0600 Subject: [PATCH 103/360] format --- src/equations/nonideal_compressible_euler_1d.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/equations/nonideal_compressible_euler_1d.jl b/src/equations/nonideal_compressible_euler_1d.jl index 440c52aed64..e7a50cd9957 100644 --- a/src/equations/nonideal_compressible_euler_1d.jl +++ b/src/equations/nonideal_compressible_euler_1d.jl @@ -338,7 +338,8 @@ end return rho * p end -@inline function energy_internal_specific(u, equations::NonIdealCompressibleEulerEquations1D) +@inline function energy_internal_specific(u, + equations::NonIdealCompressibleEulerEquations1D) eos = equations.equation_of_state V, _, T = cons2prim(u, equations) e = energy_internal_specific(V, T, eos) From 3b360b46286331adb4b4110e5ac5f836053da4b4 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Mon, 26 Jan 2026 13:56:40 -0600 Subject: [PATCH 104/360] exporting `energy_internal_specific` --- src/Trixi.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Trixi.jl b/src/Trixi.jl index 52187d035d7..ccb12d2668a 100644 --- a/src/Trixi.jl +++ b/src/Trixi.jl @@ -252,7 +252,7 @@ export density, pressure, density_pressure, velocity, temperature, equilibrium_distribution, waterheight, waterheight_pressure export entropy, entropy_thermodynamic, entropy_math, entropy_guermond_etal, - energy_total, energy_kinetic, energy_internal, + energy_total, energy_kinetic, energy_internal, energy_internal_specific, energy_magnetic, cross_helicity, magnetic_field, divergence_cleaning_field, enstrophy, vorticity export lake_at_rest_error From 71a4e5bae3514e6d5c435d4c82598b359162a48c Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Mon, 26 Jan 2026 15:59:14 -0600 Subject: [PATCH 105/360] Revert "Merge branch 'jc/rename_nonideal_energy_internal' into jc/add_PR" This reverts commit 345a5c22169c5e7421216a839b34de01476c6953, reversing changes made to fd5922393ddcdc4a6a2d2defd71fb6f52613ba86. --- src/Trixi.jl | 2 +- src/equations/equation_of_state_ideal_gas.jl | 4 ++-- src/equations/equation_of_state_vdw.jl | 6 +++--- src/equations/equations_of_state.jl | 14 +++++++------- src/equations/nonideal_compressible_euler_1d.jl | 12 ++++++------ 5 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/Trixi.jl b/src/Trixi.jl index 33cdf9578ee..492d61f309e 100644 --- a/src/Trixi.jl +++ b/src/Trixi.jl @@ -254,7 +254,7 @@ export density, pressure, density_pressure, velocity, temperature, equilibrium_distribution, waterheight, waterheight_pressure export entropy, entropy_thermodynamic, entropy_math, entropy_guermond_etal, - energy_total, energy_kinetic, energy_internal, energy_internal_specific, + energy_total, energy_kinetic, energy_internal, energy_magnetic, cross_helicity, magnetic_field, divergence_cleaning_field, enstrophy, vorticity export lake_at_rest_error diff --git a/src/equations/equation_of_state_ideal_gas.jl b/src/equations/equation_of_state_ideal_gas.jl index 09efd6ae038..bc5e74de035 100644 --- a/src/equations/equation_of_state_ideal_gas.jl +++ b/src/equations/equation_of_state_ideal_gas.jl @@ -47,12 +47,12 @@ function pressure(V, T, eos::IdealGas) end """ - energy_internal_specific(V, T, eos::IdealGas) + energy_internal(V, T, eos::IdealGas) Computes internal energy for an ideal gas from specific volume `V` and temperature `T` as ``e = c_v T``. """ -function energy_internal_specific(V, T, eos::IdealGas) +function energy_internal(V, T, eos::IdealGas) (; cv) = eos e = cv * T return e diff --git a/src/equations/equation_of_state_vdw.jl b/src/equations/equation_of_state_vdw.jl index 8546a55d1ce..18af6a7b220 100644 --- a/src/equations/equation_of_state_vdw.jl +++ b/src/equations/equation_of_state_vdw.jl @@ -55,12 +55,12 @@ function pressure(V, T, eos::VanDerWaals) end """ - energy_internal_specific(V, T, eos::VanDerWaals) + energy_internal(V, T, eos::VanDerWaals) Computes internal energy for a van der Waals gas from specific volume `V` and temperature `T` as ``e = c_v T - a \rho``. """ -function energy_internal_specific(V, T, eos::VanDerWaals) +function energy_internal(V, T, eos::VanDerWaals) (; cv, a) = eos rho = inv(V) e = cv * T - a * rho @@ -83,7 +83,7 @@ end function speed_of_sound(V, T, eos::VanDerWaals) (; a, b, gamma) = eos rho = inv(V) - e = energy_internal_specific(V, T, eos) + e = energy_internal(V, T, eos) c2 = gamma * (gamma - 1) * (e + rho * a) / (1 - rho * b)^2 - 2 * a * rho return sqrt(c2) end diff --git a/src/equations/equations_of_state.jl b/src/equations/equations_of_state.jl index 2480601c728..c653c8dfc4c 100644 --- a/src/equations/equations_of_state.jl +++ b/src/equations/equations_of_state.jl @@ -11,7 +11,7 @@ The interface for an `AbstractEquationOfState` requires specifying the following four functions: - `pressure(V, T, eos)` -- `energy_internal_specific(V, T, eos)`, the specific internal energy +- `energy_internal(V, T, eos)`, the specific internal energy - `entropy_specific(V, T, eos)`, the specific entropy - `speed_of_sound(V, T, eos)` @@ -37,14 +37,14 @@ include("equation_of_state_peng_robinson.jl") function gibbs_free_energy(V, T, eos) s = entropy_specific(V, T, eos) p = pressure(V, T, eos) - e = energy_internal_specific(V, T, eos) + e = energy_internal(V, T, eos) h = e + p * V return h - T * s end # compute c_v = de/dT @inline function heat_capacity_constant_volume(V, T, eos::AbstractEquationOfState) - return ForwardDiff.derivative(T -> energy_internal_specific(V, T, eos), T) + return ForwardDiff.derivative(T -> energy_internal(V, T, eos), T) end # this is used in [`flux_terashima_etal`](@ref) and [`flux_terashima_etal_central`](@ref) @@ -65,17 +65,17 @@ eos_newton_maxiter(eos) = 100 maxiter = 100) Calculates the temperature as a function of specific volume `V` and internal energy `e` -by using Newton's method to determine `T` such that `energy_internal_specific(V, T, eos) = e`. +by using Newton's method to determine `T` such that `energy_internal(V, T, eos) = e`. Note that the tolerance may need to be adjusted based on the specific equation of state. """ function temperature(V, e, eos::AbstractEquationOfState; initial_T = eos_initial_temperature(V, e, eos), tol = eos_newton_tol(eos), maxiter = eos_newton_maxiter(eos)) T = initial_T - de = energy_internal_specific(V, T, eos) - e + de = energy_internal(V, T, eos) - e iter = 1 while abs(de) > tol * abs(e) && iter < maxiter - de = energy_internal_specific(V, T, eos) - e + de = energy_internal(V, T, eos) - e # for thermodynamically admissible states, c_v = de_dT_V > 0, which should # guarantee convergence of this iteration. @@ -95,7 +95,7 @@ end # helper function used in [`flux_terashima_etal`](@ref) and [`flux_terashima_etal_central`](@ref) @inline function drho_e_drho_at_const_p(V, T, eos::AbstractEquationOfState) rho = inv(V) - e = energy_internal_specific(V, T, eos) + e = energy_internal(V, T, eos) dpdT_V, dpdV_T = calc_pressure_derivatives(V, T, eos) dpdrho_T = dpdV_T * (-V / rho) # V = inv(rho), so dVdrho = -1/rho^2 = -V^2. diff --git a/src/equations/nonideal_compressible_euler_1d.jl b/src/equations/nonideal_compressible_euler_1d.jl index 8df91373ce9..bac0d5d91c3 100644 --- a/src/equations/nonideal_compressible_euler_1d.jl +++ b/src/equations/nonideal_compressible_euler_1d.jl @@ -34,8 +34,8 @@ by some user-specified equation of state (EOS) p = p(V, T) ``` -Similarly, the internal energy is specified by `e = energy_internal_specific(V, T, eos)`, see -[`energy_internal_specific(V, T, eos::IdealGas)`](@ref), [`energy_internal_specific(V, T, eos::VanDerWaals)`](@ref). +Similarly, the internal energy is specified by `e = energy_internal(V, T, eos)`, see +[`energy_internal(V, T, eos::IdealGas)`](@ref), [`energy_internal(V, T, eos::VanDerWaals)`](@ref). Because of this, the primitive variables are also defined to be `V, v1, T` (instead of `rho, v1, p` for `CompressibleEulerEquations1D`). The implementation also assumes @@ -287,7 +287,7 @@ end V, v1, T = prim rho = inv(V) rho_v1 = rho * v1 - e = energy_internal_specific(V, T, eos) + e = energy_internal(V, T, eos) rho_e_total = rho * e + 0.5f0 * rho_v1 * v1 return SVector(rho, rho_v1, rho_e_total) end @@ -357,13 +357,13 @@ end return rho * p end -@inline function energy_internal_specific(u, - equations::NonIdealCompressibleEulerEquations1D) +@inline function energy_internal(u, + equations::AbstractNonIdealCompressibleEulerEquations) eos = equations.equation_of_state q = cons2prim(u, equations) V = first(q) T = last(q) - e = energy_internal_specific(V, T, eos) + e = energy_internal(V, T, eos) return e end From b2e7df945f2aaaf837ad4e84470974e9a4ed576b Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Mon, 26 Jan 2026 16:30:57 -0600 Subject: [PATCH 106/360] adding VdW Riemann problem --- src/Trixi.jl | 3 +- .../nonideal_compressible_euler_1d.jl | 37 +++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/src/Trixi.jl b/src/Trixi.jl index 492d61f309e..87b11fab65c 100644 --- a/src/Trixi.jl +++ b/src/Trixi.jl @@ -184,7 +184,8 @@ export AcousticPerturbationEquations2D, export NonIdealCompressibleEulerEquations1D, NonIdealCompressibleEulerEquations2D export IdealGas, VanDerWaals, PengRobinson -export initial_condition_transcritical_shock +export initial_condition_Riemann_problem, initial_condition_transcritical_wave, + initial_condition_transcritical_shock export LaplaceDiffusion1D, LaplaceDiffusion2D, LaplaceDiffusion3D, LaplaceDiffusionEntropyVariables1D, LaplaceDiffusionEntropyVariables2D, diff --git a/src/equations/nonideal_compressible_euler_1d.jl b/src/equations/nonideal_compressible_euler_1d.jl index bac0d5d91c3..80e3e4c12d8 100644 --- a/src/equations/nonideal_compressible_euler_1d.jl +++ b/src/equations/nonideal_compressible_euler_1d.jl @@ -407,6 +407,43 @@ function initial_condition_density_wave(x, t, return prim2cons(SVector(V, v1, T), equations) end +# The 1D Riemann problem from "An oscillation free shock-capturing method +# for compressible van der Waals supercritical fluid flows" by Pantano, +# Saurel, and Schmitt (2017). +# +# This is intended to be run with the `VanDerWaals(; gamma=1.4)` eos until +# final time 14e-3 on the domain [-12, 12]. +function initial_condition_Riemann_problem(x, t, + equations::NonIdealCompressibleEulerEquations1D{<:VanDerWaals}) + RealT = eltype(x) + eos = equations.equation_of_state + + if x[1] < 0 + rho, v1, p = SVector(497.417, 0, 4e7) + else + rho, v1, p = SVector(13.33117, 0, 4e6) # 6e6 + end + + V = inv(rho) + + # invert for temperature given p, V + T = RealT(1.0) + tol = 100 * eps(RealT) + dp = pressure(V, T, eos) - p + iter = 1 + while abs(dp) / abs(p) > tol && iter < 100 + dp = pressure(V, T, eos) - p + dpdT_V = ForwardDiff.derivative(T -> pressure(V, T, eos), T) + T = max(tol, T - dp / dpdT_V) + iter += 1 + end + if iter == 100 + println("Warning: solver for temperature(V, p) did not converge") + end + + return prim2cons(SVector(V, v1, T), equations) +end + # the smooth Peng-Robinson N2 transcritical wave taken from "An entropy-stable hybrid scheme # for simulations of transcritical real-fluid flows" by Ma, Ihme (2017). # From 097c77e7c30fd968dbda5a2f89e8c74ec6bf4291 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Mon, 26 Jan 2026 23:32:12 -0600 Subject: [PATCH 107/360] initial working volume integral entropy correction --- .../elixir_euler_entropy_correction.jl | 68 +++++++++++++++++++ src/Trixi.jl | 12 ++-- src/callbacks_step/analysis_dg1d.jl | 31 +++++++++ src/equations/compressible_euler_1d.jl | 4 ++ src/solvers/dg.jl | 51 ++++++++++++++ src/solvers/dgsem/calc_volume_integral.jl | 60 ++++++++++++++++ src/solvers/dgsem_tree/indicators.jl | 30 ++++++++ src/solvers/dgsem_tree/indicators_1d.jl | 12 ++++ 8 files changed, 263 insertions(+), 5 deletions(-) create mode 100644 examples/tree_1d_dgsem/elixir_euler_entropy_correction.jl diff --git a/examples/tree_1d_dgsem/elixir_euler_entropy_correction.jl b/examples/tree_1d_dgsem/elixir_euler_entropy_correction.jl new file mode 100644 index 00000000000..12a1c185104 --- /dev/null +++ b/examples/tree_1d_dgsem/elixir_euler_entropy_correction.jl @@ -0,0 +1,68 @@ +using OrdinaryDiffEqLowStorageRK +using Trixi + +############################################################################### +# semidiscretization of the compressible Euler equations +equations = CompressibleEulerEquations1D(1.4) + +function initial_condition_modified_sod(x, t, equations::CompressibleEulerEquations1D) + if x[1] < 0.3 + return prim2cons(SVector(1, 0.75, 1), equations) + else + return prim2cons(SVector(0.125, 0.0, 0.1), equations) + end +end + +initial_condition = initial_condition_modified_sod + +volume_flux = flux_central +surface_flux = flux_lax_friedrichs +basis = LobattoLegendreBasis(3) +volume_integral = VolumeIntegralEntropyCorrection(equations, basis; + volume_flux_dg = volume_flux, + volume_flux_fv = surface_flux) +solver = DGSEM(basis, surface_flux, volume_integral) + +coordinates_min = 0.0 +coordinates_max = 1.0 +mesh = TreeMesh(coordinates_min, coordinates_max, + initial_refinement_level = 6, + n_cells_max = 30_000, + periodicity = false) + +boundary_conditions = (x_neg = BoundaryConditionDirichlet(initial_condition), + x_pos = BoundaryConditionDirichlet(initial_condition)) +semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver; + boundary_conditions = boundary_conditions) + +############################################################################### +# ODE solvers, callbacks etc. + +tspan = (0.0, 0.20) +ode = semidiscretize(semi, tspan) + +summary_callback = SummaryCallback() + +analysis_interval = 2000 +analysis_callback = AnalysisCallback(semi, interval = analysis_interval) + +alive_callback = AliveCallback(analysis_interval = analysis_interval) + +save_solution = SaveSolutionCallback(interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim) + +stepsize_callback = StepsizeCallback(cfl = 0.8) + +callbacks = CallbackSet(summary_callback, + analysis_callback, alive_callback, + save_solution, + stepsize_callback) + +############################################################################### +# run the simulation + +sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false); + dt = stepsize_callback(ode), # solve needs some value here but it will be overwritten by the stepsize_callback + ode_default_options()..., callback = callbacks); diff --git a/src/Trixi.jl b/src/Trixi.jl index 52187d035d7..b1c1983278a 100644 --- a/src/Trixi.jl +++ b/src/Trixi.jl @@ -80,7 +80,7 @@ using RecursiveArrayTools: VectorOfArray using Requires: @require using Static: Static, One, True, False @reexport using StaticArrays: SVector -using StaticArrays: StaticArrays, MVector, MArray, SMatrix, @SMatrix +using StaticArrays: StaticArrays, MVector, MMatrix, MArray, SMatrix, @SMatrix using StrideArrays: PtrArray, StrideArray, StaticInt @reexport using StructArrays: StructArrays, StructArray using TimerOutputs: TimerOutputs, @notimeit, print_timer, reset_timer! @@ -252,9 +252,9 @@ export density, pressure, density_pressure, velocity, temperature, equilibrium_distribution, waterheight, waterheight_pressure export entropy, entropy_thermodynamic, entropy_math, entropy_guermond_etal, - energy_total, energy_kinetic, energy_internal, - energy_magnetic, cross_helicity, magnetic_field, divergence_cleaning_field, - enstrophy, vorticity + energy_total, energy_kinetic, energy_internal, entropy_potential +energy_magnetic, cross_helicity, magnetic_field, divergence_cleaning_field, +enstrophy, vorticity export lake_at_rest_error export ncomponents, eachcomponent export have_constant_speed @@ -271,6 +271,7 @@ export DG, VolumeIntegralShockCapturingHG, VolumeIntegralShockCapturingRRG, IndicatorHennemannGassner, VolumeIntegralUpwind, + VolumeIntegralEntropyCorrection, SurfaceIntegralWeakForm, SurfaceIntegralStrongForm, SurfaceIntegralUpwind, MortarL2 @@ -317,7 +318,8 @@ export load_mesh, load_time, load_timestep, load_timestep!, load_dt, load_adaptive_time_integrator! export ControllerThreeLevel, ControllerThreeLevelCombined, - IndicatorLöhner, IndicatorLoehner, IndicatorMax + IndicatorLöhner, IndicatorLoehner, IndicatorMax, + IndicatorEntropyCorrection export PositivityPreservingLimiterZhangShu, EntropyBoundedLimiter diff --git a/src/callbacks_step/analysis_dg1d.jl b/src/callbacks_step/analysis_dg1d.jl index 8d5a85f2499..5632be6f1c5 100644 --- a/src/callbacks_step/analysis_dg1d.jl +++ b/src/callbacks_step/analysis_dg1d.jl @@ -119,6 +119,37 @@ function calc_error_norms(func, u, t, analyzer, return l2_error, linf_error end +# integrate `du_local` against the entropy variables evaluated at `u` +function integrate_against_entropy_variables(du_local, u, element, + mesh::AbstractMesh{1}, + equations, dg::DGSEM, cache, args...) + (; weights) = dg.basis + + # we integrate over the reference element since `du` is calculated by + # a weak form-like volume integral, and should already be scaled by the + # Jacobian factor. + + integral = zero(eltype(u)) + + for i in eachnode(dg) + du_node = get_node_vars(du_local, equations, dg, i) + u_node = get_node_vars(u, equations, dg, i, element) + integral = integral + + weights[i] * dot(cons2entropy(u_node, equations), du_node) + end + + return integral +end + +# calculate surface integral of func(u, equations) * normal +function surface_integral(func::Func, u, element, mesh::TreeMesh{1}, equations, + dg::DGSEM, cache) where {Func} + u_left = get_node_vars(u, equations, dg, 1, element) + u_right = get_node_vars(u, equations, dg, nnodes(dg), element) + surface_integral = (func(u_right, equations) - func(u_left, equations)) + return surface_integral +end + function integrate_via_indices(func::Func, u, mesh::TreeMesh{1}, equations, dg::DGSEM, cache, args...; normalize = true) where {Func} diff --git a/src/equations/compressible_euler_1d.jl b/src/equations/compressible_euler_1d.jl index 63c65a3422d..03796496f77 100644 --- a/src/equations/compressible_euler_1d.jl +++ b/src/equations/compressible_euler_1d.jl @@ -1147,4 +1147,8 @@ of total energy and kinetic energy. @inline function energy_internal(cons, equations::CompressibleEulerEquations1D) return energy_total(cons, equations) - energy_kinetic(cons, equations) end + +@inline function entropy_potential(u, equations::CompressibleEulerEquations1D) + return u[2] +end end # @muladd diff --git a/src/solvers/dg.jl b/src/solvers/dg.jl index 2ec05459a79..2436202020a 100644 --- a/src/solvers/dg.jl +++ b/src/solvers/dg.jl @@ -280,6 +280,57 @@ function Base.show(io::IO, mime::MIME"text/plain", end end +""" + VolumeIntegralEntropyCorrection(volume_flux_dg=flux_central, + volume_flux_fv=flux_lax_friedrichs, + indicator=nothing) + +Entropy correction volume integral type for DG methods using a convex blending of +the **first-order** finite volume method with numerical flux `volume_flux_fv` and the +[`VolumeIntegralFluxDifferencing`](@ref) with volume flux `volume_flux_dg`. +The amount of blending is determined by the violation of a cell entropy equality by +the volume integral +""" +struct VolumeIntegralEntropyCorrection{VolumeFluxDG, VolumeFluxFV, Indicator} <: + AbstractVolumeIntegralShockCapturing + volume_flux_dg::VolumeFluxDG # symmetric, e.g. split-form or entropy-conservative + volume_flux_fv::VolumeFluxFV # non-symmetric in general, e.g. entropy-dissipative + indicator::Indicator +end + +function VolumeIntegralEntropyCorrection(equations, basis; + volume_flux_dg = flux_central, + volume_flux_fv = flux_lax_friedrichs) + indicator = IndicatorEntropyCorrection(equations, basis) + return VolumeIntegralEntropyCorrection{typeof(volume_flux_dg), + typeof(volume_flux_fv), + typeof(indicator)}(volume_flux_dg, + volume_flux_fv, + indicator) +end + +function Base.show(io::IO, mime::MIME"text/plain", + integral::VolumeIntegralEntropyCorrection) + @nospecialize integral # reduce precompilation time + + if get(io, :compact, false) + show(io, integral) + else + summary_header(io, "VolumeIntegralEntropyCorrection") + summary_line(io, "volume flux DG", integral.volume_flux_dg) + summary_line(io, "volume flux FV", integral.volume_flux_fv) + summary_line(io, "indicator", integral.indicator |> typeof |> nameof) + show(increment_indent(io), mime, integral.indicator) + summary_footer(io) + end +end + +function get_element_variables!(element_variables, u, mesh, equations, + volume_integral::VolumeIntegralEntropyCorrection, + dg, cache) + # do nothing +end + # Abstract supertype for first-order `VolumeIntegralPureLGLFiniteVolume` and # second-order `VolumeIntegralPureLGLFiniteVolumeO2` subcell-based finite volume # volume integrals. diff --git a/src/solvers/dgsem/calc_volume_integral.jl b/src/solvers/dgsem/calc_volume_integral.jl index a337d8b6eeb..fff6d064593 100644 --- a/src/solvers/dgsem/calc_volume_integral.jl +++ b/src/solvers/dgsem/calc_volume_integral.jl @@ -155,4 +155,64 @@ function calc_volume_integral!(du, u, mesh, return nothing end + +@inline regularized_ratio(a, b) = a * b / (1e-15 + b^2) + +function calc_volume_integral!(du, u, mesh, + have_nonconservative_terms, equations, + volume_integral::VolumeIntegralEntropyCorrection, + dg::DGSEM, cache) + (; volume_flux_dg, volume_flux_fv, indicator) = volume_integral + du_element_threaded = indicator.cache.indicator_threaded + + @threaded for element in eachelement(dg, cache) + flux_differencing_kernel!(du, u, element, mesh, + have_nonconservative_terms, equations, + volume_flux_dg, dg, cache) + + # check entropy production of "high order" volume integral + volume_integral_entropy_vars = integrate_against_entropy_variables(view(du, .., + element), + u, element, + mesh, + equations, + dg, cache) + surface_integral_entropy_potential = surface_integral(entropy_potential, u, + element, mesh, equations, + dg, cache) + entropy_residual = volume_integral_entropy_vars + + surface_integral_entropy_potential + + if entropy_residual < 0 + # Store "high order" result + du_element = du_element_threaded[Threads.threadid()] + @views du_element .= du[.., element] + + # Reset weak form volume integral + du[.., element] .= zero(eltype(du)) + + # Calculate FV volume integral contribution + fv_kernel!(du, u, mesh, + have_nonconservative_terms, equations, + volume_flux_fv, dg, cache, element) + + # calculate difference between high and low order FV integral; + # this should be entropy dissipative if entropy_residual > 0. + @views du_element .= (du_element .- du[.., element]) + + entropy_dissipation = integrate_against_entropy_variables(du_element, u, + element, + mesh, equations, + dg, cache) + + # calculate blending factor + theta = min(1, regularized_ratio(entropy_residual, entropy_dissipation)) + + # blend the high order method back in + @views du[.., element] .= du[.., element] .+ (1 - theta) * du_element + end + end + + return nothing +end end # @muladd diff --git a/src/solvers/dgsem_tree/indicators.jl b/src/solvers/dgsem_tree/indicators.jl index fdc2ca3a536..5d3645b87c1 100644 --- a/src/solvers/dgsem_tree/indicators.jl +++ b/src/solvers/dgsem_tree/indicators.jl @@ -272,4 +272,34 @@ function Base.show(io::IO, ::MIME"text/plain", indicator::IndicatorMax) summary_box(io, "IndicatorMax", setup) end end + +""" + IndicatorEntropyCorrection(equations::AbstractEquations) + +Indicator used for entropy correction using subcell FV schemes. + +See also [`VolumeIntegralEntropyCorrection`](@ref). +""" +struct IndicatorEntropyCorrection{Cache} <: AbstractIndicator + cache::Cache +end + +# this method is used when the indicator is constructed as for shock-capturing volume integrals +function IndicatorEntropyCorrection(equations::AbstractEquations, + basis::LobattoLegendreBasis) + cache = create_cache(IndicatorEntropyCorrection, equations, basis) + return IndicatorEntropyCorrection{typeof(cache)}(cache) +end + +function Base.show(io::IO, indicator::IndicatorEntropyCorrection) + @nospecialize indicator # reduce precompilation time + print(io, "IndicatorEntropyCorrection") + return nothing +end + +function Base.show(io::IO, ::MIME"text/plain", indicator::IndicatorEntropyCorrection) + @nospecialize indicator # reduce precompilation time + summary_box(io, "IndicatorEntropyCorrection") + return nothing +end end # @muladd diff --git a/src/solvers/dgsem_tree/indicators_1d.jl b/src/solvers/dgsem_tree/indicators_1d.jl index 8f115143368..d2618665408 100644 --- a/src/solvers/dgsem_tree/indicators_1d.jl +++ b/src/solvers/dgsem_tree/indicators_1d.jl @@ -186,4 +186,16 @@ function (indicator_max::IndicatorMax)(u::AbstractArray{<:Any, 3}, return alpha end + +# this method is used when the indicator is constructed as for +# shock-capturing volume integrals. +function create_cache(::Type{IndicatorEntropyCorrection}, + equations::AbstractEquations{1}, basis::LobattoLegendreBasis) + uEltype = real(basis) + MVec = MMatrix{nvariables(equations), nnodes(basis), uEltype} + + indicator_threaded = MVec[MVec(undef) for _ in 1:Threads.maxthreadid()] + + return (; indicator_threaded) +end end # @muladd From 8ca8cecbf8e7244b26856a56c18ecdbb67332f10 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Tue, 27 Jan 2026 11:33:38 -0600 Subject: [PATCH 108/360] finish implementation in 1D --- src/callbacks_step/analysis_dg1d.jl | 4 ++-- src/solvers/dg.jl | 20 ++++++++++++++------ src/solvers/dgsem/calc_volume_integral.jl | 13 +++++++++++-- src/solvers/dgsem_tree/indicators.jl | 19 ++++++++++++++----- src/solvers/dgsem_tree/indicators_1d.jl | 6 +++++- 5 files changed, 46 insertions(+), 16 deletions(-) diff --git a/src/callbacks_step/analysis_dg1d.jl b/src/callbacks_step/analysis_dg1d.jl index 5632be6f1c5..7dab83a1664 100644 --- a/src/callbacks_step/analysis_dg1d.jl +++ b/src/callbacks_step/analysis_dg1d.jl @@ -141,12 +141,12 @@ function integrate_against_entropy_variables(du_local, u, element, return integral end -# calculate surface integral of func(u, equations) * normal +# calculate surface integral of func(u, orientation, equations) * normal function surface_integral(func::Func, u, element, mesh::TreeMesh{1}, equations, dg::DGSEM, cache) where {Func} u_left = get_node_vars(u, equations, dg, 1, element) u_right = get_node_vars(u, equations, dg, nnodes(dg), element) - surface_integral = (func(u_right, equations) - func(u_left, equations)) + surface_integral = (func(u_right, 1, equations) - func(u_left, 1, equations)) return surface_integral end diff --git a/src/solvers/dg.jl b/src/solvers/dg.jl index 2436202020a..23f55b4c79b 100644 --- a/src/solvers/dg.jl +++ b/src/solvers/dg.jl @@ -281,15 +281,21 @@ function Base.show(io::IO, mime::MIME"text/plain", end """ - VolumeIntegralEntropyCorrection(volume_flux_dg=flux_central, + VolumeIntegralEntropyCorrection(equations, basis; + volume_flux_dg=flux_central, volume_flux_fv=flux_lax_friedrichs, - indicator=nothing) + scaling = true) Entropy correction volume integral type for DG methods using a convex blending of the **first-order** finite volume method with numerical flux `volume_flux_fv` and the [`VolumeIntegralFluxDifferencing`](@ref) with volume flux `volume_flux_dg`. The amount of blending is determined by the violation of a cell entropy equality by -the volume integral +the volume integral. + +`scaling ≥ 1` arbitrarily scales the blending parameter by a constant, increasing +the amount of the subcell FV added in. This can be used to add shock capturing-like +behavior. + """ struct VolumeIntegralEntropyCorrection{VolumeFluxDG, VolumeFluxFV, Indicator} <: AbstractVolumeIntegralShockCapturing @@ -300,8 +306,9 @@ end function VolumeIntegralEntropyCorrection(equations, basis; volume_flux_dg = flux_central, - volume_flux_fv = flux_lax_friedrichs) - indicator = IndicatorEntropyCorrection(equations, basis) + volume_flux_fv = flux_lax_friedrichs, + scaling = true) + indicator = IndicatorEntropyCorrection(equations, basis; scaling) return VolumeIntegralEntropyCorrection{typeof(volume_flux_dg), typeof(volume_flux_fv), typeof(indicator)}(volume_flux_dg, @@ -328,7 +335,8 @@ end function get_element_variables!(element_variables, u, mesh, equations, volume_integral::VolumeIntegralEntropyCorrection, dg, cache) - # do nothing + element_variables[:indicator_shock_capturing] = volume_integral.indicator.cache.alpha + return nothing end # Abstract supertype for first-order `VolumeIntegralPureLGLFiniteVolume` and diff --git a/src/solvers/dgsem/calc_volume_integral.jl b/src/solvers/dgsem/calc_volume_integral.jl index fff6d064593..bbdba31e57d 100644 --- a/src/solvers/dgsem/calc_volume_integral.jl +++ b/src/solvers/dgsem/calc_volume_integral.jl @@ -164,6 +164,9 @@ function calc_volume_integral!(du, u, mesh, dg::DGSEM, cache) (; volume_flux_dg, volume_flux_fv, indicator) = volume_integral du_element_threaded = indicator.cache.indicator_threaded + (; scaling) = indicator + (; alpha) = indicator.cache + resize!(alpha, nelements(dg, cache)) @threaded for element in eachelement(dg, cache) flux_differencing_kernel!(du, u, element, mesh, @@ -180,6 +183,8 @@ function calc_volume_integral!(du, u, mesh, surface_integral_entropy_potential = surface_integral(entropy_potential, u, element, mesh, equations, dg, cache) + + # this quantity should be ≥ 0 for an entropy stable discretization entropy_residual = volume_integral_entropy_vars + surface_integral_entropy_potential @@ -205,8 +210,12 @@ function calc_volume_integral!(du, u, mesh, mesh, equations, dg, cache) - # calculate blending factor - theta = min(1, regularized_ratio(entropy_residual, entropy_dissipation)) + # calculate blending factor + ratio = regularized_ratio(entropy_residual, entropy_dissipation) + theta = max(0, min(1, scaling * ratio)) + + # save blending coefficient for visualization + alpha[element] = theta # blend the high order method back in @views du[.., element] .= du[.., element] .+ (1 - theta) * du_element diff --git a/src/solvers/dgsem_tree/indicators.jl b/src/solvers/dgsem_tree/indicators.jl index 5d3645b87c1..784cdb01b19 100644 --- a/src/solvers/dgsem_tree/indicators.jl +++ b/src/solvers/dgsem_tree/indicators.jl @@ -274,21 +274,30 @@ function Base.show(io::IO, ::MIME"text/plain", indicator::IndicatorMax) end """ - IndicatorEntropyCorrection(equations::AbstractEquations) + IndicatorEntropyCorrection(equations::AbstractEquations, basis; + scaling=true) -Indicator used for entropy correction using subcell FV schemes. +Indicator used for entropy correction using subcell FV schemes, where the +blending is determined so that the volume integral entropy production is the +same or more than that of an EC scheme. + +`scaling ≥ 1` arbitrarily scales the blending parameter by a constant, increasing +the amount of the subcell FV added in. This can be used to add shock capturing-like +behavior. See also [`VolumeIntegralEntropyCorrection`](@ref). """ -struct IndicatorEntropyCorrection{Cache} <: AbstractIndicator +struct IndicatorEntropyCorrection{Cache, ScalingT} <: AbstractIndicator cache::Cache + scaling::ScalingT end # this method is used when the indicator is constructed as for shock-capturing volume integrals function IndicatorEntropyCorrection(equations::AbstractEquations, - basis::LobattoLegendreBasis) + basis::LobattoLegendreBasis; + scaling = true) cache = create_cache(IndicatorEntropyCorrection, equations, basis) - return IndicatorEntropyCorrection{typeof(cache)}(cache) + return IndicatorEntropyCorrection{typeof(cache), typeof(scaling)}(cache, scaling) end function Base.show(io::IO, indicator::IndicatorEntropyCorrection) diff --git a/src/solvers/dgsem_tree/indicators_1d.jl b/src/solvers/dgsem_tree/indicators_1d.jl index d2618665408..ed2a503e130 100644 --- a/src/solvers/dgsem_tree/indicators_1d.jl +++ b/src/solvers/dgsem_tree/indicators_1d.jl @@ -194,8 +194,12 @@ function create_cache(::Type{IndicatorEntropyCorrection}, uEltype = real(basis) MVec = MMatrix{nvariables(equations), nnodes(basis), uEltype} + # stores the blending coefficients + alpha = Vector{uEltype}() + + # container for elementwise volume integrals indicator_threaded = MVec[MVec(undef) for _ in 1:Threads.maxthreadid()] - return (; indicator_threaded) + return (; alpha, indicator_threaded) end end # @muladd From 933e54b2d6eb00f5c182a2fd1b442ea3d5ffafea Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Tue, 27 Jan 2026 11:33:46 -0600 Subject: [PATCH 109/360] 2D implementation --- src/callbacks_step/analysis_dg2d.jl | 46 +++++++++++++++++++++++++ src/solvers/dgsem_tree/indicators_2d.jl | 16 +++++++++ 2 files changed, 62 insertions(+) diff --git a/src/callbacks_step/analysis_dg2d.jl b/src/callbacks_step/analysis_dg2d.jl index ace06057ba1..25a0364ccf9 100644 --- a/src/callbacks_step/analysis_dg2d.jl +++ b/src/callbacks_step/analysis_dg2d.jl @@ -185,6 +185,52 @@ function calc_error_norms(func, u, t, analyzer, return l2_error, linf_error end +# integrate `du_local` against the entropy variables evaluated at `u` +function integrate_against_entropy_variables(du_local, u, element, + mesh::AbstractMesh{2}, + equations, dg::DGSEM, cache, args...) + (; weights) = dg.basis + + jacobian_1d = inv(cache.elements.inverse_jacobian[element]) # O(h) + + integral = zero(eltype(u)) + + for j in eachnode(dg), i in eachnode(dg) + du_node = get_node_vars(du_local, equations, dg, i, j) + u_node = get_node_vars(u, equations, dg, i, j, element) + integral = integral + + weights[i] * weights[j] * + dot(cons2entropy(u_node, equations), du_node) + end + # return integral + return integral * jacobian_1d +end + +# calculate surface integral of func(u, equations) * normal +function surface_integral(func::Func, u, element, mesh::TreeMesh{2}, equations, + dg::DGSEM, cache) where {Func} + surface_integral = zero(real(dg)) + for ii in eachnode(dg) + # x direction + u_left = get_node_vars(u, equations, dg, 1, ii, element) + u_right = get_node_vars(u, equations, dg, nnodes(dg), ii, element) + surface_integral = surface_integral + + dg.basis.weights[ii] * + (func(u_right, 1, equations) - func(u_left, 1, equations)) + + # y direction + u_left = get_node_vars(u, equations, dg, ii, 1, element) + u_right = get_node_vars(u, equations, dg, ii, nnodes(dg), element) + surface_integral = surface_integral + + dg.basis.weights[ii] * + (func(u_right, 2, equations) - func(u_left, 2, equations)) + end + + # return surface_integral + jacobian_1d = inv(cache.elements.inverse_jacobian[element]) # O(h) + return surface_integral * jacobian_1d +end + function integrate_via_indices(func::Func, u, mesh::TreeMesh{2}, equations, dg::DGSEM, cache, args...; normalize = true) where {Func} diff --git a/src/solvers/dgsem_tree/indicators_2d.jl b/src/solvers/dgsem_tree/indicators_2d.jl index 82330daf57a..0a64ee2ea09 100644 --- a/src/solvers/dgsem_tree/indicators_2d.jl +++ b/src/solvers/dgsem_tree/indicators_2d.jl @@ -22,6 +22,22 @@ function create_cache(::Type{IndicatorHennemannGassner}, return (; alpha, alpha_tmp, indicator_threaded, modal_threaded, modal_tmp1_threaded) end +# this method is used when the indicator is constructed as for +# shock-capturing volume integrals. +function create_cache(::Type{IndicatorEntropyCorrection}, + equations::AbstractEquations{2}, basis::LobattoLegendreBasis) + uEltype = real(basis) + MVec = MArray{Tuple{nvariables(equations), nnodes(basis), nnodes(basis)}, uEltype} + + # stores the blending coefficients + alpha = Vector{uEltype}() + + # container for elementwise volume integrals + indicator_threaded = MVec[MVec(undef) for _ in 1:Threads.maxthreadid()] + + return (; alpha, indicator_threaded) +end + # Use this function barrier and unpack inside to avoid passing closures to Polyester.jl # with @batch (@threaded). # Otherwise, @threaded does not work here with Julia ARM on macOS. From d39f0cd0c36b0663ebf73b99d9762313f600fcab Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Tue, 27 Jan 2026 11:34:16 -0600 Subject: [PATCH 110/360] add `entropy_potential` to CompressibleEuler 1D/2D --- src/equations/compressible_euler_1d.jl | 3 ++- src/equations/compressible_euler_2d.jl | 9 +++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/equations/compressible_euler_1d.jl b/src/equations/compressible_euler_1d.jl index 03796496f77..86c74bed8bd 100644 --- a/src/equations/compressible_euler_1d.jl +++ b/src/equations/compressible_euler_1d.jl @@ -1148,7 +1148,8 @@ of total energy and kinetic energy. return energy_total(cons, equations) - energy_kinetic(cons, equations) end -@inline function entropy_potential(u, equations::CompressibleEulerEquations1D) +@inline function entropy_potential(u, orientation::Int, + equations::CompressibleEulerEquations1D) return u[2] end end # @muladd diff --git a/src/equations/compressible_euler_2d.jl b/src/equations/compressible_euler_2d.jl index d67ddb64284..cc667c9d918 100644 --- a/src/equations/compressible_euler_2d.jl +++ b/src/equations/compressible_euler_2d.jl @@ -2230,6 +2230,15 @@ end return energy_total(cons, equations) - energy_kinetic(cons, equations) end +@inline function entropy_potential(u, orientation::Int, + equations::CompressibleEulerEquations2D) + if orientation == 1 + return u[2] + else # if orientation == 2 + return u[3] + end +end + # State validation for Newton-bisection method of subcell IDP limiting @inline function Base.isvalid(u, equations::CompressibleEulerEquations2D) if u[1] <= 0 || pressure(u, equations) <= 0 From 5255a38ae7bc6eb22560a4e2071dde93935de1e4 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Tue, 27 Jan 2026 11:34:21 -0600 Subject: [PATCH 111/360] add 2D example --- .../elixir_euler_entropy_correction.jl | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 examples/tree_2d_dgsem/elixir_euler_entropy_correction.jl diff --git a/examples/tree_2d_dgsem/elixir_euler_entropy_correction.jl b/examples/tree_2d_dgsem/elixir_euler_entropy_correction.jl new file mode 100644 index 00000000000..2da4ea3b88c --- /dev/null +++ b/examples/tree_2d_dgsem/elixir_euler_entropy_correction.jl @@ -0,0 +1,72 @@ +using OrdinaryDiffEqLowStorageRK +using OrdinaryDiffEqSSPRK +using Trixi + +############################################################################### +# semidiscretization of the compressible Euler equations +equations = CompressibleEulerEquations2D(1.4) + +""" + initial_condition_kelvin_helmholtz_instability(x, t, equations::CompressibleEulerEquations2D) + +A version of the classical Kelvin-Helmholtz instability based on +- Andrés M. Rueda-Ramírez, Gregor J. Gassner (2021) + A Subcell Finite Volume Positivity-Preserving Limiter for DGSEM Discretizations + of the Euler Equations + [arXiv: 2102.06017](https://arxiv.org/abs/2102.06017) +""" +function initial_condition_kelvin_helmholtz_instability(x, t, + equations::CompressibleEulerEquations2D) + # change discontinuity to tanh + # typical resolution 128^2, 256^2 + # domain size is [-1,+1]^2 + RealT = eltype(x) + slope = 15 + B = tanh(slope * x[2] + 7.5f0) - tanh(slope * x[2] - 7.5f0) + rho = 0.5f0 + 0.75f0 * B + v1 = 0.5f0 * (B - 1) + v2 = convert(RealT, 0.1) * sinpi(2 * x[1]) + p = 1 + return prim2cons(SVector(rho, v1, v2, p), equations) +end +initial_condition = initial_condition_kelvin_helmholtz_instability + +surface_flux = flux_lax_friedrichs +volume_flux = flux_central +polydeg = 3 +basis = LobattoLegendreBasis(polydeg) +volume_integral = VolumeIntegralEntropyCorrection(equations, basis; + volume_flux_dg = volume_flux, + volume_flux_fv = surface_flux, + scaling = 2.0) +solver = DGSEM(basis, surface_flux, volume_integral) + +coordinates_min = (-1.0, -1.0) +coordinates_max = (1.0, 1.0) +mesh = TreeMesh(coordinates_min, coordinates_max, + initial_refinement_level = 6, + n_cells_max = 100_000) +semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) + +############################################################################### +# ODE solvers, callbacks etc. + +tspan = (0.0, 5.0) +ode = semidiscretize(semi, tspan) + +summary_callback = SummaryCallback() + +analysis_interval = 100 +analysis_callback = AnalysisCallback(semi, interval = analysis_interval) + +alive_callback = AliveCallback(analysis_interval = analysis_interval) + +############################################################################### +# run the simulation + +callbacks = CallbackSet(summary_callback, + analysis_callback, + alive_callback) +sol = solve(ode, SSPRK43(); + abstol = 1e-6, reltol = 1e-4, + ode_default_options()..., callback = callbacks); From 9850e5914b48d4da8cd143c2401ab6f67ee468c6 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Tue, 27 Jan 2026 11:45:55 -0600 Subject: [PATCH 112/360] note that `entropy_potential` must be defined --- src/solvers/dg.jl | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/solvers/dg.jl b/src/solvers/dg.jl index 23f55b4c79b..1bdca60d8a1 100644 --- a/src/solvers/dg.jl +++ b/src/solvers/dg.jl @@ -296,6 +296,10 @@ the volume integral. the amount of the subcell FV added in. This can be used to add shock capturing-like behavior. +The use of `VolumeIntegralEntropyCorrection` requires either + `entropy_potential(u, orientation, equations)` for TreeMesh, or + `entropy_potential(u, normal_direction, equations)` for other mesh types +to be defined. """ struct VolumeIntegralEntropyCorrection{VolumeFluxDG, VolumeFluxFV, Indicator} <: AbstractVolumeIntegralShockCapturing From 2d47856f66d4b78ace179316dfc46a4a9d59cfae Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Tue, 27 Jan 2026 12:14:39 -0600 Subject: [PATCH 113/360] add entropy potential for nonideal euler --- src/equations/nonideal_compressible_euler_1d.jl | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/equations/nonideal_compressible_euler_1d.jl b/src/equations/nonideal_compressible_euler_1d.jl index 9add0ee7971..ea6aef615b3 100644 --- a/src/equations/nonideal_compressible_euler_1d.jl +++ b/src/equations/nonideal_compressible_euler_1d.jl @@ -351,4 +351,12 @@ end rho_e = rho_e_total - 0.5f0 * rho_v1^2 / rho return rho_e end + +@inline function entropy_potential(u, orientation::Int, equations::NonIdealCompressibleEulerEquations1D) + eos = equations.equation_of_state + V, v1, T = cons2prim(u, equations) + p = pressure(V, T, eos) + return p * v1 / T +end + end # @muladd From ee0ae9e24aee07bd9b953b148529e72a16d44aad Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Tue, 27 Jan 2026 14:07:07 -0600 Subject: [PATCH 114/360] format --- src/equations/nonideal_compressible_euler_1d.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/equations/nonideal_compressible_euler_1d.jl b/src/equations/nonideal_compressible_euler_1d.jl index ea6aef615b3..02ecc13e6ab 100644 --- a/src/equations/nonideal_compressible_euler_1d.jl +++ b/src/equations/nonideal_compressible_euler_1d.jl @@ -352,11 +352,11 @@ end return rho_e end -@inline function entropy_potential(u, orientation::Int, equations::NonIdealCompressibleEulerEquations1D) +@inline function entropy_potential(u, orientation::Int, + equations::NonIdealCompressibleEulerEquations1D) eos = equations.equation_of_state V, v1, T = cons2prim(u, equations) p = pressure(V, T, eos) return p * v1 / T end - end # @muladd From 171ec309fcfcb2142a7d07922c8c019caa2cc5bf Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Tue, 27 Jan 2026 14:09:46 -0600 Subject: [PATCH 115/360] add 1D test --- test/test_tree_1d_euler.jl | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/test/test_tree_1d_euler.jl b/test/test_tree_1d_euler.jl index 3af0c38b64b..a42ca4f785a 100644 --- a/test/test_tree_1d_euler.jl +++ b/test/test_tree_1d_euler.jl @@ -620,4 +620,30 @@ end @test_allocations(Trixi.rhs!, semi, sol, 1000) end +@trixi_testset "elixir_euler_nonideal_density_wave.jl with entropy correction" begin + @test_trixi_include(joinpath(EXAMPLES_DIR, + "elixir_euler_nonideal_density_wave.jl"), + solver=DGSEM(LobattoLegendreBasis(3), + flux_lax_friedrichs, + VolumeIntegralEntropyCorrection(equations, + LobattoLegendreBasis(3); + volume_flux_dg = volume_flux, + volume_flux_fv = surface_flux)), + tspan=(0.0, 0.1), + l2=[ + 0.0014836428894376321, + 0.0002850707686431336, + 0.06261081503929328 + ], + linf=[ + 0.002700476173981947, + 0.0008496629031280178, + 0.17460023775242917 + ]) + + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + @test_allocations(Trixi.rhs!, semi, sol, 1000) +end + end # module From 22c786030ff183ae8f76edc7817422e353239713 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Tue, 27 Jan 2026 23:03:04 -0600 Subject: [PATCH 116/360] add a 2D test --- test/test_tree_2d_euler.jl | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/test/test_tree_2d_euler.jl b/test/test_tree_2d_euler.jl index ecf078272c4..684f150a290 100644 --- a/test/test_tree_2d_euler.jl +++ b/test/test_tree_2d_euler.jl @@ -110,6 +110,32 @@ end @test_allocations(Trixi.rhs!, semi, sol, 1000) end +@trixi_testset "elixir_euler_density_wave.jl with entropy correction" begin + @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_density_wave.jl"), + solver=DGSEM(LobattoLegendreBasis(3), + flux_lax_friedrichs, + VolumeIntegralEntropyCorrection(equations, + LobattoLegendreBasis(3); + volume_flux_dg = volume_flux, + volume_flux_fv = surface_flux)), + tspan=(0.0, 0.1), + l2=[ + 0.028712539480767976, + 0.0028712539480767085, + 0.00574250789615355, + 0.0007178134870171292 + ], + linf=[ + 0.07201294589822704, + 0.007201294589823409, + 0.014402589179645153, + 0.0018003236474513074 + ]) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + @test_allocations(Trixi.rhs!, semi, sol, 1000) +end + @trixi_testset "elixir_euler_source_terms_nonperiodic.jl" begin @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_source_terms_nonperiodic.jl"), From 0ed7a3b5615a3a387f07a8ca93f9ab659e252000 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Tue, 27 Jan 2026 23:12:12 -0600 Subject: [PATCH 117/360] make modified sod example more challenging --- .../elixir_euler_entropy_correction.jl | 20 ++++++++----------- 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/examples/tree_1d_dgsem/elixir_euler_entropy_correction.jl b/examples/tree_1d_dgsem/elixir_euler_entropy_correction.jl index 12a1c185104..b43c1783bb0 100644 --- a/examples/tree_1d_dgsem/elixir_euler_entropy_correction.jl +++ b/examples/tree_1d_dgsem/elixir_euler_entropy_correction.jl @@ -1,4 +1,4 @@ -using OrdinaryDiffEqLowStorageRK +using OrdinaryDiffEqSSPRK using Trixi ############################################################################### @@ -9,7 +9,8 @@ function initial_condition_modified_sod(x, t, equations::CompressibleEulerEquati if x[1] < 0.3 return prim2cons(SVector(1, 0.75, 1), equations) else - return prim2cons(SVector(0.125, 0.0, 0.1), equations) + # this version of modified sod uses a 100x density and pressure jump + return prim2cons(SVector(0.0125, 0.0, 0.01), equations) end end @@ -38,7 +39,7 @@ semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver; ############################################################################### # ODE solvers, callbacks etc. -tspan = (0.0, 0.20) +tspan = (0.0, 0.2) ode = semidiscretize(semi, tspan) summary_callback = SummaryCallback() @@ -53,16 +54,11 @@ save_solution = SaveSolutionCallback(interval = 100, save_final_solution = true, solution_variables = cons2prim) -stepsize_callback = StepsizeCallback(cfl = 0.8) - -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution, - stepsize_callback) - ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false); - dt = stepsize_callback(ode), # solve needs some value here but it will be overwritten by the stepsize_callback +callbacks = CallbackSet(summary_callback, + analysis_callback, alive_callback) +sol = solve(ode, SSPRK43(); + abstol = 1e-6, reltol = 1e-4, ode_default_options()..., callback = callbacks); From a5fa0ba1e61bc47c37b4b565498209306384cee9 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Tue, 27 Jan 2026 23:12:19 -0600 Subject: [PATCH 118/360] comment --- ...ler_kelvin_helmholtz_instability_entropy_correction.jl} | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) rename examples/tree_2d_dgsem/{elixir_euler_entropy_correction.jl => elixir_euler_kelvin_helmholtz_instability_entropy_correction.jl} (99%) diff --git a/examples/tree_2d_dgsem/elixir_euler_entropy_correction.jl b/examples/tree_2d_dgsem/elixir_euler_kelvin_helmholtz_instability_entropy_correction.jl similarity index 99% rename from examples/tree_2d_dgsem/elixir_euler_entropy_correction.jl rename to examples/tree_2d_dgsem/elixir_euler_kelvin_helmholtz_instability_entropy_correction.jl index 2da4ea3b88c..bfcfc3fe502 100644 --- a/examples/tree_2d_dgsem/elixir_euler_entropy_correction.jl +++ b/examples/tree_2d_dgsem/elixir_euler_kelvin_helmholtz_instability_entropy_correction.jl @@ -61,12 +61,13 @@ analysis_callback = AnalysisCallback(semi, interval = analysis_interval) alive_callback = AliveCallback(analysis_interval = analysis_interval) -############################################################################### -# run the simulation - callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback) + +############################################################################### +# run the simulation + sol = solve(ode, SSPRK43(); abstol = 1e-6, reltol = 1e-4, ode_default_options()..., callback = callbacks); From e8cdeb5fbd8e05e0cba24f54dd188f30f6e8283a Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Tue, 27 Jan 2026 23:23:04 -0600 Subject: [PATCH 119/360] removing unneeded elixir --- ...elmholtz_instability_entropy_correction.jl | 73 ------------------- 1 file changed, 73 deletions(-) delete mode 100644 examples/tree_2d_dgsem/elixir_euler_kelvin_helmholtz_instability_entropy_correction.jl diff --git a/examples/tree_2d_dgsem/elixir_euler_kelvin_helmholtz_instability_entropy_correction.jl b/examples/tree_2d_dgsem/elixir_euler_kelvin_helmholtz_instability_entropy_correction.jl deleted file mode 100644 index bfcfc3fe502..00000000000 --- a/examples/tree_2d_dgsem/elixir_euler_kelvin_helmholtz_instability_entropy_correction.jl +++ /dev/null @@ -1,73 +0,0 @@ -using OrdinaryDiffEqLowStorageRK -using OrdinaryDiffEqSSPRK -using Trixi - -############################################################################### -# semidiscretization of the compressible Euler equations -equations = CompressibleEulerEquations2D(1.4) - -""" - initial_condition_kelvin_helmholtz_instability(x, t, equations::CompressibleEulerEquations2D) - -A version of the classical Kelvin-Helmholtz instability based on -- Andrés M. Rueda-Ramírez, Gregor J. Gassner (2021) - A Subcell Finite Volume Positivity-Preserving Limiter for DGSEM Discretizations - of the Euler Equations - [arXiv: 2102.06017](https://arxiv.org/abs/2102.06017) -""" -function initial_condition_kelvin_helmholtz_instability(x, t, - equations::CompressibleEulerEquations2D) - # change discontinuity to tanh - # typical resolution 128^2, 256^2 - # domain size is [-1,+1]^2 - RealT = eltype(x) - slope = 15 - B = tanh(slope * x[2] + 7.5f0) - tanh(slope * x[2] - 7.5f0) - rho = 0.5f0 + 0.75f0 * B - v1 = 0.5f0 * (B - 1) - v2 = convert(RealT, 0.1) * sinpi(2 * x[1]) - p = 1 - return prim2cons(SVector(rho, v1, v2, p), equations) -end -initial_condition = initial_condition_kelvin_helmholtz_instability - -surface_flux = flux_lax_friedrichs -volume_flux = flux_central -polydeg = 3 -basis = LobattoLegendreBasis(polydeg) -volume_integral = VolumeIntegralEntropyCorrection(equations, basis; - volume_flux_dg = volume_flux, - volume_flux_fv = surface_flux, - scaling = 2.0) -solver = DGSEM(basis, surface_flux, volume_integral) - -coordinates_min = (-1.0, -1.0) -coordinates_max = (1.0, 1.0) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 6, - n_cells_max = 100_000) -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) - -############################################################################### -# ODE solvers, callbacks etc. - -tspan = (0.0, 5.0) -ode = semidiscretize(semi, tspan) - -summary_callback = SummaryCallback() - -analysis_interval = 100 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval) - -alive_callback = AliveCallback(analysis_interval = analysis_interval) - -callbacks = CallbackSet(summary_callback, - analysis_callback, - alive_callback) - -############################################################################### -# run the simulation - -sol = solve(ode, SSPRK43(); - abstol = 1e-6, reltol = 1e-4, - ode_default_options()..., callback = callbacks); From 81f334cbd3cfc59705738951ee7f754efb2cf237 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Tue, 27 Jan 2026 23:23:09 -0600 Subject: [PATCH 120/360] add KHI test --- test/test_tree_2d_euler.jl | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/test/test_tree_2d_euler.jl b/test/test_tree_2d_euler.jl index 684f150a290..6bcb5b17167 100644 --- a/test/test_tree_2d_euler.jl +++ b/test/test_tree_2d_euler.jl @@ -581,6 +581,31 @@ end @test_allocations(Trixi.rhs!, semi, sol, 1000) end +@trixi_testset "elixir_euler_kelvin_helmholtz_instability.jl (VolumeIntegralEntropyCorrection)" begin + @test_trixi_include(joinpath(EXAMPLES_DIR, + "elixir_euler_kelvin_helmholtz_instability.jl"), + # adding `scaling = 2` increases the amount of subcell FV blended in by + # a factor of 2. If this is not added, the KHI simulation crashes with a + # positivity violation at some time t < 3. + volume_integral=VolumeIntegralEntropyCorrection(equations, + basis; + volume_flux_dg = volume_flux, + volume_flux_fv = surface_flux, + scaling = 2.0), + l2=[ + 0.5598004443337804, + 0.19783160019699073, + 0.22886409540262306, + 0.17616905595853216 + ], + linf=[ + 2.2756723403007273, + 0.7899848710261611, + 0.7613239119300793, + 0.6332611254490705 + ]) +end + @trixi_testset "elixir_euler_kelvin_helmholtz_instability_amr.jl" begin @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_kelvin_helmholtz_instability_amr.jl"), From 9efc34a05f821a42315d8ee1b57d1a507dbf22db Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Tue, 27 Jan 2026 23:23:18 -0600 Subject: [PATCH 121/360] add modified sod test --- ... elixir_euler_modified_sod_entropy_correction.jl} | 0 test/test_tree_1d_euler.jl | 12 ++++++++++++ 2 files changed, 12 insertions(+) rename examples/tree_1d_dgsem/{elixir_euler_entropy_correction.jl => elixir_euler_modified_sod_entropy_correction.jl} (100%) diff --git a/examples/tree_1d_dgsem/elixir_euler_entropy_correction.jl b/examples/tree_1d_dgsem/elixir_euler_modified_sod_entropy_correction.jl similarity index 100% rename from examples/tree_1d_dgsem/elixir_euler_entropy_correction.jl rename to examples/tree_1d_dgsem/elixir_euler_modified_sod_entropy_correction.jl diff --git a/test/test_tree_1d_euler.jl b/test/test_tree_1d_euler.jl index a42ca4f785a..8e5d24cfd56 100644 --- a/test/test_tree_1d_euler.jl +++ b/test/test_tree_1d_euler.jl @@ -620,6 +620,18 @@ end @test_allocations(Trixi.rhs!, semi, sol, 1000) end +@trixi_testset "elixir_euler_modified_sod_entropy_correction.jl" begin + @test_trixi_include(joinpath(EXAMPLES_DIR, + "elixir_euler_modified_sod_entropy_correction.jl"), + tspan=(0.0, 0.1), + l2=[0.17918607115375737, 0.30210797213211904, 0.5919230042600214], + linf=[0.6787210680110314, 0.8094457930038574, 1.9399759515642199]) + + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + @test_allocations(Trixi.rhs!, semi, sol, 1000) +end + @trixi_testset "elixir_euler_nonideal_density_wave.jl with entropy correction" begin @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_nonideal_density_wave.jl"), From 3a4f0361300dbed91bcfa3d585276044daebf2ef Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Wed, 28 Jan 2026 00:01:44 -0600 Subject: [PATCH 122/360] remove commented out code --- src/callbacks_step/analysis_dg2d.jl | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/callbacks_step/analysis_dg2d.jl b/src/callbacks_step/analysis_dg2d.jl index 25a0364ccf9..e52e18c2344 100644 --- a/src/callbacks_step/analysis_dg2d.jl +++ b/src/callbacks_step/analysis_dg2d.jl @@ -202,7 +202,7 @@ function integrate_against_entropy_variables(du_local, u, element, weights[i] * weights[j] * dot(cons2entropy(u_node, equations), du_node) end - # return integral + return integral * jacobian_1d end @@ -226,7 +226,6 @@ function surface_integral(func::Func, u, element, mesh::TreeMesh{2}, equations, (func(u_right, 2, equations) - func(u_left, 2, equations)) end - # return surface_integral jacobian_1d = inv(cache.elements.inverse_jacobian[element]) # O(h) return surface_integral * jacobian_1d end From 71540460fc73c37d048a7d1176c046e63a16fd2e Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Wed, 28 Jan 2026 15:55:19 -0600 Subject: [PATCH 123/360] MVec -> MMat --- src/solvers/dgsem_tree/indicators_1d.jl | 4 ++-- src/solvers/dgsem_tree/indicators_2d.jl | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/solvers/dgsem_tree/indicators_1d.jl b/src/solvers/dgsem_tree/indicators_1d.jl index ed2a503e130..0004c8b3805 100644 --- a/src/solvers/dgsem_tree/indicators_1d.jl +++ b/src/solvers/dgsem_tree/indicators_1d.jl @@ -192,13 +192,13 @@ end function create_cache(::Type{IndicatorEntropyCorrection}, equations::AbstractEquations{1}, basis::LobattoLegendreBasis) uEltype = real(basis) - MVec = MMatrix{nvariables(equations), nnodes(basis), uEltype} + MMat = MMatrix{nvariables(equations), nnodes(basis), uEltype} # stores the blending coefficients alpha = Vector{uEltype}() # container for elementwise volume integrals - indicator_threaded = MVec[MVec(undef) for _ in 1:Threads.maxthreadid()] + indicator_threaded = MMat[MMat(undef) for _ in 1:Threads.maxthreadid()] return (; alpha, indicator_threaded) end diff --git a/src/solvers/dgsem_tree/indicators_2d.jl b/src/solvers/dgsem_tree/indicators_2d.jl index 0a64ee2ea09..5a8c562b3be 100644 --- a/src/solvers/dgsem_tree/indicators_2d.jl +++ b/src/solvers/dgsem_tree/indicators_2d.jl @@ -27,13 +27,13 @@ end function create_cache(::Type{IndicatorEntropyCorrection}, equations::AbstractEquations{2}, basis::LobattoLegendreBasis) uEltype = real(basis) - MVec = MArray{Tuple{nvariables(equations), nnodes(basis), nnodes(basis)}, uEltype} + MMat = MArray{Tuple{nvariables(equations), nnodes(basis), nnodes(basis)}, uEltype} # stores the blending coefficients alpha = Vector{uEltype}() # container for elementwise volume integrals - indicator_threaded = MVec[MVec(undef) for _ in 1:Threads.maxthreadid()] + indicator_threaded = MMat[MMat(undef) for _ in 1:Threads.maxthreadid()] return (; alpha, indicator_threaded) end From 30f526e9cf19aca994c2940b81b1ed41821c26ba Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Wed, 28 Jan 2026 16:02:06 -0600 Subject: [PATCH 124/360] add docstrings --- src/equations/compressible_euler_1d.jl | 10 ++++++++++ src/equations/compressible_euler_2d.jl | 10 ++++++++++ src/equations/nonideal_compressible_euler_1d.jl | 6 ++++++ 3 files changed, 26 insertions(+) diff --git a/src/equations/compressible_euler_1d.jl b/src/equations/compressible_euler_1d.jl index 86c74bed8bd..0ad283470b8 100644 --- a/src/equations/compressible_euler_1d.jl +++ b/src/equations/compressible_euler_1d.jl @@ -1148,6 +1148,16 @@ of total energy and kinetic energy. return energy_total(cons, equations) - energy_kinetic(cons, equations) end +""" + entropy_potential(u, orientation::Int, equations::CompressibleEulerEquations1D) + +Calculate the entropy potential, which for the compressible Euler equations is simply +momentum for the choice of entropy ``S(u) = \frac{\rho s}{\gamma - 1}``. + +"Entropy stable high order discontinuous Galerkin methods with suitable quadrature rules +for hyperbolic conservation laws" by Chen and Shu (2017). + +""" @inline function entropy_potential(u, orientation::Int, equations::CompressibleEulerEquations1D) return u[2] diff --git a/src/equations/compressible_euler_2d.jl b/src/equations/compressible_euler_2d.jl index cc667c9d918..36a144e6d91 100644 --- a/src/equations/compressible_euler_2d.jl +++ b/src/equations/compressible_euler_2d.jl @@ -2230,6 +2230,16 @@ end return energy_total(cons, equations) - energy_kinetic(cons, equations) end +""" + entropy_potential(u, orientation::Int, equations::CompressibleEulerEquations1D) + +Calculate the entropy potential, which for the compressible Euler equations is simply +the momentum for the choice of entropy ``S(u) = \frac{\rho s}{\gamma - 1}``. + +"Entropy stable high order discontinuous Galerkin methods with suitable quadrature rules +for hyperbolic conservation laws" by Chen and Shu (2017). + +""" @inline function entropy_potential(u, orientation::Int, equations::CompressibleEulerEquations2D) if orientation == 1 diff --git a/src/equations/nonideal_compressible_euler_1d.jl b/src/equations/nonideal_compressible_euler_1d.jl index 02ecc13e6ab..d7e9d14c4a3 100644 --- a/src/equations/nonideal_compressible_euler_1d.jl +++ b/src/equations/nonideal_compressible_euler_1d.jl @@ -352,6 +352,12 @@ end return rho_e end +""" + entropy_potential(u, orientation::Int, equations::CompressibleEulerEquations1D) + +Calculate the entropy potential, which for the compressible Euler equations with general +EOS is ``p v_i / T`` for the choice of entropy ``S(u) = \rho s``. +""" @inline function entropy_potential(u, orientation::Int, equations::NonIdealCompressibleEulerEquations1D) eos = equations.equation_of_state From e40ded1d3a0b59f48bed44528d2f19f83a552452 Mon Sep 17 00:00:00 2001 From: Jesse Chan <1156048+jlchan@users.noreply.github.com> Date: Wed, 28 Jan 2026 16:02:38 -0600 Subject: [PATCH 125/360] Update src/solvers/dgsem/calc_volume_integral.jl Co-authored-by: Daniel Doehring --- src/solvers/dgsem/calc_volume_integral.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/solvers/dgsem/calc_volume_integral.jl b/src/solvers/dgsem/calc_volume_integral.jl index bbdba31e57d..0270bf814a1 100644 --- a/src/solvers/dgsem/calc_volume_integral.jl +++ b/src/solvers/dgsem/calc_volume_integral.jl @@ -193,7 +193,7 @@ function calc_volume_integral!(du, u, mesh, du_element = du_element_threaded[Threads.threadid()] @views du_element .= du[.., element] - # Reset weak form volume integral + # Reset pure flux-differencing volume integral du[.., element] .= zero(eltype(du)) # Calculate FV volume integral contribution From ff815fff767d0d30547f80d3fed813484a44ab03 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Wed, 28 Jan 2026 16:22:10 -0600 Subject: [PATCH 126/360] fix docstrings --- src/equations/compressible_euler_1d.jl | 2 +- src/equations/compressible_euler_2d.jl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/equations/compressible_euler_1d.jl b/src/equations/compressible_euler_1d.jl index 0ad283470b8..98c3448ac44 100644 --- a/src/equations/compressible_euler_1d.jl +++ b/src/equations/compressible_euler_1d.jl @@ -1148,7 +1148,7 @@ of total energy and kinetic energy. return energy_total(cons, equations) - energy_kinetic(cons, equations) end -""" +@doc raw""" entropy_potential(u, orientation::Int, equations::CompressibleEulerEquations1D) Calculate the entropy potential, which for the compressible Euler equations is simply diff --git a/src/equations/compressible_euler_2d.jl b/src/equations/compressible_euler_2d.jl index 36a144e6d91..55642aff4dd 100644 --- a/src/equations/compressible_euler_2d.jl +++ b/src/equations/compressible_euler_2d.jl @@ -2230,7 +2230,7 @@ end return energy_total(cons, equations) - energy_kinetic(cons, equations) end -""" +@doc raw""" entropy_potential(u, orientation::Int, equations::CompressibleEulerEquations1D) Calculate the entropy potential, which for the compressible Euler equations is simply From cfab41f1450db92f16b6c13207a890ca7097e9f6 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Wed, 28 Jan 2026 16:22:28 -0600 Subject: [PATCH 127/360] use standard Array instead of MArray --- src/solvers/dgsem_tree/indicators_2d.jl | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/solvers/dgsem_tree/indicators_2d.jl b/src/solvers/dgsem_tree/indicators_2d.jl index 5a8c562b3be..db4bd66ff56 100644 --- a/src/solvers/dgsem_tree/indicators_2d.jl +++ b/src/solvers/dgsem_tree/indicators_2d.jl @@ -27,13 +27,12 @@ end function create_cache(::Type{IndicatorEntropyCorrection}, equations::AbstractEquations{2}, basis::LobattoLegendreBasis) uEltype = real(basis) - MMat = MArray{Tuple{nvariables(equations), nnodes(basis), nnodes(basis)}, uEltype} # stores the blending coefficients alpha = Vector{uEltype}() # container for elementwise volume integrals - indicator_threaded = MMat[MMat(undef) for _ in 1:Threads.maxthreadid()] + indicator_threaded = [zeros(uEltype, nvariables(equations), nnodes(basis), nnodes(basis)) for _ in 1:Threads.maxthreadid()] return (; alpha, indicator_threaded) end From ba40da92fd183ca70f390216b07fc24e48f8dcad Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Wed, 28 Jan 2026 16:53:58 -0600 Subject: [PATCH 128/360] add dropped comma --- src/Trixi.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Trixi.jl b/src/Trixi.jl index b1c1983278a..48a88dfb0c1 100644 --- a/src/Trixi.jl +++ b/src/Trixi.jl @@ -252,9 +252,9 @@ export density, pressure, density_pressure, velocity, temperature, equilibrium_distribution, waterheight, waterheight_pressure export entropy, entropy_thermodynamic, entropy_math, entropy_guermond_etal, - energy_total, energy_kinetic, energy_internal, entropy_potential -energy_magnetic, cross_helicity, magnetic_field, divergence_cleaning_field, -enstrophy, vorticity + energy_total, energy_kinetic, energy_internal, entropy_potential, + energy_magnetic, cross_helicity, magnetic_field, divergence_cleaning_field, + enstrophy, vorticity export lake_at_rest_error export ncomponents, eachcomponent export have_constant_speed From 65c8e6e8150be160f140190fc5f6ae8b1c9d9041 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Wed, 28 Jan 2026 16:54:08 -0600 Subject: [PATCH 129/360] formatting --- src/solvers/dgsem_tree/indicators_2d.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/solvers/dgsem_tree/indicators_2d.jl b/src/solvers/dgsem_tree/indicators_2d.jl index db4bd66ff56..1f98af1721b 100644 --- a/src/solvers/dgsem_tree/indicators_2d.jl +++ b/src/solvers/dgsem_tree/indicators_2d.jl @@ -32,7 +32,8 @@ function create_cache(::Type{IndicatorEntropyCorrection}, alpha = Vector{uEltype}() # container for elementwise volume integrals - indicator_threaded = [zeros(uEltype, nvariables(equations), nnodes(basis), nnodes(basis)) for _ in 1:Threads.maxthreadid()] + indicator_threaded = [zeros(uEltype, nvariables(equations), nnodes(basis), + nnodes(basis)) for _ in 1:Threads.maxthreadid()] return (; alpha, indicator_threaded) end From b46fc66b664e41648fba90199f0a8f2bad0b0fb5 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Thu, 29 Jan 2026 13:57:56 -0600 Subject: [PATCH 130/360] flip sign of entropy residual --- src/solvers/dgsem/calc_volume_integral.jl | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/solvers/dgsem/calc_volume_integral.jl b/src/solvers/dgsem/calc_volume_integral.jl index 0270bf814a1..c8d835ad867 100644 --- a/src/solvers/dgsem/calc_volume_integral.jl +++ b/src/solvers/dgsem/calc_volume_integral.jl @@ -184,11 +184,12 @@ function calc_volume_integral!(du, u, mesh, element, mesh, equations, dg, cache) - # this quantity should be ≥ 0 for an entropy stable discretization - entropy_residual = volume_integral_entropy_vars + - surface_integral_entropy_potential + # this quantity should be ≤ 0 for an entropy stable volume integral, and + # exactly zero for an entropy conservative volume integral + entropy_residual = -(volume_integral_entropy_vars + + surface_integral_entropy_potential) - if entropy_residual < 0 + if entropy_residual > 0 # Store "high order" result du_element = du_element_threaded[Threads.threadid()] @views du_element .= du[.., element] @@ -211,8 +212,8 @@ function calc_volume_integral!(du, u, mesh, dg, cache) # calculate blending factor - ratio = regularized_ratio(entropy_residual, entropy_dissipation) - theta = max(0, min(1, scaling * ratio)) + ratio = regularized_ratio(-entropy_residual, entropy_dissipation) + theta = min(1, scaling * ratio) # TODO: replacing this with a differentiable version of `min` # save blending coefficient for visualization alpha[element] = theta From e0ae2ba778915aaf3c022c94112463e3562b6c9d Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Thu, 29 Jan 2026 13:58:05 -0600 Subject: [PATCH 131/360] replace 1e-15 with eps --- src/solvers/dgsem/calc_volume_integral.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/solvers/dgsem/calc_volume_integral.jl b/src/solvers/dgsem/calc_volume_integral.jl index c8d835ad867..b3290dd7503 100644 --- a/src/solvers/dgsem/calc_volume_integral.jl +++ b/src/solvers/dgsem/calc_volume_integral.jl @@ -156,7 +156,7 @@ function calc_volume_integral!(du, u, mesh, return nothing end -@inline regularized_ratio(a, b) = a * b / (1e-15 + b^2) +@inline regularized_ratio(a, b) = a * b / (eps(b) + b^2) function calc_volume_integral!(du, u, mesh, have_nonconservative_terms, equations, From 7be2a14cdaad03968737d3e21161323a77387e43 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Thu, 29 Jan 2026 14:00:40 -0600 Subject: [PATCH 132/360] remove experimental elixir --- .../elixir_euler_nonideal_flux_epec.jl | 116 ------------------ 1 file changed, 116 deletions(-) delete mode 100644 examples/tree_1d_dgsem/elixir_euler_nonideal_flux_epec.jl diff --git a/examples/tree_1d_dgsem/elixir_euler_nonideal_flux_epec.jl b/examples/tree_1d_dgsem/elixir_euler_nonideal_flux_epec.jl deleted file mode 100644 index 7825d9dcc6c..00000000000 --- a/examples/tree_1d_dgsem/elixir_euler_nonideal_flux_epec.jl +++ /dev/null @@ -1,116 +0,0 @@ -using OrdinaryDiffEqLowStorageRK -using Trixi -using Trixi: ForwardDiff - -############################################################################### -# semidiscretization of the compressible Euler equations - -eos = PengRobinson() -equations = NonIdealCompressibleEulerEquations1D(eos) - -@inline function drho_e_dp_at_const_rho(V, T, eos::Trixi.AbstractEquationOfState) - rho = inv(V) - dpdT_V, _dpdV_T = Trixi.calc_pressure_derivatives(V, T, eos) - c_v = Trixi.heat_capacity_constant_volume(V, T, eos) - - # (∂(ρe)/∂p)|ρ = ρ c_v / (∂p/∂T)|V - return (rho * c_v) / dpdT_V -end - -@inline function flux_epec(u_ll, u_rr, orientation::Int, - equations::NonIdealCompressibleEulerEquations1D) - eos = equations.equation_of_state - V_ll, v1_ll, T_ll = cons2prim(u_ll, equations) - V_rr, v1_rr, T_rr = cons2prim(u_rr, equations) - - rho_ll = u_ll[1] - rho_rr = u_rr[1] - rho_e_ll = Trixi.internal_energy_density(u_ll, equations) - rho_e_rr = Trixi.internal_energy_density(u_rr, equations) - p_ll = pressure(V_ll, T_ll, eos) - p_rr = pressure(V_rr, T_rr, eos) - - rho_avg = 0.5f0 * (rho_ll + rho_rr) - v1_avg = 0.5f0 * (v1_ll + v1_rr) - p_avg = 0.5f0 * (p_ll + p_rr) - rho_e_avg = 0.5f0 * (rho_e_ll + rho_e_rr) - p_v1_avg = 0.5f0 * (p_ll * v1_rr + p_rr * v1_ll) - - # chain rule from Terashima - drho_e_drho_p_ll = Trixi.drho_e_drho_at_const_p(V_ll, T_ll, eos) - drho_e_drho_p_rr = Trixi.drho_e_drho_at_const_p(V_rr, T_rr, eos) - drho_e_drho_p_avg = 0.5f0 * (drho_e_drho_p_ll + drho_e_drho_p_rr) - drho_e_drho_p_rho_avg = 0.5f0 * (drho_e_drho_p_ll * rho_ll + drho_e_drho_p_rr * rho_rr) - - # @variables aL bL aR bR - # drho_e_drho_p_avg * rho_avg - drho_e_drho_p_rho_avg - # {a}{b} - {ab} = (aL + aR)(bL + bR) - (aL*bL + aR * bR) - # (aL*bL + aL * bR + aR * bL + a - - rho_e_jump = rho_e_rr - rho_e_ll - rho_jump = rho_rr - rho_ll - p_jump = p_rr - p_ll - - drho_e_dp_at_const_rho_ll = drho_e_dp_at_const_rho(V_ll, T_ll, eos) - drho_e_dp_at_const_rho_rr = drho_e_dp_at_const_rho(V_rr, T_rr, eos) - drho_e_dp_at_const_rho_avg = 0.5f0 * (drho_e_dp_at_const_rho_ll + - drho_e_dp_at_const_rho_rr) - num = (rho_e_jump - drho_e_drho_p_avg * rho_jump - drho_e_dp_at_const_rho_avg * p_jump) - den = drho_e_drho_p_rr - drho_e_drho_p_ll - rho_avg = rho_avg - num * den / (den^2 + eps(typeof(den))) - rho_e_avg = (rho_e_avg + drho_e_drho_p_avg * rho_avg - drho_e_drho_p_rho_avg) - - # Ignore orientation since it is always "1" in 1D - f_rho = rho_avg * v1_avg - f_rho_v1 = rho_avg * v1_avg * v1_avg + p_avg - f_rho_E = rho_e_avg * v1_avg + rho_avg * 0.5f0 * (v1_ll * v1_rr) * v1_avg + p_v1_avg - - return SVector(f_rho, f_rho_v1, f_rho_E) -end - -initial_condition = Trixi.initial_condition_transcritical_wave - -volume_flux = flux_epec -volume_flux = flux_terashima_etal -solver = DGSEM(polydeg = 3, volume_integral = VolumeIntegralFluxDifferencing(volume_flux), - surface_flux = flux_lax_friedrichs) - -coordinates_min = -0.5 -coordinates_max = 0.5 -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 3, - n_cells_max = 30_000) - -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) - -############################################################################### -# ODE solvers, callbacks etc. - -tspan = (0.0, 5e-4) -ode = semidiscretize(semi, tspan) - -summary_callback = SummaryCallback() - -analysis_interval = 2000 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval) - -alive_callback = AliveCallback(analysis_interval = analysis_interval) - -save_solution = SaveSolutionCallback(interval = 100, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) - -stepsize_callback = StepsizeCallback(cfl = 0.5) - -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution, - stepsize_callback) - -############################################################################### -# run the simulation - -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false); - dt = stepsize_callback(ode), # solve needs some value here but it will be overwritten by the stepsize_callback - ode_default_options()..., callback = callbacks); From 19d14ac7c7427f867b5393fa6261aac54ec2c019 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Thu, 29 Jan 2026 14:21:01 -0600 Subject: [PATCH 133/360] add PR EOS, initial conditions, and tests add transcritical shock elixir --- ...ixir_euler_nonideal_transcritical_shock.jl | 59 +++++++ src/Trixi.jl | 5 +- .../equation_of_state_peng_robinson.jl | 159 ++++++++++++++++++ src/equations/equations_of_state.jl | 15 +- .../nonideal_compressible_euler_1d.jl | 138 +++++++++++++++ test/test_tree_1d_euler.jl | 39 +++++ 6 files changed, 410 insertions(+), 5 deletions(-) create mode 100644 examples/tree_1d_dgsem/elixir_euler_nonideal_transcritical_shock.jl create mode 100644 src/equations/equation_of_state_peng_robinson.jl diff --git a/examples/tree_1d_dgsem/elixir_euler_nonideal_transcritical_shock.jl b/examples/tree_1d_dgsem/elixir_euler_nonideal_transcritical_shock.jl new file mode 100644 index 00000000000..3fe25a26589 --- /dev/null +++ b/examples/tree_1d_dgsem/elixir_euler_nonideal_transcritical_shock.jl @@ -0,0 +1,59 @@ +using OrdinaryDiffEqLowStorageRK +using Trixi + +############################################################################### +# semidiscretization of the compressible Euler equations + +eos = PengRobinson() +equations = NonIdealCompressibleEulerEquations1D(eos) + +initial_condition = initial_condition_transcritical_shock + +volume_flux = flux_central_terashima_etal +solver = DGSEM(polydeg = 3, volume_integral = VolumeIntegralFluxDifferencing(volume_flux), + surface_flux = flux_lax_friedrichs) + +coordinates_min = -0.5 +coordinates_max = 0.5 +mesh = TreeMesh(coordinates_min, coordinates_max, + initial_refinement_level = 6, + n_cells_max = 30_000, + periodicity = false) + +boundary_conditions = (x_neg = BoundaryConditionDirichlet(initial_condition), + x_pos = BoundaryConditionDirichlet(initial_condition)) + +semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, + boundary_conditions = boundary_conditions) + +############################################################################### +# ODE solvers, callbacks etc. + +tspan = (0.0, 5.0e-4) +ode = semidiscretize(semi, tspan) + +summary_callback = SummaryCallback() + +analysis_interval = 2000 +analysis_callback = AnalysisCallback(semi, interval = analysis_interval) + +alive_callback = AliveCallback(analysis_interval = analysis_interval) + +save_solution = SaveSolutionCallback(interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim) + +stepsize_callback = StepsizeCallback(cfl = 0.25) + +callbacks = CallbackSet(summary_callback, + analysis_callback, alive_callback, + save_solution, + stepsize_callback) + +############################################################################### +# run the simulation + +sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false); + dt = stepsize_callback(ode), # solve needs some value here but it will be overwritten by the stepsize_callback + ode_default_options()..., callback = callbacks); diff --git a/src/Trixi.jl b/src/Trixi.jl index 52187d035d7..7219b60038f 100644 --- a/src/Trixi.jl +++ b/src/Trixi.jl @@ -182,7 +182,10 @@ export AcousticPerturbationEquations2D, LinearElasticityEquations1D, PassiveTracerEquations -export NonIdealCompressibleEulerEquations1D, IdealGas, VanDerWaals +export NonIdealCompressibleEulerEquations1D +export IdealGas, VanDerWaals, PengRobinson +export initial_condition_Riemann_problem, initial_condition_transcritical_wave, + initial_condition_transcritical_shock export LaplaceDiffusion1D, LaplaceDiffusion2D, LaplaceDiffusion3D, LaplaceDiffusionEntropyVariables1D, LaplaceDiffusionEntropyVariables2D, diff --git a/src/equations/equation_of_state_peng_robinson.jl b/src/equations/equation_of_state_peng_robinson.jl new file mode 100644 index 00000000000..7f4376de42e --- /dev/null +++ b/src/equations/equation_of_state_peng_robinson.jl @@ -0,0 +1,159 @@ +# By default, Julia/LLVM does not use fused multiply-add operations (FMAs). +# Since these FMAs can increase the performance of many numerical algorithms, +# we need to opt-in explicitly. +# See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. +@muladd begin +#! format: noindent + +@doc raw""" + PengRobinson{RealT <: Real} <: AbstractEquationOfState + +This defines the Peng-Robinson equation of state +given by the pressure and internal energy relations +```math +p = \frac{R T}{V - b} - \frac{a(T)}{V^2 + 2bV - b^2}, \quad e = c_{v,0} T + K(a(T) - Ta'(T)) +``` +where `V = inv(rho)` and auxiliary expressions for `a(T)` and `K` are given by +```math +a(T) = a_0\left(1 + \kappa \left 1 - \sqrt{\frac{T}{T_0}}\right)\right)^2, \quad +K = \frac{1}{b 2\sqrt{2}} \log\left( \frac{V + (1 - b \sqrt{2})}{V + (1 + b\sqrt{2})}\right). +``` +Moreover, `c_v = c_{v,0} - K T a''(T)`. + +All expressions used here are taken from "An entropy-stable hybrid scheme for simulations +of transcritical real-fluid flows" by Ma, Lv, Ihme (2017). + + +See also "Towards a fully well-balanced and entropy-stable scheme for the Euler equations with +gravity: preserving isentropic steady solutions" by Berthon, Michel-Dansac, and Thomann (2024). + + +""" +struct PengRobinson{RealT <: Real} <: AbstractEquationOfState + R::RealT + a0::RealT + b::RealT + cv0::RealT + kappa::RealT + T0::RealT + inv2sqrt2b::RealT + one_minus_sqrt2_b::RealT + one_plus_sqrt2_b::RealT + function PengRobinson(R, a0, b, cv0, kappa, T0) + inv2sqrt2b = inv(2 * sqrt(2) * b) + one_minus_sqrt2_b = (1 - sqrt(2)) * b + one_plus_sqrt2_b = (1 + sqrt(2)) * b + return new{typeof(R)}(R, a0, b, cv0, kappa, T0, + inv2sqrt2b, one_minus_sqrt2_b, one_plus_sqrt2_b) + end +end + +""" + PengRobinson(; RealT = Float64) + +By default, the Peng-Robinson parameters are in mass basis for N2. +""" +function PengRobinson(; RealT = Float64) + Rgas = 8.31446261815324 + molar_mass_N2 = 0.02801 * 1000 # kg/m3 + R = Rgas * 1000 / molar_mass_N2 + pc = 3.40e6 + Tc = 126.2 + omega = 0.0372 + cv0 = 743.2 + b = 0.077796 * R * Tc / pc + a0 = 0.457236 * (R * Tc)^2 / pc + kappa = 0.37464 + 1.54226 * omega - 0.26992 * omega^2 + return PengRobinson(RealT.((R, a0, b, cv0, kappa, Tc))...) +end + +# the default tolerance of 10 * eps() does not converge for most Peng-Robinson examples, +# so we choose a looser tolerance here. Researchers at the US Naval Research Lab noted +# that they typically just use 8 fixed Newton iterations for Peng-Robinson. +eos_newton_tol(eos::PengRobinson) = 1e-8 + +""" + pressure(V, T, eos::PengRobinson) + +Computes pressure for a Peng-Robinson gas from specific volume `V` and temperature `T`, +see also [`NonIdealCompressibleEulerEquations1D`](@ref). +""" +function pressure(V, T, eos::PengRobinson) + (; R, b) = eos + p = R * T / (V - b) - a(T, eos) / (V^2 + 2 * b * V - b^2) + return p +end + +""" + energy_internal(V, T, eos::PengRobinson) + +Computes internal energy for a Peng-Robinson gas from specific volume `V` and temperature `T` as +``e = c_{v,0} T + K_1 (a(T) - T a'(T))``. +""" +function energy_internal(V, T, eos::PengRobinson) + (; cv0) = eos + K1 = calc_K1(V, eos) + e = cv0 * T + K1 * (a(T, eos) - T * da(T, eos)) + return e +end + +@inline function heat_capacity_constant_volume(V, T, eos::PengRobinson) + (; cv0) = eos + K1 = calc_K1(V, eos) + cv = cv0 - K1 * T * d2a(T, eos) + return cv +end + +function entropy_specific(V, T, eos::PengRobinson) + (; cv0, R, b) = eos + + # The specific entropy is defined up to some reference value s0, which is + # arbitrarily set to zero here. + K1 = calc_K1(V, eos) + return cv0 * log(T) + R * log(V - b) - da(T, eos) * K1 +end + +function speed_of_sound(V, T, eos::PengRobinson) + (; cv0, R, b) = eos + + dpdT_V, dpdV_T = calc_pressure_derivatives(V, T, eos) + + # calculate ratio of specific heats + K1 = calc_K1(V, eos) + d2aT = d2a(T, eos) + cp0 = cv0 + R + cv = cv0 - K1 * T * d2aT + cp = cp0 - R - K1 * T * d2aT - T * dpdT_V^2 / dpdV_T + gamma = cp / cv + + # calculate bulk modulus, which should be positive + # for admissible thermodynamic states. + kappa_T = -inv(V * dpdV_T) + c2 = gamma * V / kappa_T + return sqrt(c2) +end + +function calc_pressure_derivatives(V, T, eos::PengRobinson) + (; R, b) = eos + denom = (V^2 + 2 * b * V - b^2) + RdivVb = R / (V - b) + dpdT_V = RdivVb - da(T, eos) / denom + dpdV_T = -RdivVb * T / (V - b) * + (1 - 2 * a(T, eos) / (R * T * (V + b) * (denom / (V^2 - b^2))^2)) + return dpdT_V, dpdV_T +end + +# The following are auxiliary functions used in calculating the PR EOS +@inline function a(T, eos::PengRobinson) + (; a0, kappa, T0) = eos + return a0 * (1 + kappa * (1 - sqrt(T / T0)))^2 +end +@inline da(T, eos) = ForwardDiff.derivative(T -> a(T, eos), T) +@inline d2a(T, eos) = ForwardDiff.derivative(T -> da(T, eos), T) + +@inline function calc_K1(V, eos::PengRobinson) + (; inv2sqrt2b, one_minus_sqrt2_b, one_plus_sqrt2_b) = eos + K1 = inv2sqrt2b * log((V + one_minus_sqrt2_b) / (V + one_plus_sqrt2_b)) + return K1 +end +end # @muladd diff --git a/src/equations/equations_of_state.jl b/src/equations/equations_of_state.jl index 3d03ada3174..c653c8dfc4c 100644 --- a/src/equations/equations_of_state.jl +++ b/src/equations/equations_of_state.jl @@ -26,6 +26,7 @@ abstract type AbstractEquationOfState end include("equation_of_state_ideal_gas.jl") include("equation_of_state_vdw.jl") +include("equation_of_state_peng_robinson.jl") ####################################################### # @@ -46,14 +47,18 @@ end return ForwardDiff.derivative(T -> energy_internal(V, T, eos), T) end +# this is used in [`flux_terashima_etal`](@ref) and [`flux_terashima_etal_central`](@ref) function calc_pressure_derivatives(V, T, eos) dpdV_T = ForwardDiff.derivative(V -> pressure(V, T, eos), V) dpdT_V = ForwardDiff.derivative(T -> pressure(V, T, eos), T) return dpdT_V, dpdV_T end -# relative tolerance for the Newton solver for temperature +# relative tolerance, initial guess, and maximum number of iterations +# for the Newton solver for temperature. eos_newton_tol(eos::AbstractEquationOfState) = 10 * eps() +eos_initial_temperature(V, e, eos::AbstractEquationOfState) = 1 +eos_newton_maxiter(eos) = 100 """ temperature(V, e, eos::AbstractEquationOfState; initial_T = 1.0, tol = eos_newton_tol(eos), @@ -63,8 +68,9 @@ Calculates the temperature as a function of specific volume `V` and internal ene by using Newton's method to determine `T` such that `energy_internal(V, T, eos) = e`. Note that the tolerance may need to be adjusted based on the specific equation of state. """ -function temperature(V, e, eos::AbstractEquationOfState; initial_T = 1.0, - tol = eos_newton_tol(eos), maxiter = 100) +function temperature(V, e, eos::AbstractEquationOfState; + initial_T = eos_initial_temperature(V, e, eos), + tol = eos_newton_tol(eos), maxiter = eos_newton_maxiter(eos)) T = initial_T de = energy_internal(V, T, eos) - e iter = 1 @@ -75,7 +81,8 @@ function temperature(V, e, eos::AbstractEquationOfState; initial_T = 1.0, # guarantee convergence of this iteration. de_dT_V = heat_capacity_constant_volume(V, T, eos) - T = T - de / de_dT_V + # guard against negative temperatures + T = max(tol, T - de / de_dT_V) iter += 1 end if iter == maxiter diff --git a/src/equations/nonideal_compressible_euler_1d.jl b/src/equations/nonideal_compressible_euler_1d.jl index 9add0ee7971..fc8b750a223 100644 --- a/src/equations/nonideal_compressible_euler_1d.jl +++ b/src/equations/nonideal_compressible_euler_1d.jl @@ -351,4 +351,142 @@ end rho_e = rho_e_total - 0.5f0 * rho_v1^2 / rho return rho_e end + +# The default amplitude and frequency k are consistent with initial_condition_density_wave +# for CompressibleEulerEquations1D. Note that this initial condition may not define admissible +# solution states for all non-ideal equations of state! +function initial_condition_density_wave(x, t, + equations::NonIdealCompressibleEulerEquations1D; + amplitude = 0.98, k = 2) + RealT = eltype(x) + eos = equations.equation_of_state + + v1 = convert(RealT, 0.1) + rho = 1 + convert(RealT, amplitude) * sinpi(k * (x[1] - v1 * t)) + p = 20 + + V = inv(rho) + + # invert for temperature given p, V + T = 1 + tol = 100 * eps(RealT) + dp = pressure(V, T, eos) - p + iter = 1 + while abs(dp) / abs(p) > tol && iter < 100 + dp = pressure(V, T, eos) - p + dpdT_V = ForwardDiff.derivative(T -> pressure(V, T, eos), T) + T = max(tol, T - dp / dpdT_V) + iter += 1 + end + if iter == 100 + println("Warning: solver for temperature(V, p) did not converge") + end + + return prim2cons(SVector(V, v1, T), equations) +end + +# The 1D Riemann problem from "An oscillation free shock-capturing method +# for compressible van der Waals supercritical fluid flows" by Pantano, +# Saurel, and Schmitt (2017). +# +# This is intended to be run with the `VanDerWaals(; gamma=1.4)` eos until +# final time 14e-3 on the domain [-12, 12]. +function initial_condition_Riemann_problem(x, t, + equations::NonIdealCompressibleEulerEquations1D{<:VanDerWaals}) + RealT = eltype(x) + eos = equations.equation_of_state + + if x[1] < 0 + rho, v1, p = SVector(497.417, 0, 4e7) + else + rho, v1, p = SVector(13.33117, 0, 4e6) # 6e6 + end + + V = inv(rho) + + # invert for temperature given p, V + T = RealT(1.0) + tol = 100 * eps(RealT) + dp = pressure(V, T, eos) - p + iter = 1 + while abs(dp) / abs(p) > tol && iter < 100 + dp = pressure(V, T, eos) - p + dpdT_V = ForwardDiff.derivative(T -> pressure(V, T, eos), T) + T = max(tol, T - dp / dpdT_V) + iter += 1 + end + if iter == 100 + println("Warning: solver for temperature(V, p) did not converge") + end + + return prim2cons(SVector(V, v1, T), equations) +end + +# the smooth Peng-Robinson N2 transcritical wave taken from "An entropy-stable hybrid scheme +# for simulations of transcritical real-fluid flows" by Ma, Ihme (2017). +# +function initial_condition_transcritical_wave(x, t, + equations::NonIdealCompressibleEulerEquations1D{<:PengRobinson}) + RealT = eltype(x) + eos = equations.equation_of_state + + rho_min, rho_max = 56.9, 793.1 + v1 = 100 + rho = 0.5 * (rho_min + rho_max) + + 0.5 * (rho_max - rho_min) * sin(2 * pi * (x[1] - v1 * t)) + p = 5e6 + + V = inv(rho) + + # invert for temperature given p, V + T = eos.T0 + tol = 100 * eps(RealT) + dp = pressure(V, T, eos) - p + iter = 1 + while abs(dp) / abs(p) > tol && iter < 100 + dp = pressure(V, T, eos) - p + dpdT_V = ForwardDiff.derivative(T -> pressure(V, T, eos), T) + T = max(tol, T - dp / dpdT_V) + iter += 1 + end + if iter == 100 + println("Warning: solver for temperature(V, p) did not converge") + end + + return prim2cons(SVector(V, v1, T), equations) +end + +# the Peng-Robinson N2 transcritical shock taken from "An entropy-stable hybrid scheme +# for simulations of transcritical real-fluid flows" by Ma, Ihme (2017). +# +function initial_condition_transcritical_shock(x, t, + equations::NonIdealCompressibleEulerEquations1D{<:PengRobinson}) + RealT = eltype(x) + eos = equations.equation_of_state + + if x[1] < 0 + rho, v1, p = SVector(800, 0, 60e6) + else + rho, v1, p = SVector(80, 0, 6e6) + end + + V = inv(rho) + + # invert for temperature given p, V + T = eos.T0 + tol = 100 * eps(RealT) + dp = pressure(V, T, eos) - p + iter = 1 + while abs(dp) / abs(p) > tol && iter < 100 + dp = pressure(V, T, eos) - p + dpdT_V = ForwardDiff.derivative(T -> pressure(V, T, eos), T) + T = max(tol, T - dp / dpdT_V) + iter += 1 + end + if iter == 100 + println("Warning: solver for temperature(V, p) did not converge") + end + + return prim2cons(SVector(V, v1, T), equations) +end end # @muladd diff --git a/test/test_tree_1d_euler.jl b/test/test_tree_1d_euler.jl index 3af0c38b64b..c475ceab92c 100644 --- a/test/test_tree_1d_euler.jl +++ b/test/test_tree_1d_euler.jl @@ -620,4 +620,43 @@ end @test_allocations(Trixi.rhs!, semi, sol, 1000) end +@trixi_testset "elixir_euler_nonideal_density_wave.jl (transcritical wave) with Peng Robinson" begin + @test_trixi_include(joinpath(EXAMPLES_DIR, + "elixir_euler_nonideal_density_wave.jl"), + solver=DGSEM(polydeg = 3, + volume_integral = VolumeIntegralFluxDifferencing(flux_terashima_etal), + surface_flux = flux_lax_friedrichs), + initial_condition=initial_condition_transcritical_wave, + tspan=(0.0, 0.001), + # note that rho_e_total errors are large because pressure is 5e6 + l2=[2.5190796911050598e-5, 0.0013782564599067785, 7.045132422388037], + linf=[ + 0.00014865356020266063, + 0.00764166860608384, + 27.349332988262177 + ]) + + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + @test_allocations(Trixi.rhs!, semi, sol, 1000) +end + +@trixi_testset "elixir_euler_nonideal_transcritical_shock.jl with Peng Robinson" begin + @test_trixi_include(joinpath(EXAMPLES_DIR, + "elixir_euler_nonideal_transcritical_shock.jl"), + initial_condition=initial_condition_transcritical_wave, + tspan=(0.0, 5e-5), + # note that rho_e_total errors are large because pressure is 5e6 + l2=[3.205713931725666e-5, 0.0024557000949388375, 7.814075349831272], + linf=[ + 0.00015527262569037248, + 0.006962752362596802, + 25.366882726550102 + ]) + + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + @test_allocations(Trixi.rhs!, semi, sol, 1000) +end + end # module From a5b6e4ac28f68a433fb17fd03a1386bf6a87c006 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Thu, 29 Jan 2026 14:22:56 -0600 Subject: [PATCH 134/360] Revert "add PR EOS, initial conditions, and tests" This reverts commit 19d14ac7c7427f867b5393fa6261aac54ec2c019. --- ...ixir_euler_nonideal_transcritical_shock.jl | 59 ------- src/Trixi.jl | 5 +- .../equation_of_state_peng_robinson.jl | 159 ------------------ src/equations/equations_of_state.jl | 15 +- .../nonideal_compressible_euler_1d.jl | 138 --------------- test/test_tree_1d_euler.jl | 39 ----- 6 files changed, 5 insertions(+), 410 deletions(-) delete mode 100644 examples/tree_1d_dgsem/elixir_euler_nonideal_transcritical_shock.jl delete mode 100644 src/equations/equation_of_state_peng_robinson.jl diff --git a/examples/tree_1d_dgsem/elixir_euler_nonideal_transcritical_shock.jl b/examples/tree_1d_dgsem/elixir_euler_nonideal_transcritical_shock.jl deleted file mode 100644 index 3fe25a26589..00000000000 --- a/examples/tree_1d_dgsem/elixir_euler_nonideal_transcritical_shock.jl +++ /dev/null @@ -1,59 +0,0 @@ -using OrdinaryDiffEqLowStorageRK -using Trixi - -############################################################################### -# semidiscretization of the compressible Euler equations - -eos = PengRobinson() -equations = NonIdealCompressibleEulerEquations1D(eos) - -initial_condition = initial_condition_transcritical_shock - -volume_flux = flux_central_terashima_etal -solver = DGSEM(polydeg = 3, volume_integral = VolumeIntegralFluxDifferencing(volume_flux), - surface_flux = flux_lax_friedrichs) - -coordinates_min = -0.5 -coordinates_max = 0.5 -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 6, - n_cells_max = 30_000, - periodicity = false) - -boundary_conditions = (x_neg = BoundaryConditionDirichlet(initial_condition), - x_pos = BoundaryConditionDirichlet(initial_condition)) - -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - boundary_conditions = boundary_conditions) - -############################################################################### -# ODE solvers, callbacks etc. - -tspan = (0.0, 5.0e-4) -ode = semidiscretize(semi, tspan) - -summary_callback = SummaryCallback() - -analysis_interval = 2000 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval) - -alive_callback = AliveCallback(analysis_interval = analysis_interval) - -save_solution = SaveSolutionCallback(interval = 100, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) - -stepsize_callback = StepsizeCallback(cfl = 0.25) - -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution, - stepsize_callback) - -############################################################################### -# run the simulation - -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false); - dt = stepsize_callback(ode), # solve needs some value here but it will be overwritten by the stepsize_callback - ode_default_options()..., callback = callbacks); diff --git a/src/Trixi.jl b/src/Trixi.jl index 7219b60038f..52187d035d7 100644 --- a/src/Trixi.jl +++ b/src/Trixi.jl @@ -182,10 +182,7 @@ export AcousticPerturbationEquations2D, LinearElasticityEquations1D, PassiveTracerEquations -export NonIdealCompressibleEulerEquations1D -export IdealGas, VanDerWaals, PengRobinson -export initial_condition_Riemann_problem, initial_condition_transcritical_wave, - initial_condition_transcritical_shock +export NonIdealCompressibleEulerEquations1D, IdealGas, VanDerWaals export LaplaceDiffusion1D, LaplaceDiffusion2D, LaplaceDiffusion3D, LaplaceDiffusionEntropyVariables1D, LaplaceDiffusionEntropyVariables2D, diff --git a/src/equations/equation_of_state_peng_robinson.jl b/src/equations/equation_of_state_peng_robinson.jl deleted file mode 100644 index 7f4376de42e..00000000000 --- a/src/equations/equation_of_state_peng_robinson.jl +++ /dev/null @@ -1,159 +0,0 @@ -# By default, Julia/LLVM does not use fused multiply-add operations (FMAs). -# Since these FMAs can increase the performance of many numerical algorithms, -# we need to opt-in explicitly. -# See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. -@muladd begin -#! format: noindent - -@doc raw""" - PengRobinson{RealT <: Real} <: AbstractEquationOfState - -This defines the Peng-Robinson equation of state -given by the pressure and internal energy relations -```math -p = \frac{R T}{V - b} - \frac{a(T)}{V^2 + 2bV - b^2}, \quad e = c_{v,0} T + K(a(T) - Ta'(T)) -``` -where `V = inv(rho)` and auxiliary expressions for `a(T)` and `K` are given by -```math -a(T) = a_0\left(1 + \kappa \left 1 - \sqrt{\frac{T}{T_0}}\right)\right)^2, \quad -K = \frac{1}{b 2\sqrt{2}} \log\left( \frac{V + (1 - b \sqrt{2})}{V + (1 + b\sqrt{2})}\right). -``` -Moreover, `c_v = c_{v,0} - K T a''(T)`. - -All expressions used here are taken from "An entropy-stable hybrid scheme for simulations -of transcritical real-fluid flows" by Ma, Lv, Ihme (2017). - - -See also "Towards a fully well-balanced and entropy-stable scheme for the Euler equations with -gravity: preserving isentropic steady solutions" by Berthon, Michel-Dansac, and Thomann (2024). - - -""" -struct PengRobinson{RealT <: Real} <: AbstractEquationOfState - R::RealT - a0::RealT - b::RealT - cv0::RealT - kappa::RealT - T0::RealT - inv2sqrt2b::RealT - one_minus_sqrt2_b::RealT - one_plus_sqrt2_b::RealT - function PengRobinson(R, a0, b, cv0, kappa, T0) - inv2sqrt2b = inv(2 * sqrt(2) * b) - one_minus_sqrt2_b = (1 - sqrt(2)) * b - one_plus_sqrt2_b = (1 + sqrt(2)) * b - return new{typeof(R)}(R, a0, b, cv0, kappa, T0, - inv2sqrt2b, one_minus_sqrt2_b, one_plus_sqrt2_b) - end -end - -""" - PengRobinson(; RealT = Float64) - -By default, the Peng-Robinson parameters are in mass basis for N2. -""" -function PengRobinson(; RealT = Float64) - Rgas = 8.31446261815324 - molar_mass_N2 = 0.02801 * 1000 # kg/m3 - R = Rgas * 1000 / molar_mass_N2 - pc = 3.40e6 - Tc = 126.2 - omega = 0.0372 - cv0 = 743.2 - b = 0.077796 * R * Tc / pc - a0 = 0.457236 * (R * Tc)^2 / pc - kappa = 0.37464 + 1.54226 * omega - 0.26992 * omega^2 - return PengRobinson(RealT.((R, a0, b, cv0, kappa, Tc))...) -end - -# the default tolerance of 10 * eps() does not converge for most Peng-Robinson examples, -# so we choose a looser tolerance here. Researchers at the US Naval Research Lab noted -# that they typically just use 8 fixed Newton iterations for Peng-Robinson. -eos_newton_tol(eos::PengRobinson) = 1e-8 - -""" - pressure(V, T, eos::PengRobinson) - -Computes pressure for a Peng-Robinson gas from specific volume `V` and temperature `T`, -see also [`NonIdealCompressibleEulerEquations1D`](@ref). -""" -function pressure(V, T, eos::PengRobinson) - (; R, b) = eos - p = R * T / (V - b) - a(T, eos) / (V^2 + 2 * b * V - b^2) - return p -end - -""" - energy_internal(V, T, eos::PengRobinson) - -Computes internal energy for a Peng-Robinson gas from specific volume `V` and temperature `T` as -``e = c_{v,0} T + K_1 (a(T) - T a'(T))``. -""" -function energy_internal(V, T, eos::PengRobinson) - (; cv0) = eos - K1 = calc_K1(V, eos) - e = cv0 * T + K1 * (a(T, eos) - T * da(T, eos)) - return e -end - -@inline function heat_capacity_constant_volume(V, T, eos::PengRobinson) - (; cv0) = eos - K1 = calc_K1(V, eos) - cv = cv0 - K1 * T * d2a(T, eos) - return cv -end - -function entropy_specific(V, T, eos::PengRobinson) - (; cv0, R, b) = eos - - # The specific entropy is defined up to some reference value s0, which is - # arbitrarily set to zero here. - K1 = calc_K1(V, eos) - return cv0 * log(T) + R * log(V - b) - da(T, eos) * K1 -end - -function speed_of_sound(V, T, eos::PengRobinson) - (; cv0, R, b) = eos - - dpdT_V, dpdV_T = calc_pressure_derivatives(V, T, eos) - - # calculate ratio of specific heats - K1 = calc_K1(V, eos) - d2aT = d2a(T, eos) - cp0 = cv0 + R - cv = cv0 - K1 * T * d2aT - cp = cp0 - R - K1 * T * d2aT - T * dpdT_V^2 / dpdV_T - gamma = cp / cv - - # calculate bulk modulus, which should be positive - # for admissible thermodynamic states. - kappa_T = -inv(V * dpdV_T) - c2 = gamma * V / kappa_T - return sqrt(c2) -end - -function calc_pressure_derivatives(V, T, eos::PengRobinson) - (; R, b) = eos - denom = (V^2 + 2 * b * V - b^2) - RdivVb = R / (V - b) - dpdT_V = RdivVb - da(T, eos) / denom - dpdV_T = -RdivVb * T / (V - b) * - (1 - 2 * a(T, eos) / (R * T * (V + b) * (denom / (V^2 - b^2))^2)) - return dpdT_V, dpdV_T -end - -# The following are auxiliary functions used in calculating the PR EOS -@inline function a(T, eos::PengRobinson) - (; a0, kappa, T0) = eos - return a0 * (1 + kappa * (1 - sqrt(T / T0)))^2 -end -@inline da(T, eos) = ForwardDiff.derivative(T -> a(T, eos), T) -@inline d2a(T, eos) = ForwardDiff.derivative(T -> da(T, eos), T) - -@inline function calc_K1(V, eos::PengRobinson) - (; inv2sqrt2b, one_minus_sqrt2_b, one_plus_sqrt2_b) = eos - K1 = inv2sqrt2b * log((V + one_minus_sqrt2_b) / (V + one_plus_sqrt2_b)) - return K1 -end -end # @muladd diff --git a/src/equations/equations_of_state.jl b/src/equations/equations_of_state.jl index c653c8dfc4c..3d03ada3174 100644 --- a/src/equations/equations_of_state.jl +++ b/src/equations/equations_of_state.jl @@ -26,7 +26,6 @@ abstract type AbstractEquationOfState end include("equation_of_state_ideal_gas.jl") include("equation_of_state_vdw.jl") -include("equation_of_state_peng_robinson.jl") ####################################################### # @@ -47,18 +46,14 @@ end return ForwardDiff.derivative(T -> energy_internal(V, T, eos), T) end -# this is used in [`flux_terashima_etal`](@ref) and [`flux_terashima_etal_central`](@ref) function calc_pressure_derivatives(V, T, eos) dpdV_T = ForwardDiff.derivative(V -> pressure(V, T, eos), V) dpdT_V = ForwardDiff.derivative(T -> pressure(V, T, eos), T) return dpdT_V, dpdV_T end -# relative tolerance, initial guess, and maximum number of iterations -# for the Newton solver for temperature. +# relative tolerance for the Newton solver for temperature eos_newton_tol(eos::AbstractEquationOfState) = 10 * eps() -eos_initial_temperature(V, e, eos::AbstractEquationOfState) = 1 -eos_newton_maxiter(eos) = 100 """ temperature(V, e, eos::AbstractEquationOfState; initial_T = 1.0, tol = eos_newton_tol(eos), @@ -68,9 +63,8 @@ Calculates the temperature as a function of specific volume `V` and internal ene by using Newton's method to determine `T` such that `energy_internal(V, T, eos) = e`. Note that the tolerance may need to be adjusted based on the specific equation of state. """ -function temperature(V, e, eos::AbstractEquationOfState; - initial_T = eos_initial_temperature(V, e, eos), - tol = eos_newton_tol(eos), maxiter = eos_newton_maxiter(eos)) +function temperature(V, e, eos::AbstractEquationOfState; initial_T = 1.0, + tol = eos_newton_tol(eos), maxiter = 100) T = initial_T de = energy_internal(V, T, eos) - e iter = 1 @@ -81,8 +75,7 @@ function temperature(V, e, eos::AbstractEquationOfState; # guarantee convergence of this iteration. de_dT_V = heat_capacity_constant_volume(V, T, eos) - # guard against negative temperatures - T = max(tol, T - de / de_dT_V) + T = T - de / de_dT_V iter += 1 end if iter == maxiter diff --git a/src/equations/nonideal_compressible_euler_1d.jl b/src/equations/nonideal_compressible_euler_1d.jl index fc8b750a223..9add0ee7971 100644 --- a/src/equations/nonideal_compressible_euler_1d.jl +++ b/src/equations/nonideal_compressible_euler_1d.jl @@ -351,142 +351,4 @@ end rho_e = rho_e_total - 0.5f0 * rho_v1^2 / rho return rho_e end - -# The default amplitude and frequency k are consistent with initial_condition_density_wave -# for CompressibleEulerEquations1D. Note that this initial condition may not define admissible -# solution states for all non-ideal equations of state! -function initial_condition_density_wave(x, t, - equations::NonIdealCompressibleEulerEquations1D; - amplitude = 0.98, k = 2) - RealT = eltype(x) - eos = equations.equation_of_state - - v1 = convert(RealT, 0.1) - rho = 1 + convert(RealT, amplitude) * sinpi(k * (x[1] - v1 * t)) - p = 20 - - V = inv(rho) - - # invert for temperature given p, V - T = 1 - tol = 100 * eps(RealT) - dp = pressure(V, T, eos) - p - iter = 1 - while abs(dp) / abs(p) > tol && iter < 100 - dp = pressure(V, T, eos) - p - dpdT_V = ForwardDiff.derivative(T -> pressure(V, T, eos), T) - T = max(tol, T - dp / dpdT_V) - iter += 1 - end - if iter == 100 - println("Warning: solver for temperature(V, p) did not converge") - end - - return prim2cons(SVector(V, v1, T), equations) -end - -# The 1D Riemann problem from "An oscillation free shock-capturing method -# for compressible van der Waals supercritical fluid flows" by Pantano, -# Saurel, and Schmitt (2017). -# -# This is intended to be run with the `VanDerWaals(; gamma=1.4)` eos until -# final time 14e-3 on the domain [-12, 12]. -function initial_condition_Riemann_problem(x, t, - equations::NonIdealCompressibleEulerEquations1D{<:VanDerWaals}) - RealT = eltype(x) - eos = equations.equation_of_state - - if x[1] < 0 - rho, v1, p = SVector(497.417, 0, 4e7) - else - rho, v1, p = SVector(13.33117, 0, 4e6) # 6e6 - end - - V = inv(rho) - - # invert for temperature given p, V - T = RealT(1.0) - tol = 100 * eps(RealT) - dp = pressure(V, T, eos) - p - iter = 1 - while abs(dp) / abs(p) > tol && iter < 100 - dp = pressure(V, T, eos) - p - dpdT_V = ForwardDiff.derivative(T -> pressure(V, T, eos), T) - T = max(tol, T - dp / dpdT_V) - iter += 1 - end - if iter == 100 - println("Warning: solver for temperature(V, p) did not converge") - end - - return prim2cons(SVector(V, v1, T), equations) -end - -# the smooth Peng-Robinson N2 transcritical wave taken from "An entropy-stable hybrid scheme -# for simulations of transcritical real-fluid flows" by Ma, Ihme (2017). -# -function initial_condition_transcritical_wave(x, t, - equations::NonIdealCompressibleEulerEquations1D{<:PengRobinson}) - RealT = eltype(x) - eos = equations.equation_of_state - - rho_min, rho_max = 56.9, 793.1 - v1 = 100 - rho = 0.5 * (rho_min + rho_max) + - 0.5 * (rho_max - rho_min) * sin(2 * pi * (x[1] - v1 * t)) - p = 5e6 - - V = inv(rho) - - # invert for temperature given p, V - T = eos.T0 - tol = 100 * eps(RealT) - dp = pressure(V, T, eos) - p - iter = 1 - while abs(dp) / abs(p) > tol && iter < 100 - dp = pressure(V, T, eos) - p - dpdT_V = ForwardDiff.derivative(T -> pressure(V, T, eos), T) - T = max(tol, T - dp / dpdT_V) - iter += 1 - end - if iter == 100 - println("Warning: solver for temperature(V, p) did not converge") - end - - return prim2cons(SVector(V, v1, T), equations) -end - -# the Peng-Robinson N2 transcritical shock taken from "An entropy-stable hybrid scheme -# for simulations of transcritical real-fluid flows" by Ma, Ihme (2017). -# -function initial_condition_transcritical_shock(x, t, - equations::NonIdealCompressibleEulerEquations1D{<:PengRobinson}) - RealT = eltype(x) - eos = equations.equation_of_state - - if x[1] < 0 - rho, v1, p = SVector(800, 0, 60e6) - else - rho, v1, p = SVector(80, 0, 6e6) - end - - V = inv(rho) - - # invert for temperature given p, V - T = eos.T0 - tol = 100 * eps(RealT) - dp = pressure(V, T, eos) - p - iter = 1 - while abs(dp) / abs(p) > tol && iter < 100 - dp = pressure(V, T, eos) - p - dpdT_V = ForwardDiff.derivative(T -> pressure(V, T, eos), T) - T = max(tol, T - dp / dpdT_V) - iter += 1 - end - if iter == 100 - println("Warning: solver for temperature(V, p) did not converge") - end - - return prim2cons(SVector(V, v1, T), equations) -end end # @muladd diff --git a/test/test_tree_1d_euler.jl b/test/test_tree_1d_euler.jl index c475ceab92c..3af0c38b64b 100644 --- a/test/test_tree_1d_euler.jl +++ b/test/test_tree_1d_euler.jl @@ -620,43 +620,4 @@ end @test_allocations(Trixi.rhs!, semi, sol, 1000) end -@trixi_testset "elixir_euler_nonideal_density_wave.jl (transcritical wave) with Peng Robinson" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_euler_nonideal_density_wave.jl"), - solver=DGSEM(polydeg = 3, - volume_integral = VolumeIntegralFluxDifferencing(flux_terashima_etal), - surface_flux = flux_lax_friedrichs), - initial_condition=initial_condition_transcritical_wave, - tspan=(0.0, 0.001), - # note that rho_e_total errors are large because pressure is 5e6 - l2=[2.5190796911050598e-5, 0.0013782564599067785, 7.045132422388037], - linf=[ - 0.00014865356020266063, - 0.00764166860608384, - 27.349332988262177 - ]) - - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - @test_allocations(Trixi.rhs!, semi, sol, 1000) -end - -@trixi_testset "elixir_euler_nonideal_transcritical_shock.jl with Peng Robinson" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_euler_nonideal_transcritical_shock.jl"), - initial_condition=initial_condition_transcritical_wave, - tspan=(0.0, 5e-5), - # note that rho_e_total errors are large because pressure is 5e6 - l2=[3.205713931725666e-5, 0.0024557000949388375, 7.814075349831272], - linf=[ - 0.00015527262569037248, - 0.006962752362596802, - 25.366882726550102 - ]) - - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - @test_allocations(Trixi.rhs!, semi, sol, 1000) -end - end # module From f8375cfbdfe118bbb600e7338b0159242dcafcc4 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Thu, 29 Jan 2026 14:21:01 -0600 Subject: [PATCH 135/360] add PR EOS, initial conditions, and tests add transcritical shock elixir --- ...ixir_euler_nonideal_transcritical_shock.jl | 59 +++++++ src/Trixi.jl | 5 +- .../equation_of_state_peng_robinson.jl | 159 ++++++++++++++++++ src/equations/equations_of_state.jl | 15 +- .../nonideal_compressible_euler_1d.jl | 138 +++++++++++++++ test/test_tree_1d_euler.jl | 39 +++++ 6 files changed, 410 insertions(+), 5 deletions(-) create mode 100644 examples/tree_1d_dgsem/elixir_euler_nonideal_transcritical_shock.jl create mode 100644 src/equations/equation_of_state_peng_robinson.jl diff --git a/examples/tree_1d_dgsem/elixir_euler_nonideal_transcritical_shock.jl b/examples/tree_1d_dgsem/elixir_euler_nonideal_transcritical_shock.jl new file mode 100644 index 00000000000..3fe25a26589 --- /dev/null +++ b/examples/tree_1d_dgsem/elixir_euler_nonideal_transcritical_shock.jl @@ -0,0 +1,59 @@ +using OrdinaryDiffEqLowStorageRK +using Trixi + +############################################################################### +# semidiscretization of the compressible Euler equations + +eos = PengRobinson() +equations = NonIdealCompressibleEulerEquations1D(eos) + +initial_condition = initial_condition_transcritical_shock + +volume_flux = flux_central_terashima_etal +solver = DGSEM(polydeg = 3, volume_integral = VolumeIntegralFluxDifferencing(volume_flux), + surface_flux = flux_lax_friedrichs) + +coordinates_min = -0.5 +coordinates_max = 0.5 +mesh = TreeMesh(coordinates_min, coordinates_max, + initial_refinement_level = 6, + n_cells_max = 30_000, + periodicity = false) + +boundary_conditions = (x_neg = BoundaryConditionDirichlet(initial_condition), + x_pos = BoundaryConditionDirichlet(initial_condition)) + +semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, + boundary_conditions = boundary_conditions) + +############################################################################### +# ODE solvers, callbacks etc. + +tspan = (0.0, 5.0e-4) +ode = semidiscretize(semi, tspan) + +summary_callback = SummaryCallback() + +analysis_interval = 2000 +analysis_callback = AnalysisCallback(semi, interval = analysis_interval) + +alive_callback = AliveCallback(analysis_interval = analysis_interval) + +save_solution = SaveSolutionCallback(interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim) + +stepsize_callback = StepsizeCallback(cfl = 0.25) + +callbacks = CallbackSet(summary_callback, + analysis_callback, alive_callback, + save_solution, + stepsize_callback) + +############################################################################### +# run the simulation + +sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false); + dt = stepsize_callback(ode), # solve needs some value here but it will be overwritten by the stepsize_callback + ode_default_options()..., callback = callbacks); diff --git a/src/Trixi.jl b/src/Trixi.jl index 52187d035d7..7219b60038f 100644 --- a/src/Trixi.jl +++ b/src/Trixi.jl @@ -182,7 +182,10 @@ export AcousticPerturbationEquations2D, LinearElasticityEquations1D, PassiveTracerEquations -export NonIdealCompressibleEulerEquations1D, IdealGas, VanDerWaals +export NonIdealCompressibleEulerEquations1D +export IdealGas, VanDerWaals, PengRobinson +export initial_condition_Riemann_problem, initial_condition_transcritical_wave, + initial_condition_transcritical_shock export LaplaceDiffusion1D, LaplaceDiffusion2D, LaplaceDiffusion3D, LaplaceDiffusionEntropyVariables1D, LaplaceDiffusionEntropyVariables2D, diff --git a/src/equations/equation_of_state_peng_robinson.jl b/src/equations/equation_of_state_peng_robinson.jl new file mode 100644 index 00000000000..7f4376de42e --- /dev/null +++ b/src/equations/equation_of_state_peng_robinson.jl @@ -0,0 +1,159 @@ +# By default, Julia/LLVM does not use fused multiply-add operations (FMAs). +# Since these FMAs can increase the performance of many numerical algorithms, +# we need to opt-in explicitly. +# See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. +@muladd begin +#! format: noindent + +@doc raw""" + PengRobinson{RealT <: Real} <: AbstractEquationOfState + +This defines the Peng-Robinson equation of state +given by the pressure and internal energy relations +```math +p = \frac{R T}{V - b} - \frac{a(T)}{V^2 + 2bV - b^2}, \quad e = c_{v,0} T + K(a(T) - Ta'(T)) +``` +where `V = inv(rho)` and auxiliary expressions for `a(T)` and `K` are given by +```math +a(T) = a_0\left(1 + \kappa \left 1 - \sqrt{\frac{T}{T_0}}\right)\right)^2, \quad +K = \frac{1}{b 2\sqrt{2}} \log\left( \frac{V + (1 - b \sqrt{2})}{V + (1 + b\sqrt{2})}\right). +``` +Moreover, `c_v = c_{v,0} - K T a''(T)`. + +All expressions used here are taken from "An entropy-stable hybrid scheme for simulations +of transcritical real-fluid flows" by Ma, Lv, Ihme (2017). + + +See also "Towards a fully well-balanced and entropy-stable scheme for the Euler equations with +gravity: preserving isentropic steady solutions" by Berthon, Michel-Dansac, and Thomann (2024). + + +""" +struct PengRobinson{RealT <: Real} <: AbstractEquationOfState + R::RealT + a0::RealT + b::RealT + cv0::RealT + kappa::RealT + T0::RealT + inv2sqrt2b::RealT + one_minus_sqrt2_b::RealT + one_plus_sqrt2_b::RealT + function PengRobinson(R, a0, b, cv0, kappa, T0) + inv2sqrt2b = inv(2 * sqrt(2) * b) + one_minus_sqrt2_b = (1 - sqrt(2)) * b + one_plus_sqrt2_b = (1 + sqrt(2)) * b + return new{typeof(R)}(R, a0, b, cv0, kappa, T0, + inv2sqrt2b, one_minus_sqrt2_b, one_plus_sqrt2_b) + end +end + +""" + PengRobinson(; RealT = Float64) + +By default, the Peng-Robinson parameters are in mass basis for N2. +""" +function PengRobinson(; RealT = Float64) + Rgas = 8.31446261815324 + molar_mass_N2 = 0.02801 * 1000 # kg/m3 + R = Rgas * 1000 / molar_mass_N2 + pc = 3.40e6 + Tc = 126.2 + omega = 0.0372 + cv0 = 743.2 + b = 0.077796 * R * Tc / pc + a0 = 0.457236 * (R * Tc)^2 / pc + kappa = 0.37464 + 1.54226 * omega - 0.26992 * omega^2 + return PengRobinson(RealT.((R, a0, b, cv0, kappa, Tc))...) +end + +# the default tolerance of 10 * eps() does not converge for most Peng-Robinson examples, +# so we choose a looser tolerance here. Researchers at the US Naval Research Lab noted +# that they typically just use 8 fixed Newton iterations for Peng-Robinson. +eos_newton_tol(eos::PengRobinson) = 1e-8 + +""" + pressure(V, T, eos::PengRobinson) + +Computes pressure for a Peng-Robinson gas from specific volume `V` and temperature `T`, +see also [`NonIdealCompressibleEulerEquations1D`](@ref). +""" +function pressure(V, T, eos::PengRobinson) + (; R, b) = eos + p = R * T / (V - b) - a(T, eos) / (V^2 + 2 * b * V - b^2) + return p +end + +""" + energy_internal(V, T, eos::PengRobinson) + +Computes internal energy for a Peng-Robinson gas from specific volume `V` and temperature `T` as +``e = c_{v,0} T + K_1 (a(T) - T a'(T))``. +""" +function energy_internal(V, T, eos::PengRobinson) + (; cv0) = eos + K1 = calc_K1(V, eos) + e = cv0 * T + K1 * (a(T, eos) - T * da(T, eos)) + return e +end + +@inline function heat_capacity_constant_volume(V, T, eos::PengRobinson) + (; cv0) = eos + K1 = calc_K1(V, eos) + cv = cv0 - K1 * T * d2a(T, eos) + return cv +end + +function entropy_specific(V, T, eos::PengRobinson) + (; cv0, R, b) = eos + + # The specific entropy is defined up to some reference value s0, which is + # arbitrarily set to zero here. + K1 = calc_K1(V, eos) + return cv0 * log(T) + R * log(V - b) - da(T, eos) * K1 +end + +function speed_of_sound(V, T, eos::PengRobinson) + (; cv0, R, b) = eos + + dpdT_V, dpdV_T = calc_pressure_derivatives(V, T, eos) + + # calculate ratio of specific heats + K1 = calc_K1(V, eos) + d2aT = d2a(T, eos) + cp0 = cv0 + R + cv = cv0 - K1 * T * d2aT + cp = cp0 - R - K1 * T * d2aT - T * dpdT_V^2 / dpdV_T + gamma = cp / cv + + # calculate bulk modulus, which should be positive + # for admissible thermodynamic states. + kappa_T = -inv(V * dpdV_T) + c2 = gamma * V / kappa_T + return sqrt(c2) +end + +function calc_pressure_derivatives(V, T, eos::PengRobinson) + (; R, b) = eos + denom = (V^2 + 2 * b * V - b^2) + RdivVb = R / (V - b) + dpdT_V = RdivVb - da(T, eos) / denom + dpdV_T = -RdivVb * T / (V - b) * + (1 - 2 * a(T, eos) / (R * T * (V + b) * (denom / (V^2 - b^2))^2)) + return dpdT_V, dpdV_T +end + +# The following are auxiliary functions used in calculating the PR EOS +@inline function a(T, eos::PengRobinson) + (; a0, kappa, T0) = eos + return a0 * (1 + kappa * (1 - sqrt(T / T0)))^2 +end +@inline da(T, eos) = ForwardDiff.derivative(T -> a(T, eos), T) +@inline d2a(T, eos) = ForwardDiff.derivative(T -> da(T, eos), T) + +@inline function calc_K1(V, eos::PengRobinson) + (; inv2sqrt2b, one_minus_sqrt2_b, one_plus_sqrt2_b) = eos + K1 = inv2sqrt2b * log((V + one_minus_sqrt2_b) / (V + one_plus_sqrt2_b)) + return K1 +end +end # @muladd diff --git a/src/equations/equations_of_state.jl b/src/equations/equations_of_state.jl index 3d03ada3174..c653c8dfc4c 100644 --- a/src/equations/equations_of_state.jl +++ b/src/equations/equations_of_state.jl @@ -26,6 +26,7 @@ abstract type AbstractEquationOfState end include("equation_of_state_ideal_gas.jl") include("equation_of_state_vdw.jl") +include("equation_of_state_peng_robinson.jl") ####################################################### # @@ -46,14 +47,18 @@ end return ForwardDiff.derivative(T -> energy_internal(V, T, eos), T) end +# this is used in [`flux_terashima_etal`](@ref) and [`flux_terashima_etal_central`](@ref) function calc_pressure_derivatives(V, T, eos) dpdV_T = ForwardDiff.derivative(V -> pressure(V, T, eos), V) dpdT_V = ForwardDiff.derivative(T -> pressure(V, T, eos), T) return dpdT_V, dpdV_T end -# relative tolerance for the Newton solver for temperature +# relative tolerance, initial guess, and maximum number of iterations +# for the Newton solver for temperature. eos_newton_tol(eos::AbstractEquationOfState) = 10 * eps() +eos_initial_temperature(V, e, eos::AbstractEquationOfState) = 1 +eos_newton_maxiter(eos) = 100 """ temperature(V, e, eos::AbstractEquationOfState; initial_T = 1.0, tol = eos_newton_tol(eos), @@ -63,8 +68,9 @@ Calculates the temperature as a function of specific volume `V` and internal ene by using Newton's method to determine `T` such that `energy_internal(V, T, eos) = e`. Note that the tolerance may need to be adjusted based on the specific equation of state. """ -function temperature(V, e, eos::AbstractEquationOfState; initial_T = 1.0, - tol = eos_newton_tol(eos), maxiter = 100) +function temperature(V, e, eos::AbstractEquationOfState; + initial_T = eos_initial_temperature(V, e, eos), + tol = eos_newton_tol(eos), maxiter = eos_newton_maxiter(eos)) T = initial_T de = energy_internal(V, T, eos) - e iter = 1 @@ -75,7 +81,8 @@ function temperature(V, e, eos::AbstractEquationOfState; initial_T = 1.0, # guarantee convergence of this iteration. de_dT_V = heat_capacity_constant_volume(V, T, eos) - T = T - de / de_dT_V + # guard against negative temperatures + T = max(tol, T - de / de_dT_V) iter += 1 end if iter == maxiter diff --git a/src/equations/nonideal_compressible_euler_1d.jl b/src/equations/nonideal_compressible_euler_1d.jl index 9add0ee7971..fc8b750a223 100644 --- a/src/equations/nonideal_compressible_euler_1d.jl +++ b/src/equations/nonideal_compressible_euler_1d.jl @@ -351,4 +351,142 @@ end rho_e = rho_e_total - 0.5f0 * rho_v1^2 / rho return rho_e end + +# The default amplitude and frequency k are consistent with initial_condition_density_wave +# for CompressibleEulerEquations1D. Note that this initial condition may not define admissible +# solution states for all non-ideal equations of state! +function initial_condition_density_wave(x, t, + equations::NonIdealCompressibleEulerEquations1D; + amplitude = 0.98, k = 2) + RealT = eltype(x) + eos = equations.equation_of_state + + v1 = convert(RealT, 0.1) + rho = 1 + convert(RealT, amplitude) * sinpi(k * (x[1] - v1 * t)) + p = 20 + + V = inv(rho) + + # invert for temperature given p, V + T = 1 + tol = 100 * eps(RealT) + dp = pressure(V, T, eos) - p + iter = 1 + while abs(dp) / abs(p) > tol && iter < 100 + dp = pressure(V, T, eos) - p + dpdT_V = ForwardDiff.derivative(T -> pressure(V, T, eos), T) + T = max(tol, T - dp / dpdT_V) + iter += 1 + end + if iter == 100 + println("Warning: solver for temperature(V, p) did not converge") + end + + return prim2cons(SVector(V, v1, T), equations) +end + +# The 1D Riemann problem from "An oscillation free shock-capturing method +# for compressible van der Waals supercritical fluid flows" by Pantano, +# Saurel, and Schmitt (2017). +# +# This is intended to be run with the `VanDerWaals(; gamma=1.4)` eos until +# final time 14e-3 on the domain [-12, 12]. +function initial_condition_Riemann_problem(x, t, + equations::NonIdealCompressibleEulerEquations1D{<:VanDerWaals}) + RealT = eltype(x) + eos = equations.equation_of_state + + if x[1] < 0 + rho, v1, p = SVector(497.417, 0, 4e7) + else + rho, v1, p = SVector(13.33117, 0, 4e6) # 6e6 + end + + V = inv(rho) + + # invert for temperature given p, V + T = RealT(1.0) + tol = 100 * eps(RealT) + dp = pressure(V, T, eos) - p + iter = 1 + while abs(dp) / abs(p) > tol && iter < 100 + dp = pressure(V, T, eos) - p + dpdT_V = ForwardDiff.derivative(T -> pressure(V, T, eos), T) + T = max(tol, T - dp / dpdT_V) + iter += 1 + end + if iter == 100 + println("Warning: solver for temperature(V, p) did not converge") + end + + return prim2cons(SVector(V, v1, T), equations) +end + +# the smooth Peng-Robinson N2 transcritical wave taken from "An entropy-stable hybrid scheme +# for simulations of transcritical real-fluid flows" by Ma, Ihme (2017). +# +function initial_condition_transcritical_wave(x, t, + equations::NonIdealCompressibleEulerEquations1D{<:PengRobinson}) + RealT = eltype(x) + eos = equations.equation_of_state + + rho_min, rho_max = 56.9, 793.1 + v1 = 100 + rho = 0.5 * (rho_min + rho_max) + + 0.5 * (rho_max - rho_min) * sin(2 * pi * (x[1] - v1 * t)) + p = 5e6 + + V = inv(rho) + + # invert for temperature given p, V + T = eos.T0 + tol = 100 * eps(RealT) + dp = pressure(V, T, eos) - p + iter = 1 + while abs(dp) / abs(p) > tol && iter < 100 + dp = pressure(V, T, eos) - p + dpdT_V = ForwardDiff.derivative(T -> pressure(V, T, eos), T) + T = max(tol, T - dp / dpdT_V) + iter += 1 + end + if iter == 100 + println("Warning: solver for temperature(V, p) did not converge") + end + + return prim2cons(SVector(V, v1, T), equations) +end + +# the Peng-Robinson N2 transcritical shock taken from "An entropy-stable hybrid scheme +# for simulations of transcritical real-fluid flows" by Ma, Ihme (2017). +# +function initial_condition_transcritical_shock(x, t, + equations::NonIdealCompressibleEulerEquations1D{<:PengRobinson}) + RealT = eltype(x) + eos = equations.equation_of_state + + if x[1] < 0 + rho, v1, p = SVector(800, 0, 60e6) + else + rho, v1, p = SVector(80, 0, 6e6) + end + + V = inv(rho) + + # invert for temperature given p, V + T = eos.T0 + tol = 100 * eps(RealT) + dp = pressure(V, T, eos) - p + iter = 1 + while abs(dp) / abs(p) > tol && iter < 100 + dp = pressure(V, T, eos) - p + dpdT_V = ForwardDiff.derivative(T -> pressure(V, T, eos), T) + T = max(tol, T - dp / dpdT_V) + iter += 1 + end + if iter == 100 + println("Warning: solver for temperature(V, p) did not converge") + end + + return prim2cons(SVector(V, v1, T), equations) +end end # @muladd diff --git a/test/test_tree_1d_euler.jl b/test/test_tree_1d_euler.jl index 3af0c38b64b..c475ceab92c 100644 --- a/test/test_tree_1d_euler.jl +++ b/test/test_tree_1d_euler.jl @@ -620,4 +620,43 @@ end @test_allocations(Trixi.rhs!, semi, sol, 1000) end +@trixi_testset "elixir_euler_nonideal_density_wave.jl (transcritical wave) with Peng Robinson" begin + @test_trixi_include(joinpath(EXAMPLES_DIR, + "elixir_euler_nonideal_density_wave.jl"), + solver=DGSEM(polydeg = 3, + volume_integral = VolumeIntegralFluxDifferencing(flux_terashima_etal), + surface_flux = flux_lax_friedrichs), + initial_condition=initial_condition_transcritical_wave, + tspan=(0.0, 0.001), + # note that rho_e_total errors are large because pressure is 5e6 + l2=[2.5190796911050598e-5, 0.0013782564599067785, 7.045132422388037], + linf=[ + 0.00014865356020266063, + 0.00764166860608384, + 27.349332988262177 + ]) + + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + @test_allocations(Trixi.rhs!, semi, sol, 1000) +end + +@trixi_testset "elixir_euler_nonideal_transcritical_shock.jl with Peng Robinson" begin + @test_trixi_include(joinpath(EXAMPLES_DIR, + "elixir_euler_nonideal_transcritical_shock.jl"), + initial_condition=initial_condition_transcritical_wave, + tspan=(0.0, 5e-5), + # note that rho_e_total errors are large because pressure is 5e6 + l2=[3.205713931725666e-5, 0.0024557000949388375, 7.814075349831272], + linf=[ + 0.00015527262569037248, + 0.006962752362596802, + 25.366882726550102 + ]) + + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + @test_allocations(Trixi.rhs!, semi, sol, 1000) +end + end # module From 694888a4661564ba6b0c6421d134b620695078a8 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Thu, 29 Jan 2026 14:25:31 -0600 Subject: [PATCH 136/360] remove redundant initial condition definition from elixir --- .../elixir_euler_nonideal_density_wave.jl | 60 +++++++++---------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/examples/tree_1d_dgsem/elixir_euler_nonideal_density_wave.jl b/examples/tree_1d_dgsem/elixir_euler_nonideal_density_wave.jl index 135a7871edb..da9f83dfd47 100644 --- a/examples/tree_1d_dgsem/elixir_euler_nonideal_density_wave.jl +++ b/examples/tree_1d_dgsem/elixir_euler_nonideal_density_wave.jl @@ -8,36 +8,6 @@ using Trixi: ForwardDiff eos = VanDerWaals(; a = 10, b = 1e-2, gamma = 1.4, R = 287) equations = NonIdealCompressibleEulerEquations1D(eos) -# the default amplitude and frequency k are chosen to be consistent with -# initial_condition_density_wave for CompressibleEulerEquations1D -function initial_condition_density_wave(x, t, - equations::NonIdealCompressibleEulerEquations1D; - amplitude = 0.98, k = 2) - RealT = eltype(x) - v1 = convert(RealT, 0.1) - rho = 1 + convert(RealT, amplitude) * sinpi(k * (x[1] - v1 * t)) - p = 20 - - V = inv(rho) - - # invert for temperature given p, V - T = 1 - tol = 100 * eps(RealT) - dp = pressure(V, T, eos) - p - iter = 1 - while abs(dp) / abs(p) > tol && iter < 100 - dp = pressure(V, T, eos) - p - dpdT_V = ForwardDiff.derivative(T -> pressure(V, T, eos), T) - T = T - dp / dpdT_V - iter += 1 - end - if iter == 100 - println("Warning: solver for temperature(V, p) did not converge") - end - - return prim2cons(SVector(V, v1, T), equations) -end - initial_condition = initial_condition_density_wave solver = DGSEM(polydeg = 3, surface_flux = flux_lax_friedrichs) @@ -78,6 +48,36 @@ callbacks = CallbackSet(summary_callback, ############################################################################### # run the simulation +sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false); + dt = stepsize_callback(ode), # solve needs some value here but it will be overwritten by the stepsize_callback + ode_default_options()..., callback = callbacks); +# ODE solvers, callbacks etc. + +tspan = (0.0, 2.0) +ode = semidiscretize(semi, tspan) + +summary_callback = SummaryCallback() + +analysis_interval = 2000 +analysis_callback = AnalysisCallback(semi, interval = analysis_interval) + +alive_callback = AliveCallback(analysis_interval = analysis_interval) + +save_solution = SaveSolutionCallback(interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim) + +stepsize_callback = StepsizeCallback(cfl = 0.5) + +callbacks = CallbackSet(summary_callback, + analysis_callback, alive_callback, + save_solution, + stepsize_callback) + +############################################################################### +# run the simulation + sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false); dt = stepsize_callback(ode), # solve needs some value here but it will be overwritten by the stepsize_callback ode_default_options()..., callback = callbacks); From c29606793a0d66120796cf1ca87f7a1102c0f9b7 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Thu, 29 Jan 2026 14:25:44 -0600 Subject: [PATCH 137/360] add plotting utility --- src/equations/nonideal_compressible_euler_1d.jl | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/equations/nonideal_compressible_euler_1d.jl b/src/equations/nonideal_compressible_euler_1d.jl index fc8b750a223..9fe717a6012 100644 --- a/src/equations/nonideal_compressible_euler_1d.jl +++ b/src/equations/nonideal_compressible_euler_1d.jl @@ -51,6 +51,18 @@ function varnames(::typeof(cons2cons), ::NonIdealCompressibleEulerEquations1D) end varnames(::typeof(cons2prim), ::NonIdealCompressibleEulerEquations1D) = ("V", "v1", "T") +# for plotting with PlotData1D(sol, solution_variables=density_velocity_pressure) +@inline function density_velocity_pressure(u, + equations::NonIdealCompressibleEulerEquations1D) + eos = equations.equation_of_state + rho, rho_v1, rho_e_total = u + V, v1, T = cons2prim(u, equations) + return SVector(rho, v1, pressure(V, T, eos)) +end +varnames(::typeof(density_velocity_pressure), ::NonIdealCompressibleEulerEquations1D) = ("rho", + "v1", + "p") + # Calculate 1D flux for a single point @inline function flux(u, orientation::Integer, equations::NonIdealCompressibleEulerEquations1D) From f7795129face218b49bcb1eddbd64f3facef48f7 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Thu, 29 Jan 2026 14:27:33 -0600 Subject: [PATCH 138/360] add AbstractNonIdealEulerEquations type --- src/equations/equations.jl | 5 +++ .../nonideal_compressible_euler_1d.jl | 36 ++++++++++++------- 2 files changed, 28 insertions(+), 13 deletions(-) diff --git a/src/equations/equations.jl b/src/equations/equations.jl index 322e411606b..469e40ac6d6 100644 --- a/src/equations/equations.jl +++ b/src/equations/equations.jl @@ -646,8 +646,13 @@ include("compressible_euler_1d.jl") include("compressible_euler_2d.jl") include("compressible_euler_3d.jl") include("compressible_euler_quasi_1d.jl") + +# Non-ideal compressibleEulerEquations +abstract type AbstractNonIdealCompressibleEulerEquations{NDIMS, NVARS} <: + AbstractCompressibleEulerEquations{NDIMS, NVARS} end include("equations_of_state.jl") include("nonideal_compressible_euler_1d.jl") +include("nonideal_compressible_euler_2d.jl") # CompressibleEulerMulticomponentEquations abstract type AbstractCompressibleEulerMulticomponentEquations{NDIMS, NVARS, NCOMP} <: diff --git a/src/equations/nonideal_compressible_euler_1d.jl b/src/equations/nonideal_compressible_euler_1d.jl index 9fe717a6012..80e3e4c12d8 100644 --- a/src/equations/nonideal_compressible_euler_1d.jl +++ b/src/equations/nonideal_compressible_euler_1d.jl @@ -42,7 +42,7 @@ Because of this, the primitive variables are also defined to be `V, v1, T` (inst mass basis unless otherwise specified. """ struct NonIdealCompressibleEulerEquations1D{EoS <: AbstractEquationOfState} <: - AbstractCompressibleEulerEquations{1, 3} + AbstractNonIdealCompressibleEulerEquations{1, 3} equation_of_state::EoS end @@ -301,25 +301,27 @@ S = -\rho s ``` where `s` is the specific entropy determined by the equation of state. """ -@inline function entropy_math(u, equations::NonIdealCompressibleEulerEquations1D) +@inline function entropy_math(u, equations::AbstractNonIdealCompressibleEulerEquations) eos = equations.equation_of_state - V, _, T = cons2prim(u, equations) + q = cons2prim(u, equations) + V = first(q) + T = last(q) rho = u[1] S = -rho * entropy_specific(V, T, eos) return S end """ - entropy(cons, equations::NonIdealCompressibleEulerEquations1D) + entropy(cons, equations::AbstractNonIdealEulerEquations) Default entropy is the mathematical entropy -[`entropy_math(cons, equations::NonIdealCompressibleEulerEquations1D)`](@ref). +[`entropy_math(cons, equations::AbstractNonIdealEulerEquations)`](@ref). """ -@inline function entropy(cons, equations::NonIdealCompressibleEulerEquations1D) +@inline function entropy(cons, equations::AbstractNonIdealCompressibleEulerEquations) return entropy_math(cons, equations) end -@inline function density(u, equations::NonIdealCompressibleEulerEquations1D) +@inline function density(u, equations::AbstractNonIdealCompressibleEulerEquations) rho = u[1] return rho end @@ -335,24 +337,32 @@ end return v1 end -@inline function pressure(u, equations::NonIdealCompressibleEulerEquations1D) +@inline function pressure(u, equations::AbstractNonIdealCompressibleEulerEquations) eos = equations.equation_of_state - V, _, T = cons2prim(u, equations) + q = cons2prim(u, equations) + V = first(q) + T = last(q) p = pressure(V, T, eos) return p end -@inline function density_pressure(u, equations::NonIdealCompressibleEulerEquations1D) +@inline function density_pressure(u, + equations::AbstractNonIdealCompressibleEulerEquations) eos = equations.equation_of_state rho = u[1] - V, _, T = cons2prim(u, equations) + q = cons2prim(u, equations) + V = first(q) + T = last(q) p = pressure(V, T, eos) return rho * p end -@inline function energy_internal(u, equations::NonIdealCompressibleEulerEquations1D) +@inline function energy_internal(u, + equations::AbstractNonIdealCompressibleEulerEquations) eos = equations.equation_of_state - V, _, T = cons2prim(u, equations) + q = cons2prim(u, equations) + V = first(q) + T = last(q) e = energy_internal(V, T, eos) return e end From e83c674aa6e584b3b0e4b7b70b785883a90db9ce Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Thu, 29 Jan 2026 16:55:12 -0600 Subject: [PATCH 139/360] fix tests --- test/test_tree_1d_euler.jl | 22 +++++++++++----------- test/test_tree_2d_euler.jl | 6 +++--- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/test/test_tree_1d_euler.jl b/test/test_tree_1d_euler.jl index 8e5d24cfd56..a3a30c8f3e2 100644 --- a/test/test_tree_1d_euler.jl +++ b/test/test_tree_1d_euler.jl @@ -624,8 +624,8 @@ end @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_modified_sod_entropy_correction.jl"), tspan=(0.0, 0.1), - l2=[0.17918607115375737, 0.30210797213211904, 0.5919230042600214], - linf=[0.6787210680110314, 0.8094457930038574, 1.9399759515642199]) + l2=[0.17918606870085826, 0.30210796226660624, 0.5919229714363661], + linf=[0.6787210683498577, 0.8094457931331333, 1.939975952122447]) # Ensure that we do not have excessive memory allocations # (e.g., from type instabilities) @@ -637,20 +637,20 @@ end "elixir_euler_nonideal_density_wave.jl"), solver=DGSEM(LobattoLegendreBasis(3), flux_lax_friedrichs, - VolumeIntegralEntropyCorrection(equations, + VolumeIntegralEntropyCorrection(CompressibleEulerEquations1D(1.4), LobattoLegendreBasis(3); - volume_flux_dg = volume_flux, - volume_flux_fv = surface_flux)), + volume_flux_dg = flux_central, + volume_flux_fv = flux_lax_friedrichs)), tspan=(0.0, 0.1), l2=[ - 0.0014836428894376321, - 0.0002850707686431336, - 0.06261081503929328 + 0.0017122131198510152, + 0.00036348851462487426, + 0.07342375186998892 ], linf=[ - 0.002700476173981947, - 0.0008496629031280178, - 0.17460023775242917 + 0.0031904717833848295, + 0.0018240656867681004, + 0.21543232235424625 ]) # Ensure that we do not have excessive memory allocations diff --git a/test/test_tree_2d_euler.jl b/test/test_tree_2d_euler.jl index 6bcb5b17167..6d064b86614 100644 --- a/test/test_tree_2d_euler.jl +++ b/test/test_tree_2d_euler.jl @@ -114,10 +114,10 @@ end @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_density_wave.jl"), solver=DGSEM(LobattoLegendreBasis(3), flux_lax_friedrichs, - VolumeIntegralEntropyCorrection(equations, + VolumeIntegralEntropyCorrection(CompressibleEulerEquations2D(1.4), LobattoLegendreBasis(3); - volume_flux_dg = volume_flux, - volume_flux_fv = surface_flux)), + volume_flux_dg = flux_central, + volume_flux_fv = flux_lax_friedrichs)), tspan=(0.0, 0.1), l2=[ 0.028712539480767976, From 793e0383784ae08ca80a6fdee77338615c6b9840 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Thu, 29 Jan 2026 16:55:55 -0600 Subject: [PATCH 140/360] fix tests, remove broken include --- src/equations/equations.jl | 1 - 1 file changed, 1 deletion(-) diff --git a/src/equations/equations.jl b/src/equations/equations.jl index 469e40ac6d6..e6ce9992caa 100644 --- a/src/equations/equations.jl +++ b/src/equations/equations.jl @@ -652,7 +652,6 @@ abstract type AbstractNonIdealCompressibleEulerEquations{NDIMS, NVARS} <: AbstractCompressibleEulerEquations{NDIMS, NVARS} end include("equations_of_state.jl") include("nonideal_compressible_euler_1d.jl") -include("nonideal_compressible_euler_2d.jl") # CompressibleEulerMulticomponentEquations abstract type AbstractCompressibleEulerMulticomponentEquations{NDIMS, NVARS, NCOMP} <: From fad9ee14046c6bd52cf00c39465ef8b76cfd86da Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Thu, 29 Jan 2026 17:11:18 -0600 Subject: [PATCH 141/360] export plotting function --- src/Trixi.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Trixi.jl b/src/Trixi.jl index 7219b60038f..5bc1f187cb5 100644 --- a/src/Trixi.jl +++ b/src/Trixi.jl @@ -251,6 +251,7 @@ export initial_condition_eoc_test_coupled_euler_gravity, export cons2cons, cons2prim, prim2cons, cons2macroscopic, cons2state, cons2mean, cons2entropy, entropy2cons export density, pressure, density_pressure, velocity, temperature, + density_velocity_pressure, global_mean_vars, equilibrium_distribution, waterheight, waterheight_pressure From 695229924ac5f9ba7a4fc3eded513e1ff39d80ee Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Thu, 29 Jan 2026 17:13:57 -0600 Subject: [PATCH 142/360] fix PR density wave test --- test/test_tree_1d_euler.jl | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/test/test_tree_1d_euler.jl b/test/test_tree_1d_euler.jl index c475ceab92c..71d136b3ccd 100644 --- a/test/test_tree_1d_euler.jl +++ b/test/test_tree_1d_euler.jl @@ -623,18 +623,15 @@ end @trixi_testset "elixir_euler_nonideal_density_wave.jl (transcritical wave) with Peng Robinson" begin @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_nonideal_density_wave.jl"), + eos=PengRobinson(), solver=DGSEM(polydeg = 3, volume_integral = VolumeIntegralFluxDifferencing(flux_terashima_etal), surface_flux = flux_lax_friedrichs), initial_condition=initial_condition_transcritical_wave, tspan=(0.0, 0.001), - # note that rho_e_total errors are large because pressure is 5e6 - l2=[2.5190796911050598e-5, 0.0013782564599067785, 7.045132422388037], - linf=[ - 0.00014865356020266063, - 0.00764166860608384, - 27.349332988262177 - ]) + # note that errors are large because the solution magnitude is large + l2=[3.562431427840198, 307.0804734149763, 671891.3209204111], + linf=[11.245466632528988, 1012.4992037314914, 2.1937079580614567e6]) # Ensure that we do not have excessive memory allocations # (e.g., from type instabilities) From e26b2dad2a3d1af9b34702888b9988d836c5f326 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Thu, 29 Jan 2026 18:36:58 -0600 Subject: [PATCH 143/360] remove untested code --- .../nonideal_compressible_euler_1d.jl | 37 ------------------- 1 file changed, 37 deletions(-) diff --git a/src/equations/nonideal_compressible_euler_1d.jl b/src/equations/nonideal_compressible_euler_1d.jl index 80e3e4c12d8..bac0d5d91c3 100644 --- a/src/equations/nonideal_compressible_euler_1d.jl +++ b/src/equations/nonideal_compressible_euler_1d.jl @@ -407,43 +407,6 @@ function initial_condition_density_wave(x, t, return prim2cons(SVector(V, v1, T), equations) end -# The 1D Riemann problem from "An oscillation free shock-capturing method -# for compressible van der Waals supercritical fluid flows" by Pantano, -# Saurel, and Schmitt (2017). -# -# This is intended to be run with the `VanDerWaals(; gamma=1.4)` eos until -# final time 14e-3 on the domain [-12, 12]. -function initial_condition_Riemann_problem(x, t, - equations::NonIdealCompressibleEulerEquations1D{<:VanDerWaals}) - RealT = eltype(x) - eos = equations.equation_of_state - - if x[1] < 0 - rho, v1, p = SVector(497.417, 0, 4e7) - else - rho, v1, p = SVector(13.33117, 0, 4e6) # 6e6 - end - - V = inv(rho) - - # invert for temperature given p, V - T = RealT(1.0) - tol = 100 * eps(RealT) - dp = pressure(V, T, eos) - p - iter = 1 - while abs(dp) / abs(p) > tol && iter < 100 - dp = pressure(V, T, eos) - p - dpdT_V = ForwardDiff.derivative(T -> pressure(V, T, eos), T) - T = max(tol, T - dp / dpdT_V) - iter += 1 - end - if iter == 100 - println("Warning: solver for temperature(V, p) did not converge") - end - - return prim2cons(SVector(V, v1, T), equations) -end - # the smooth Peng-Robinson N2 transcritical wave taken from "An entropy-stable hybrid scheme # for simulations of transcritical real-fluid flows" by Ma, Ihme (2017). # From 99a6409f6fe91726622d4af4b4bc5e433fac3b1c Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Thu, 29 Jan 2026 18:37:25 -0600 Subject: [PATCH 144/360] fix docstring --- src/equations/nonideal_compressible_euler_1d.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/equations/nonideal_compressible_euler_1d.jl b/src/equations/nonideal_compressible_euler_1d.jl index bac0d5d91c3..ba16874a88d 100644 --- a/src/equations/nonideal_compressible_euler_1d.jl +++ b/src/equations/nonideal_compressible_euler_1d.jl @@ -293,7 +293,7 @@ end end @doc raw""" - entropy_math(cons, equations::NonIdealCompressibleEulerEquations1D) + entropy_math(cons, equations::AbstractNonIdealCompressibleEulerEquations) Calculate mathematical entropy for a conservative state `cons` as ```math From 9bbca8d9ac56b9ee89157b6a597cb8d48ec5b41c Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Thu, 29 Jan 2026 18:37:32 -0600 Subject: [PATCH 145/360] update tests --- test/test_tree_1d_euler.jl | 10 +++------- test/test_unit.jl | 2 ++ 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/test/test_tree_1d_euler.jl b/test/test_tree_1d_euler.jl index 71d136b3ccd..6ff2703305e 100644 --- a/test/test_tree_1d_euler.jl +++ b/test/test_tree_1d_euler.jl @@ -641,15 +641,11 @@ end @trixi_testset "elixir_euler_nonideal_transcritical_shock.jl with Peng Robinson" begin @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_nonideal_transcritical_shock.jl"), - initial_condition=initial_condition_transcritical_wave, + initial_condition=initial_condition_transcritical_shock, tspan=(0.0, 5e-5), # note that rho_e_total errors are large because pressure is 5e6 - l2=[3.205713931725666e-5, 0.0024557000949388375, 7.814075349831272], - linf=[ - 0.00015527262569037248, - 0.006962752362596802, - 25.366882726550102 - ]) + l2=[46.87606704575898, 12776.72009989676, 3.0691124394639865e6], + linf=[728.557135738047, 82812.85038902842, 7.330706462442407e7]) # Ensure that we do not have excessive memory allocations # (e.g., from type instabilities) diff --git a/test/test_unit.jl b/test/test_unit.jl index 41734a10831..701ddbf6d28 100644 --- a/test/test_unit.jl +++ b/test/test_unit.jl @@ -806,6 +806,8 @@ end e = energy_internal(V, T, eos) @test temperature(V, e, eos) ≈ invoke(temperature, Tuple{Any, Any, Trixi.AbstractEquationOfState}, V, e, eos) + @test density_velocity_pressure(u, equations) ≈ + SVector(u[1], v1, pressure(u, equations)) # check that fallback calc_pressure_derivatives matches specialized routines @test Trixi.calc_pressure_derivatives(V, T, eos)[1] ≈ From eda25c32c6879d2047ec2e007020ded214df6e65 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Thu, 29 Jan 2026 22:13:46 -0600 Subject: [PATCH 146/360] remove undefined export --- src/Trixi.jl | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Trixi.jl b/src/Trixi.jl index 5bc1f187cb5..4b20639affe 100644 --- a/src/Trixi.jl +++ b/src/Trixi.jl @@ -184,8 +184,7 @@ export AcousticPerturbationEquations2D, export NonIdealCompressibleEulerEquations1D export IdealGas, VanDerWaals, PengRobinson -export initial_condition_Riemann_problem, initial_condition_transcritical_wave, - initial_condition_transcritical_shock +export initial_condition_transcritical_wave, initial_condition_transcritical_shock export LaplaceDiffusion1D, LaplaceDiffusion2D, LaplaceDiffusion3D, LaplaceDiffusionEntropyVariables1D, LaplaceDiffusionEntropyVariables2D, From 4e54a983d8e45ff344053038ab85ddad592f0227 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Thu, 29 Jan 2026 22:14:57 -0600 Subject: [PATCH 147/360] try again to fix docstring --- src/equations/nonideal_compressible_euler_1d.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/equations/nonideal_compressible_euler_1d.jl b/src/equations/nonideal_compressible_euler_1d.jl index ba16874a88d..1e505f16597 100644 --- a/src/equations/nonideal_compressible_euler_1d.jl +++ b/src/equations/nonideal_compressible_euler_1d.jl @@ -293,7 +293,7 @@ end end @doc raw""" - entropy_math(cons, equations::AbstractNonIdealCompressibleEulerEquations) + entropy_math(u, equations::AbstractNonIdealCompressibleEulerEquations) Calculate mathematical entropy for a conservative state `cons` as ```math From 8c0236e0ca6df9e31950d199c726723b5348c3af Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Thu, 29 Jan 2026 22:55:43 -0600 Subject: [PATCH 148/360] fix docs again --- src/equations/nonideal_compressible_euler_1d.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/equations/nonideal_compressible_euler_1d.jl b/src/equations/nonideal_compressible_euler_1d.jl index 1e505f16597..d38800c06ba 100644 --- a/src/equations/nonideal_compressible_euler_1d.jl +++ b/src/equations/nonideal_compressible_euler_1d.jl @@ -315,7 +315,7 @@ end entropy(cons, equations::AbstractNonIdealEulerEquations) Default entropy is the mathematical entropy -[`entropy_math(cons, equations::AbstractNonIdealEulerEquations)`](@ref). +[`entropy_math(u, equations::AbstractNonIdealEulerEquations)`](@ref). """ @inline function entropy(cons, equations::AbstractNonIdealCompressibleEulerEquations) return entropy_math(cons, equations) From c6ab06316af78e81837938ac05f433a895b1d69e Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Thu, 29 Jan 2026 23:05:11 -0600 Subject: [PATCH 149/360] move initial conditions into elixirs and tests --- .../elixir_euler_nonideal_density_wave.jl | 32 ++++++ ...ixir_euler_nonideal_transcritical_shock.jl | 33 ++++++ src/Trixi.jl | 1 - .../nonideal_compressible_euler_1d.jl | 100 ------------------ test/test_tree_1d_euler.jl | 35 ++++++ 5 files changed, 100 insertions(+), 101 deletions(-) diff --git a/examples/tree_1d_dgsem/elixir_euler_nonideal_density_wave.jl b/examples/tree_1d_dgsem/elixir_euler_nonideal_density_wave.jl index da9f83dfd47..6098447fdbe 100644 --- a/examples/tree_1d_dgsem/elixir_euler_nonideal_density_wave.jl +++ b/examples/tree_1d_dgsem/elixir_euler_nonideal_density_wave.jl @@ -8,6 +8,38 @@ using Trixi: ForwardDiff eos = VanDerWaals(; a = 10, b = 1e-2, gamma = 1.4, R = 287) equations = NonIdealCompressibleEulerEquations1D(eos) +# The default amplitude and frequency k are consistent with initial_condition_density_wave +# for CompressibleEulerEquations1D. Note that this initial condition may not define admissible +# solution states for all non-ideal equations of state! +function Trixi.initial_condition_density_wave(x, t, + equations::NonIdealCompressibleEulerEquations1D; + amplitude = 0.98, k = 2) + RealT = eltype(x) + eos = equations.equation_of_state + + v1 = convert(RealT, 0.1) + rho = 1 + convert(RealT, amplitude) * sinpi(k * (x[1] - v1 * t)) + p = 20 + + V = inv(rho) + + # invert for temperature given p, V + T = 1 + tol = 100 * eps(RealT) + dp = pressure(V, T, eos) - p + iter = 1 + while abs(dp) / abs(p) > tol && iter < 100 + dp = pressure(V, T, eos) - p + dpdT_V = ForwardDiff.derivative(T -> pressure(V, T, eos), T) + T = max(tol, T - dp / dpdT_V) + iter += 1 + end + if iter == 100 + println("Warning: solver for temperature(V, p) did not converge") + end + + return prim2cons(SVector(V, v1, T), equations) +end initial_condition = initial_condition_density_wave solver = DGSEM(polydeg = 3, surface_flux = flux_lax_friedrichs) diff --git a/examples/tree_1d_dgsem/elixir_euler_nonideal_transcritical_shock.jl b/examples/tree_1d_dgsem/elixir_euler_nonideal_transcritical_shock.jl index 3fe25a26589..e2368c723b7 100644 --- a/examples/tree_1d_dgsem/elixir_euler_nonideal_transcritical_shock.jl +++ b/examples/tree_1d_dgsem/elixir_euler_nonideal_transcritical_shock.jl @@ -7,6 +7,39 @@ using Trixi eos = PengRobinson() equations = NonIdealCompressibleEulerEquations1D(eos) +# the Peng-Robinson N2 transcritical shock taken from "An entropy-stable hybrid scheme +# for simulations of transcritical real-fluid flows" by Ma, Ihme (2017). +# +function initial_condition_transcritical_shock(x, t, + equations::NonIdealCompressibleEulerEquations1D{<:PengRobinson}) + RealT = eltype(x) + eos = equations.equation_of_state + + if x[1] < 0 + rho, v1, p = SVector(800, 0, 60e6) + else + rho, v1, p = SVector(80, 0, 6e6) + end + + V = inv(rho) + + # invert for temperature given p, V + T = eos.T0 + tol = 100 * eps(RealT) + dp = pressure(V, T, eos) - p + iter = 1 + while abs(dp) / abs(p) > tol && iter < 100 + dp = pressure(V, T, eos) - p + dpdT_V = ForwardDiff.derivative(T -> pressure(V, T, eos), T) + T = max(tol, T - dp / dpdT_V) + iter += 1 + end + if iter == 100 + println("Warning: solver for temperature(V, p) did not converge") + end + + return prim2cons(SVector(V, v1, T), equations) +end initial_condition = initial_condition_transcritical_shock volume_flux = flux_central_terashima_etal diff --git a/src/Trixi.jl b/src/Trixi.jl index 4b20639affe..2154dedce63 100644 --- a/src/Trixi.jl +++ b/src/Trixi.jl @@ -184,7 +184,6 @@ export AcousticPerturbationEquations2D, export NonIdealCompressibleEulerEquations1D export IdealGas, VanDerWaals, PengRobinson -export initial_condition_transcritical_wave, initial_condition_transcritical_shock export LaplaceDiffusion1D, LaplaceDiffusion2D, LaplaceDiffusion3D, LaplaceDiffusionEntropyVariables1D, LaplaceDiffusionEntropyVariables2D, diff --git a/src/equations/nonideal_compressible_euler_1d.jl b/src/equations/nonideal_compressible_euler_1d.jl index d38800c06ba..97f96c1c2b2 100644 --- a/src/equations/nonideal_compressible_euler_1d.jl +++ b/src/equations/nonideal_compressible_euler_1d.jl @@ -374,104 +374,4 @@ end return rho_e end -# The default amplitude and frequency k are consistent with initial_condition_density_wave -# for CompressibleEulerEquations1D. Note that this initial condition may not define admissible -# solution states for all non-ideal equations of state! -function initial_condition_density_wave(x, t, - equations::NonIdealCompressibleEulerEquations1D; - amplitude = 0.98, k = 2) - RealT = eltype(x) - eos = equations.equation_of_state - - v1 = convert(RealT, 0.1) - rho = 1 + convert(RealT, amplitude) * sinpi(k * (x[1] - v1 * t)) - p = 20 - - V = inv(rho) - - # invert for temperature given p, V - T = 1 - tol = 100 * eps(RealT) - dp = pressure(V, T, eos) - p - iter = 1 - while abs(dp) / abs(p) > tol && iter < 100 - dp = pressure(V, T, eos) - p - dpdT_V = ForwardDiff.derivative(T -> pressure(V, T, eos), T) - T = max(tol, T - dp / dpdT_V) - iter += 1 - end - if iter == 100 - println("Warning: solver for temperature(V, p) did not converge") - end - - return prim2cons(SVector(V, v1, T), equations) -end - -# the smooth Peng-Robinson N2 transcritical wave taken from "An entropy-stable hybrid scheme -# for simulations of transcritical real-fluid flows" by Ma, Ihme (2017). -# -function initial_condition_transcritical_wave(x, t, - equations::NonIdealCompressibleEulerEquations1D{<:PengRobinson}) - RealT = eltype(x) - eos = equations.equation_of_state - - rho_min, rho_max = 56.9, 793.1 - v1 = 100 - rho = 0.5 * (rho_min + rho_max) + - 0.5 * (rho_max - rho_min) * sin(2 * pi * (x[1] - v1 * t)) - p = 5e6 - - V = inv(rho) - - # invert for temperature given p, V - T = eos.T0 - tol = 100 * eps(RealT) - dp = pressure(V, T, eos) - p - iter = 1 - while abs(dp) / abs(p) > tol && iter < 100 - dp = pressure(V, T, eos) - p - dpdT_V = ForwardDiff.derivative(T -> pressure(V, T, eos), T) - T = max(tol, T - dp / dpdT_V) - iter += 1 - end - if iter == 100 - println("Warning: solver for temperature(V, p) did not converge") - end - - return prim2cons(SVector(V, v1, T), equations) -end - -# the Peng-Robinson N2 transcritical shock taken from "An entropy-stable hybrid scheme -# for simulations of transcritical real-fluid flows" by Ma, Ihme (2017). -# -function initial_condition_transcritical_shock(x, t, - equations::NonIdealCompressibleEulerEquations1D{<:PengRobinson}) - RealT = eltype(x) - eos = equations.equation_of_state - - if x[1] < 0 - rho, v1, p = SVector(800, 0, 60e6) - else - rho, v1, p = SVector(80, 0, 6e6) - end - - V = inv(rho) - - # invert for temperature given p, V - T = eos.T0 - tol = 100 * eps(RealT) - dp = pressure(V, T, eos) - p - iter = 1 - while abs(dp) / abs(p) > tol && iter < 100 - dp = pressure(V, T, eos) - p - dpdT_V = ForwardDiff.derivative(T -> pressure(V, T, eos), T) - T = max(tol, T - dp / dpdT_V) - iter += 1 - end - if iter == 100 - println("Warning: solver for temperature(V, p) did not converge") - end - - return prim2cons(SVector(V, v1, T), equations) -end end # @muladd diff --git a/test/test_tree_1d_euler.jl b/test/test_tree_1d_euler.jl index 6ff2703305e..db58c687bc7 100644 --- a/test/test_tree_1d_euler.jl +++ b/test/test_tree_1d_euler.jl @@ -621,6 +621,41 @@ end end @trixi_testset "elixir_euler_nonideal_density_wave.jl (transcritical wave) with Peng Robinson" begin + + # the smooth Peng-Robinson N2 transcritical wave taken from "An entropy-stable hybrid scheme + # for simulations of transcritical real-fluid flows" by Ma, Ihme (2017). + # + function initial_condition_transcritical_wave(x, t, + equations::NonIdealCompressibleEulerEquations1D{<:PengRobinson}) + RealT = eltype(x) + eos = equations.equation_of_state + + rho_min, rho_max = 56.9, 793.1 + v1 = 100 + rho = 0.5 * (rho_min + rho_max) + + 0.5 * (rho_max - rho_min) * sin(2 * pi * (x[1] - v1 * t)) + p = 5e6 + + V = inv(rho) + + # invert for temperature given p, V + T = eos.T0 + tol = 100 * eps(RealT) + dp = pressure(V, T, eos) - p + iter = 1 + while abs(dp) / abs(p) > tol && iter < 100 + dp = pressure(V, T, eos) - p + dpdT_V = ForwardDiff.derivative(T -> pressure(V, T, eos), T) + T = max(tol, T - dp / dpdT_V) + iter += 1 + end + if iter == 100 + println("Warning: solver for temperature(V, p) did not converge") + end + + return prim2cons(SVector(V, v1, T), equations) + end + @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_nonideal_density_wave.jl"), eos=PengRobinson(), From d98fc8651de4fbce81f79580c784b2b55d98c757 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Thu, 29 Jan 2026 23:10:12 -0600 Subject: [PATCH 150/360] rename a(T, eos) -> peng_robinson_a(T, eos), same for da, d2a --- .../equation_of_state_peng_robinson.jl | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/equations/equation_of_state_peng_robinson.jl b/src/equations/equation_of_state_peng_robinson.jl index 7f4376de42e..91b79dba05c 100644 --- a/src/equations/equation_of_state_peng_robinson.jl +++ b/src/equations/equation_of_state_peng_robinson.jl @@ -80,7 +80,7 @@ see also [`NonIdealCompressibleEulerEquations1D`](@ref). """ function pressure(V, T, eos::PengRobinson) (; R, b) = eos - p = R * T / (V - b) - a(T, eos) / (V^2 + 2 * b * V - b^2) + p = R * T / (V - b) - peng_robinson_a(T, eos) / (V^2 + 2 * b * V - b^2) return p end @@ -93,14 +93,14 @@ Computes internal energy for a Peng-Robinson gas from specific volume `V` and te function energy_internal(V, T, eos::PengRobinson) (; cv0) = eos K1 = calc_K1(V, eos) - e = cv0 * T + K1 * (a(T, eos) - T * da(T, eos)) + e = cv0 * T + K1 * (peng_robinson_a(T, eos) - T * peng_robinson_da(T, eos)) return e end @inline function heat_capacity_constant_volume(V, T, eos::PengRobinson) (; cv0) = eos K1 = calc_K1(V, eos) - cv = cv0 - K1 * T * d2a(T, eos) + cv = cv0 - K1 * T * peng_robinson_d2a(T, eos) return cv end @@ -110,7 +110,7 @@ function entropy_specific(V, T, eos::PengRobinson) # The specific entropy is defined up to some reference value s0, which is # arbitrarily set to zero here. K1 = calc_K1(V, eos) - return cv0 * log(T) + R * log(V - b) - da(T, eos) * K1 + return cv0 * log(T) + R * log(V - b) - peng_robinson_da(T, eos) * K1 end function speed_of_sound(V, T, eos::PengRobinson) @@ -120,7 +120,7 @@ function speed_of_sound(V, T, eos::PengRobinson) # calculate ratio of specific heats K1 = calc_K1(V, eos) - d2aT = d2a(T, eos) + d2aT = peng_robinson_d2a(T, eos) cp0 = cv0 + R cv = cv0 - K1 * T * d2aT cp = cp0 - R - K1 * T * d2aT - T * dpdT_V^2 / dpdV_T @@ -137,19 +137,19 @@ function calc_pressure_derivatives(V, T, eos::PengRobinson) (; R, b) = eos denom = (V^2 + 2 * b * V - b^2) RdivVb = R / (V - b) - dpdT_V = RdivVb - da(T, eos) / denom + dpdT_V = RdivVb - peng_robinson_da(T, eos) / denom dpdV_T = -RdivVb * T / (V - b) * - (1 - 2 * a(T, eos) / (R * T * (V + b) * (denom / (V^2 - b^2))^2)) + (1 - 2 * peng_robinson_a(T, eos) / (R * T * (V + b) * (denom / (V^2 - b^2))^2)) return dpdT_V, dpdV_T end # The following are auxiliary functions used in calculating the PR EOS -@inline function a(T, eos::PengRobinson) +@inline function peng_robinson_a(T, eos::PengRobinson) (; a0, kappa, T0) = eos return a0 * (1 + kappa * (1 - sqrt(T / T0)))^2 end -@inline da(T, eos) = ForwardDiff.derivative(T -> a(T, eos), T) -@inline d2a(T, eos) = ForwardDiff.derivative(T -> da(T, eos), T) +@inline peng_robinson_da(T, eos) = ForwardDiff.derivative(T -> peng_robinson_a(T, eos), T) +@inline peng_robinson_d2a(T, eos) = ForwardDiff.derivative(T -> peng_robinson_da(T, eos), T) @inline function calc_K1(V, eos::PengRobinson) (; inv2sqrt2b, one_minus_sqrt2_b, one_plus_sqrt2_b) = eos From ddfb408313f38003a7553aa9528fdb064dd3c3d3 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Thu, 29 Jan 2026 23:10:26 -0600 Subject: [PATCH 151/360] add ForwardDiff to elixir for use in initial condition --- .../tree_1d_dgsem/elixir_euler_nonideal_transcritical_shock.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/tree_1d_dgsem/elixir_euler_nonideal_transcritical_shock.jl b/examples/tree_1d_dgsem/elixir_euler_nonideal_transcritical_shock.jl index e2368c723b7..6caa3aa6689 100644 --- a/examples/tree_1d_dgsem/elixir_euler_nonideal_transcritical_shock.jl +++ b/examples/tree_1d_dgsem/elixir_euler_nonideal_transcritical_shock.jl @@ -1,5 +1,6 @@ using OrdinaryDiffEqLowStorageRK using Trixi +using Trixi: ForwardDiff ############################################################################### # semidiscretization of the compressible Euler equations From cf8892b69b23320d07df9ebb5e75eb39a68bd154 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Thu, 29 Jan 2026 23:11:13 -0600 Subject: [PATCH 152/360] remove duplicate callbacks --- .../elixir_euler_nonideal_density_wave.jl | 30 ------------------- 1 file changed, 30 deletions(-) diff --git a/examples/tree_1d_dgsem/elixir_euler_nonideal_density_wave.jl b/examples/tree_1d_dgsem/elixir_euler_nonideal_density_wave.jl index 6098447fdbe..83dec9001cb 100644 --- a/examples/tree_1d_dgsem/elixir_euler_nonideal_density_wave.jl +++ b/examples/tree_1d_dgsem/elixir_euler_nonideal_density_wave.jl @@ -80,36 +80,6 @@ callbacks = CallbackSet(summary_callback, ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false); - dt = stepsize_callback(ode), # solve needs some value here but it will be overwritten by the stepsize_callback - ode_default_options()..., callback = callbacks); -# ODE solvers, callbacks etc. - -tspan = (0.0, 2.0) -ode = semidiscretize(semi, tspan) - -summary_callback = SummaryCallback() - -analysis_interval = 2000 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval) - -alive_callback = AliveCallback(analysis_interval = analysis_interval) - -save_solution = SaveSolutionCallback(interval = 100, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) - -stepsize_callback = StepsizeCallback(cfl = 0.5) - -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution, - stepsize_callback) - -############################################################################### -# run the simulation - sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false); dt = stepsize_callback(ode), # solve needs some value here but it will be overwritten by the stepsize_callback ode_default_options()..., callback = callbacks); From e89759f36a47f9b7d3f069538b060d52d3ef51a5 Mon Sep 17 00:00:00 2001 From: Jesse Chan <1156048+jlchan@users.noreply.github.com> Date: Thu, 29 Jan 2026 23:12:56 -0600 Subject: [PATCH 153/360] Apply suggestions from code review Co-authored-by: Hendrik Ranocha --- src/equations/equation_of_state_peng_robinson.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/equations/equation_of_state_peng_robinson.jl b/src/equations/equation_of_state_peng_robinson.jl index 91b79dba05c..87b5f3d2588 100644 --- a/src/equations/equation_of_state_peng_robinson.jl +++ b/src/equations/equation_of_state_peng_robinson.jl @@ -13,12 +13,12 @@ given by the pressure and internal energy relations ```math p = \frac{R T}{V - b} - \frac{a(T)}{V^2 + 2bV - b^2}, \quad e = c_{v,0} T + K(a(T) - Ta'(T)) ``` -where `V = inv(rho)` and auxiliary expressions for `a(T)` and `K` are given by +where ``V = inv(rho)`` and auxiliary expressions for ``a(T)`` and ``K`` are given by ```math a(T) = a_0\left(1 + \kappa \left 1 - \sqrt{\frac{T}{T_0}}\right)\right)^2, \quad K = \frac{1}{b 2\sqrt{2}} \log\left( \frac{V + (1 - b \sqrt{2})}{V + (1 + b\sqrt{2})}\right). ``` -Moreover, `c_v = c_{v,0} - K T a''(T)`. +Moreover, ``c_v = c_{v,0} - K T a''(T)``. All expressions used here are taken from "An entropy-stable hybrid scheme for simulations of transcritical real-fluid flows" by Ma, Lv, Ihme (2017). From cc858ed3d1605213b74b4948faf754ae56f4b020 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Thu, 29 Jan 2026 23:14:48 -0600 Subject: [PATCH 154/360] 0.5 -> 0.5f0 --- test/test_tree_1d_euler.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/test_tree_1d_euler.jl b/test/test_tree_1d_euler.jl index db58c687bc7..dd179efb525 100644 --- a/test/test_tree_1d_euler.jl +++ b/test/test_tree_1d_euler.jl @@ -626,14 +626,14 @@ end # for simulations of transcritical real-fluid flows" by Ma, Ihme (2017). # function initial_condition_transcritical_wave(x, t, - equations::NonIdealCompressibleEulerEquations1D{<:PengRobinson}) + equations::NonIdealCompressibleEulerEquations1D{<:PengRobinson}) RealT = eltype(x) eos = equations.equation_of_state rho_min, rho_max = 56.9, 793.1 v1 = 100 - rho = 0.5 * (rho_min + rho_max) + - 0.5 * (rho_max - rho_min) * sin(2 * pi * (x[1] - v1 * t)) + rho = 0.5f0 * (rho_min + rho_max) + + 0.5f0 * (rho_max - rho_min) * sin(2 * pi * (x[1] - v1 * t)) p = 5e6 V = inv(rho) From fd80fd163c6923038bab403f3f390e1d3bc2ebc7 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Thu, 29 Jan 2026 23:14:55 -0600 Subject: [PATCH 155/360] formatting --- src/equations/equation_of_state_peng_robinson.jl | 9 ++++++--- src/equations/nonideal_compressible_euler_1d.jl | 1 - 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/equations/equation_of_state_peng_robinson.jl b/src/equations/equation_of_state_peng_robinson.jl index 87b5f3d2588..3e60668fb8c 100644 --- a/src/equations/equation_of_state_peng_robinson.jl +++ b/src/equations/equation_of_state_peng_robinson.jl @@ -139,7 +139,8 @@ function calc_pressure_derivatives(V, T, eos::PengRobinson) RdivVb = R / (V - b) dpdT_V = RdivVb - peng_robinson_da(T, eos) / denom dpdV_T = -RdivVb * T / (V - b) * - (1 - 2 * peng_robinson_a(T, eos) / (R * T * (V + b) * (denom / (V^2 - b^2))^2)) + (1 - + 2 * peng_robinson_a(T, eos) / (R * T * (V + b) * (denom / (V^2 - b^2))^2)) return dpdT_V, dpdV_T end @@ -148,8 +149,10 @@ end (; a0, kappa, T0) = eos return a0 * (1 + kappa * (1 - sqrt(T / T0)))^2 end -@inline peng_robinson_da(T, eos) = ForwardDiff.derivative(T -> peng_robinson_a(T, eos), T) -@inline peng_robinson_d2a(T, eos) = ForwardDiff.derivative(T -> peng_robinson_da(T, eos), T) +@inline peng_robinson_da(T, eos) = ForwardDiff.derivative(T -> peng_robinson_a(T, eos), + T) +@inline peng_robinson_d2a(T, eos) = ForwardDiff.derivative(T -> peng_robinson_da(T, eos), + T) @inline function calc_K1(V, eos::PengRobinson) (; inv2sqrt2b, one_minus_sqrt2_b, one_plus_sqrt2_b) = eos diff --git a/src/equations/nonideal_compressible_euler_1d.jl b/src/equations/nonideal_compressible_euler_1d.jl index 97f96c1c2b2..bb58647284b 100644 --- a/src/equations/nonideal_compressible_euler_1d.jl +++ b/src/equations/nonideal_compressible_euler_1d.jl @@ -373,5 +373,4 @@ end rho_e = rho_e_total - 0.5f0 * rho_v1^2 / rho return rho_e end - end # @muladd From 3dbb9e0869755929cc97d80084342737ef5b90d2 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Thu, 29 Jan 2026 23:41:27 -0600 Subject: [PATCH 156/360] add PR constructor which allows you to specify constants --- .../equation_of_state_peng_robinson.jl | 32 +++++++++++++------ 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/src/equations/equation_of_state_peng_robinson.jl b/src/equations/equation_of_state_peng_robinson.jl index 3e60668fb8c..42a318f69c6 100644 --- a/src/equations/equation_of_state_peng_robinson.jl +++ b/src/equations/equation_of_state_peng_robinson.jl @@ -39,13 +39,25 @@ struct PengRobinson{RealT <: Real} <: AbstractEquationOfState inv2sqrt2b::RealT one_minus_sqrt2_b::RealT one_plus_sqrt2_b::RealT - function PengRobinson(R, a0, b, cv0, kappa, T0) - inv2sqrt2b = inv(2 * sqrt(2) * b) - one_minus_sqrt2_b = (1 - sqrt(2)) * b - one_plus_sqrt2_b = (1 + sqrt(2)) * b - return new{typeof(R)}(R, a0, b, cv0, kappa, T0, - inv2sqrt2b, one_minus_sqrt2_b, one_plus_sqrt2_b) - end +end + +""" + PengRobinson(a0, b, cv0, kappa, T0, R = 8.31446261815324) + +Initializes a Peng-Robinson equation of state given values for physical constants. +Here, ``R`` is the universal gas constant, and the constants ``a0, b, cv0, kappa, T0`` +follow the naming conventions in Section 2.2 of "Towards a fully well-balanced and +entropy-stable scheme for the Euler equations with gravity: General equations of +state" by Michel-Dansac and Thomann (2025). + + +""" +function PengRobinson(a0, b, cv0, kappa, T0, R = 8.31446261815324) + inv2sqrt2b = inv(2 * sqrt(2) * b) + one_minus_sqrt2_b = (1 - sqrt(2)) * b + one_plus_sqrt2_b = (1 + sqrt(2)) * b + return PengRobinson{typeof(a0)}(R, a0, b, cv0, kappa, T0, + inv2sqrt2b, one_minus_sqrt2_b, one_plus_sqrt2_b) end """ @@ -55,8 +67,8 @@ By default, the Peng-Robinson parameters are in mass basis for N2. """ function PengRobinson(; RealT = Float64) Rgas = 8.31446261815324 - molar_mass_N2 = 0.02801 * 1000 # kg/m3 - R = Rgas * 1000 / molar_mass_N2 + molar_mass = 0.02801 * 1000 # kg/m3 + R = Rgas * 1000 / molar_mass pc = 3.40e6 Tc = 126.2 omega = 0.0372 @@ -64,7 +76,7 @@ function PengRobinson(; RealT = Float64) b = 0.077796 * R * Tc / pc a0 = 0.457236 * (R * Tc)^2 / pc kappa = 0.37464 + 1.54226 * omega - 0.26992 * omega^2 - return PengRobinson(RealT.((R, a0, b, cv0, kappa, Tc))...) + return PengRobinson(RealT.((a0, b, cv0, kappa, Tc, R))...) end # the default tolerance of 10 * eps() does not converge for most Peng-Robinson examples, From 17f1c3697d23f42b9d99cf10b81fa2bceb97b509 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Fri, 30 Jan 2026 00:07:59 -0600 Subject: [PATCH 157/360] adding test for indicator printing --- test/test_unit.jl | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/test_unit.jl b/test/test_unit.jl index 41734a10831..0dbbd40aec0 100644 --- a/test/test_unit.jl +++ b/test/test_unit.jl @@ -471,6 +471,9 @@ end indicator_max = IndicatorMax("variable", (; cache = nothing)) @test_nowarn show(stdout, indicator_max) + + indicator_ec = IndicatorEntropyCorrection(CompressibleEulerEquations1D(1.4), LobattoLegendreBasis(3)) + @test_nowarn show(stdout, indicator_ec) end @timed_testset "LBM 2D constructor" begin From 394e6ff9063c2c6c48769226b864855082a83f72 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Fri, 30 Jan 2026 00:13:28 -0600 Subject: [PATCH 158/360] format test indicator printing --- test/test_unit.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/test_unit.jl b/test/test_unit.jl index 0dbbd40aec0..9e27aec2598 100644 --- a/test/test_unit.jl +++ b/test/test_unit.jl @@ -472,7 +472,8 @@ end indicator_max = IndicatorMax("variable", (; cache = nothing)) @test_nowarn show(stdout, indicator_max) - indicator_ec = IndicatorEntropyCorrection(CompressibleEulerEquations1D(1.4), LobattoLegendreBasis(3)) + indicator_ec = IndicatorEntropyCorrection(CompressibleEulerEquations1D(1.4), + LobattoLegendreBasis(3)) @test_nowarn show(stdout, indicator_ec) end From 6f3ac7a0be27053e8d015e48e340b7787cad9ecc Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Fri, 30 Jan 2026 00:13:54 -0600 Subject: [PATCH 159/360] use a more general `create_cache` --- src/solvers/dgsem_tree/indicators.jl | 19 +++++++++++++++++++ src/solvers/dgsem_tree/indicators_1d.jl | 16 ---------------- src/solvers/dgsem_tree/indicators_2d.jl | 16 ---------------- 3 files changed, 19 insertions(+), 32 deletions(-) diff --git a/src/solvers/dgsem_tree/indicators.jl b/src/solvers/dgsem_tree/indicators.jl index 784cdb01b19..4fe23b17a18 100644 --- a/src/solvers/dgsem_tree/indicators.jl +++ b/src/solvers/dgsem_tree/indicators.jl @@ -300,6 +300,25 @@ function IndicatorEntropyCorrection(equations::AbstractEquations, return IndicatorEntropyCorrection{typeof(cache), typeof(scaling)}(cache, scaling) end +# this method is used when the indicator is constructed as for +# shock-capturing volume integrals. +function create_cache(::Type{IndicatorEntropyCorrection}, + equations::AbstractEquations{NDIMS, NVARS}, + basis::LobattoLegendreBasis) where {NDIMS, NVARS} + uEltype = real(basis) + AT = Array{uEltype, NDIMS + 1} + + # container for elementwise volume integrals + indicator_threaded = AT[AT(undef, NVARS, + ntuple(_ -> nnodes(basis), NDIMS)...) + for _ in 1:Threads.maxthreadid()] + + # stores the blending coefficients + alpha = Vector{uEltype}() + + return (; alpha, indicator_threaded) +end + function Base.show(io::IO, indicator::IndicatorEntropyCorrection) @nospecialize indicator # reduce precompilation time print(io, "IndicatorEntropyCorrection") diff --git a/src/solvers/dgsem_tree/indicators_1d.jl b/src/solvers/dgsem_tree/indicators_1d.jl index 0004c8b3805..8f115143368 100644 --- a/src/solvers/dgsem_tree/indicators_1d.jl +++ b/src/solvers/dgsem_tree/indicators_1d.jl @@ -186,20 +186,4 @@ function (indicator_max::IndicatorMax)(u::AbstractArray{<:Any, 3}, return alpha end - -# this method is used when the indicator is constructed as for -# shock-capturing volume integrals. -function create_cache(::Type{IndicatorEntropyCorrection}, - equations::AbstractEquations{1}, basis::LobattoLegendreBasis) - uEltype = real(basis) - MMat = MMatrix{nvariables(equations), nnodes(basis), uEltype} - - # stores the blending coefficients - alpha = Vector{uEltype}() - - # container for elementwise volume integrals - indicator_threaded = MMat[MMat(undef) for _ in 1:Threads.maxthreadid()] - - return (; alpha, indicator_threaded) -end end # @muladd diff --git a/src/solvers/dgsem_tree/indicators_2d.jl b/src/solvers/dgsem_tree/indicators_2d.jl index 1f98af1721b..82330daf57a 100644 --- a/src/solvers/dgsem_tree/indicators_2d.jl +++ b/src/solvers/dgsem_tree/indicators_2d.jl @@ -22,22 +22,6 @@ function create_cache(::Type{IndicatorHennemannGassner}, return (; alpha, alpha_tmp, indicator_threaded, modal_threaded, modal_tmp1_threaded) end -# this method is used when the indicator is constructed as for -# shock-capturing volume integrals. -function create_cache(::Type{IndicatorEntropyCorrection}, - equations::AbstractEquations{2}, basis::LobattoLegendreBasis) - uEltype = real(basis) - - # stores the blending coefficients - alpha = Vector{uEltype}() - - # container for elementwise volume integrals - indicator_threaded = [zeros(uEltype, nvariables(equations), nnodes(basis), - nnodes(basis)) for _ in 1:Threads.maxthreadid()] - - return (; alpha, indicator_threaded) -end - # Use this function barrier and unpack inside to avoid passing closures to Polyester.jl # with @batch (@threaded). # Otherwise, @threaded does not work here with Julia ARM on macOS. From ed99da6216f23279e45eeabf028d24ab9f989273 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Fri, 30 Jan 2026 00:14:00 -0600 Subject: [PATCH 160/360] avoid some allocations --- src/solvers/dgsem/calc_volume_integral.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/solvers/dgsem/calc_volume_integral.jl b/src/solvers/dgsem/calc_volume_integral.jl index b3290dd7503..5c5c1941067 100644 --- a/src/solvers/dgsem/calc_volume_integral.jl +++ b/src/solvers/dgsem/calc_volume_integral.jl @@ -219,7 +219,7 @@ function calc_volume_integral!(du, u, mesh, alpha[element] = theta # blend the high order method back in - @views du[.., element] .= du[.., element] .+ (1 - theta) * du_element + @views du[.., element] .= du[.., element] .+ (1 - theta) .* du_element end end From 28a94da796feb0b967ba43c07d92faf0e54d2ab2 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Fri, 30 Jan 2026 00:26:54 -0600 Subject: [PATCH 161/360] print out EOS parameter in Trixi.get_name --- src/equations/nonideal_compressible_euler_1d.jl | 5 +++++ test/test_unit.jl | 1 + 2 files changed, 6 insertions(+) diff --git a/src/equations/nonideal_compressible_euler_1d.jl b/src/equations/nonideal_compressible_euler_1d.jl index bb58647284b..ebb8c05a203 100644 --- a/src/equations/nonideal_compressible_euler_1d.jl +++ b/src/equations/nonideal_compressible_euler_1d.jl @@ -46,6 +46,11 @@ struct NonIdealCompressibleEulerEquations1D{EoS <: AbstractEquationOfState} <: equation_of_state::EoS end +function get_name(equations::AbstractNonIdealCompressibleEulerEquations) + return (equations |> typeof |> nameof |> string) * "{" * + (equations.equation_of_state |> typeof |> nameof |> string) * "}" +end + function varnames(::typeof(cons2cons), ::NonIdealCompressibleEulerEquations1D) return ("rho", "rho_v1", "rho_e_total") end diff --git a/test/test_unit.jl b/test/test_unit.jl index 701ddbf6d28..b656dda6908 100644 --- a/test/test_unit.jl +++ b/test/test_unit.jl @@ -783,6 +783,7 @@ end @timed_testset "Test consistency (fluxes, entropy/cons2entropy) for NonIdealCompressibleEulerEquations1D" begin eos = VanDerWaals(; a = 10, b = 0.01, R = 287, gamma = 1.4) equations = NonIdealCompressibleEulerEquations1D(eos) + @test Trixi.get_name(equations) == "NonIdealCompressibleEulerEquations1D{VanDerWaals}" q = SVector(2.0, 0.1, 10.0) V, v1, T = q u = prim2cons(q, equations) From 7c7d50ebad4f1b4749850e887d372b6ca229edf5 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Fri, 30 Jan 2026 00:27:14 -0600 Subject: [PATCH 162/360] fix transcritical wave test by moving IC to an elixir --- ...lixir_euler_nonideal_transcritical_wave.jl | 88 +++++++++++++++++++ test/test_tree_1d_euler.jl | 48 +--------- 2 files changed, 92 insertions(+), 44 deletions(-) create mode 100644 examples/tree_1d_dgsem/elixir_euler_nonideal_transcritical_wave.jl diff --git a/examples/tree_1d_dgsem/elixir_euler_nonideal_transcritical_wave.jl b/examples/tree_1d_dgsem/elixir_euler_nonideal_transcritical_wave.jl new file mode 100644 index 00000000000..e674a1205b3 --- /dev/null +++ b/examples/tree_1d_dgsem/elixir_euler_nonideal_transcritical_wave.jl @@ -0,0 +1,88 @@ +using OrdinaryDiffEqLowStorageRK +using Trixi +using Trixi: ForwardDiff + +############################################################################### +# semidiscretization of the compressible Euler equations + +eos = PengRobinson() +equations = NonIdealCompressibleEulerEquations1D(eos) + +# the smooth Peng-Robinson N2 transcritical wave taken from "An entropy-stable hybrid scheme +# for simulations of transcritical real-fluid flows" by Ma, Ihme (2017). +# +function initial_condition_transcritical_wave(x, t, + equations::NonIdealCompressibleEulerEquations1D{<:PengRobinson}) + RealT = eltype(x) + eos = equations.equation_of_state + + rho_min, rho_max = 56.9, 793.1 + v1 = 100 + rho = 0.5f0 * (rho_min + rho_max) + + 0.5f0 * (rho_max - rho_min) * sin(2 * pi * (x[1] - v1 * t)) + p = 5e6 + + V = inv(rho) + + # invert for temperature given p, V + T = eos.T0 + tol = 100 * eps(RealT) + dp = pressure(V, T, eos) - p + iter = 1 + while abs(dp) / abs(p) > tol && iter < 100 + dp = pressure(V, T, eos) - p + dpdT_V = ForwardDiff.derivative(T -> pressure(V, T, eos), T) + T = max(tol, T - dp / dpdT_V) + iter += 1 + end + if iter == 100 + println("Warning: solver for temperature(V, p) did not converge") + end + + return prim2cons(SVector(V, v1, T), equations) +end +initial_condition = initial_condition_transcritical_wave + +volume_integral = VolumeIntegralFluxDifferencing(flux_terashima_etal) +solver = DGSEM(polydeg = 3, volume_integral = volume_integral, + surface_flux = flux_lax_friedrichs) + +coordinates_min = -1.0 +coordinates_max = 1.0 +mesh = TreeMesh(coordinates_min, coordinates_max, + initial_refinement_level = 3, + n_cells_max = 30_000) + +semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) + +############################################################################### +# ODE solvers, callbacks etc. + +tspan = (0.0, 0.01) +ode = semidiscretize(semi, tspan) + +summary_callback = SummaryCallback() + +analysis_interval = 2000 +analysis_callback = AnalysisCallback(semi, interval = analysis_interval) + +alive_callback = AliveCallback(analysis_interval = analysis_interval) + +save_solution = SaveSolutionCallback(interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim) + +stepsize_callback = StepsizeCallback(cfl = 0.5) + +callbacks = CallbackSet(summary_callback, + analysis_callback, alive_callback, + save_solution, + stepsize_callback) + +############################################################################### +# run the simulation + +sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false); + dt = stepsize_callback(ode), # solve needs some value here but it will be overwritten by the stepsize_callback + ode_default_options()..., callback = callbacks); diff --git a/test/test_tree_1d_euler.jl b/test/test_tree_1d_euler.jl index dd179efb525..53cddb310bb 100644 --- a/test/test_tree_1d_euler.jl +++ b/test/test_tree_1d_euler.jl @@ -620,53 +620,13 @@ end @test_allocations(Trixi.rhs!, semi, sol, 1000) end -@trixi_testset "elixir_euler_nonideal_density_wave.jl (transcritical wave) with Peng Robinson" begin - - # the smooth Peng-Robinson N2 transcritical wave taken from "An entropy-stable hybrid scheme - # for simulations of transcritical real-fluid flows" by Ma, Ihme (2017). - # - function initial_condition_transcritical_wave(x, t, - equations::NonIdealCompressibleEulerEquations1D{<:PengRobinson}) - RealT = eltype(x) - eos = equations.equation_of_state - - rho_min, rho_max = 56.9, 793.1 - v1 = 100 - rho = 0.5f0 * (rho_min + rho_max) + - 0.5f0 * (rho_max - rho_min) * sin(2 * pi * (x[1] - v1 * t)) - p = 5e6 - - V = inv(rho) - - # invert for temperature given p, V - T = eos.T0 - tol = 100 * eps(RealT) - dp = pressure(V, T, eos) - p - iter = 1 - while abs(dp) / abs(p) > tol && iter < 100 - dp = pressure(V, T, eos) - p - dpdT_V = ForwardDiff.derivative(T -> pressure(V, T, eos), T) - T = max(tol, T - dp / dpdT_V) - iter += 1 - end - if iter == 100 - println("Warning: solver for temperature(V, p) did not converge") - end - - return prim2cons(SVector(V, v1, T), equations) - end - +@trixi_testset "elixir_euler_nonideal_transcritical_wave.jl (Peng Robinson)" begin @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_euler_nonideal_density_wave.jl"), - eos=PengRobinson(), - solver=DGSEM(polydeg = 3, - volume_integral = VolumeIntegralFluxDifferencing(flux_terashima_etal), - surface_flux = flux_lax_friedrichs), - initial_condition=initial_condition_transcritical_wave, + "elixir_euler_nonideal_transcritical_wave.jl"), tspan=(0.0, 0.001), # note that errors are large because the solution magnitude is large - l2=[3.562431427840198, 307.0804734149763, 671891.3209204111], - linf=[11.245466632528988, 1012.4992037314914, 2.1937079580614567e6]) + l2=[3.5624314278401767, 307.08047341497075, 671891.3209204172], + linf=[11.245466632528647, 1012.4992037314532, 2.193707958061479e6]) # Ensure that we do not have excessive memory allocations # (e.g., from type instabilities) From ea3347c8191bd59606bdfa1dd69c306114fb987c Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Fri, 30 Jan 2026 00:28:44 -0600 Subject: [PATCH 163/360] fix docstring for entropy_math again --- src/equations/nonideal_compressible_euler_1d.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/equations/nonideal_compressible_euler_1d.jl b/src/equations/nonideal_compressible_euler_1d.jl index ebb8c05a203..bf490838412 100644 --- a/src/equations/nonideal_compressible_euler_1d.jl +++ b/src/equations/nonideal_compressible_euler_1d.jl @@ -317,10 +317,10 @@ where `s` is the specific entropy determined by the equation of state. end """ - entropy(cons, equations::AbstractNonIdealEulerEquations) + entropy(cons, equations::AbstractNonIdealCompressibleEulerEquations) Default entropy is the mathematical entropy -[`entropy_math(u, equations::AbstractNonIdealEulerEquations)`](@ref). +[`entropy_math(u, equations::AbstractNonIdealCompressibleEulerEquations)`](@ref). """ @inline function entropy(cons, equations::AbstractNonIdealCompressibleEulerEquations) return entropy_math(cons, equations) From 395341489b9d8a3273e4dce5102c7ebcbd2ba643 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Fri, 30 Jan 2026 00:30:40 -0600 Subject: [PATCH 164/360] formatting --- test/test_unit.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/test_unit.jl b/test/test_unit.jl index b656dda6908..52c2c0b0bc8 100644 --- a/test/test_unit.jl +++ b/test/test_unit.jl @@ -783,7 +783,8 @@ end @timed_testset "Test consistency (fluxes, entropy/cons2entropy) for NonIdealCompressibleEulerEquations1D" begin eos = VanDerWaals(; a = 10, b = 0.01, R = 287, gamma = 1.4) equations = NonIdealCompressibleEulerEquations1D(eos) - @test Trixi.get_name(equations) == "NonIdealCompressibleEulerEquations1D{VanDerWaals}" + @test Trixi.get_name(equations) == + "NonIdealCompressibleEulerEquations1D{VanDerWaals}" q = SVector(2.0, 0.1, 10.0) V, v1, T = q u = prim2cons(q, equations) From 95bcf536370a85ac4a59d672a6762804e14a00ef Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Fri, 30 Jan 2026 00:50:28 -0600 Subject: [PATCH 165/360] remove stale import --- src/Trixi.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Trixi.jl b/src/Trixi.jl index 48a88dfb0c1..fde5113cb23 100644 --- a/src/Trixi.jl +++ b/src/Trixi.jl @@ -80,7 +80,7 @@ using RecursiveArrayTools: VectorOfArray using Requires: @require using Static: Static, One, True, False @reexport using StaticArrays: SVector -using StaticArrays: StaticArrays, MVector, MMatrix, MArray, SMatrix, @SMatrix +using StaticArrays: StaticArrays, MVector, MArray, SMatrix, @SMatrix using StrideArrays: PtrArray, StrideArray, StaticInt @reexport using StructArrays: StructArrays, StructArray using TimerOutputs: TimerOutputs, @notimeit, print_timer, reset_timer! From a5e80ff36fd5a58e92b05cae3b5ad45735420c3c Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Fri, 30 Jan 2026 08:20:06 -0600 Subject: [PATCH 166/360] rename indicator cache variable --- src/solvers/dgsem/calc_volume_integral.jl | 2 +- src/solvers/dgsem_tree/indicators.jl | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/solvers/dgsem/calc_volume_integral.jl b/src/solvers/dgsem/calc_volume_integral.jl index 5c5c1941067..e42c0e3cc17 100644 --- a/src/solvers/dgsem/calc_volume_integral.jl +++ b/src/solvers/dgsem/calc_volume_integral.jl @@ -163,7 +163,7 @@ function calc_volume_integral!(du, u, mesh, volume_integral::VolumeIntegralEntropyCorrection, dg::DGSEM, cache) (; volume_flux_dg, volume_flux_fv, indicator) = volume_integral - du_element_threaded = indicator.cache.indicator_threaded + du_element_threaded = indicator.cache.volume_integral_values_threaded (; scaling) = indicator (; alpha) = indicator.cache resize!(alpha, nelements(dg, cache)) diff --git a/src/solvers/dgsem_tree/indicators.jl b/src/solvers/dgsem_tree/indicators.jl index 4fe23b17a18..c8f0ef97216 100644 --- a/src/solvers/dgsem_tree/indicators.jl +++ b/src/solvers/dgsem_tree/indicators.jl @@ -309,14 +309,14 @@ function create_cache(::Type{IndicatorEntropyCorrection}, AT = Array{uEltype, NDIMS + 1} # container for elementwise volume integrals - indicator_threaded = AT[AT(undef, NVARS, - ntuple(_ -> nnodes(basis), NDIMS)...) - for _ in 1:Threads.maxthreadid()] + volume_integral_values_threaded = AT[AT(undef, NVARS, + ntuple(_ -> nnodes(basis), NDIMS)...) + for _ in 1:Threads.maxthreadid()] # stores the blending coefficients alpha = Vector{uEltype}() - return (; alpha, indicator_threaded) + return (; alpha, volume_integral_values_threaded) end function Base.show(io::IO, indicator::IndicatorEntropyCorrection) From 324b4572ee882eb056d0d4a46dc90c751c232a3e Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Fri, 30 Jan 2026 08:22:31 -0600 Subject: [PATCH 167/360] move regularized_ratio --- src/auxiliary/math.jl | 4 ++++ src/solvers/dgsem/calc_volume_integral.jl | 2 -- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/auxiliary/math.jl b/src/auxiliary/math.jl index 83bbff6f4cd..6e615ba8728 100644 --- a/src/auxiliary/math.jl +++ b/src/auxiliary/math.jl @@ -445,4 +445,8 @@ end @inline function maxmod(sl, sr) return 0.5f0 * (sign(sl) + sign(sr)) * max(abs(sl), abs(sr)) end + +# regularized approximation to the ratio a / b which is numerically stable +# for b close to zero. +@inline regularized_ratio(a, b) = a * b / (eps(b) + b^2) end # @muladd diff --git a/src/solvers/dgsem/calc_volume_integral.jl b/src/solvers/dgsem/calc_volume_integral.jl index e42c0e3cc17..d18f6db5b31 100644 --- a/src/solvers/dgsem/calc_volume_integral.jl +++ b/src/solvers/dgsem/calc_volume_integral.jl @@ -156,8 +156,6 @@ function calc_volume_integral!(du, u, mesh, return nothing end -@inline regularized_ratio(a, b) = a * b / (eps(b) + b^2) - function calc_volume_integral!(du, u, mesh, have_nonconservative_terms, equations, volume_integral::VolumeIntegralEntropyCorrection, From de3a946a0e31250467148b44cb50bd330e72e9a5 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Fri, 30 Jan 2026 08:31:08 -0600 Subject: [PATCH 168/360] format expressions --- src/equations/equation_of_state_peng_robinson.jl | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/equations/equation_of_state_peng_robinson.jl b/src/equations/equation_of_state_peng_robinson.jl index 42a318f69c6..6da7d409896 100644 --- a/src/equations/equation_of_state_peng_robinson.jl +++ b/src/equations/equation_of_state_peng_robinson.jl @@ -148,11 +148,12 @@ end function calc_pressure_derivatives(V, T, eos::PengRobinson) (; R, b) = eos denom = (V^2 + 2 * b * V - b^2) - RdivVb = R / (V - b) + a_T = peng_robinson_a(T, eos) + inv_V_minus_b = inv(V - b) + RdivVb = R * inv_V_minus_b dpdT_V = RdivVb - peng_robinson_da(T, eos) / denom - dpdV_T = -RdivVb * T / (V - b) * - (1 - - 2 * peng_robinson_a(T, eos) / (R * T * (V + b) * (denom / (V^2 - b^2))^2)) + dpdV_T = -RdivVb * T * inv_V_minus_b * + (1 - 2 * a_T / (R * T * (V + b) * (denom / (V^2 - b^2))^2)) return dpdT_V, dpdV_T end From 11455a95ef6792871ecb6f1febc4d1cd092c6915 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Fri, 30 Jan 2026 08:36:35 -0600 Subject: [PATCH 169/360] add mass basis comments --- src/equations/equation_of_state_peng_robinson.jl | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/equations/equation_of_state_peng_robinson.jl b/src/equations/equation_of_state_peng_robinson.jl index 6da7d409896..f7cb8948655 100644 --- a/src/equations/equation_of_state_peng_robinson.jl +++ b/src/equations/equation_of_state_peng_robinson.jl @@ -63,7 +63,10 @@ end """ PengRobinson(; RealT = Float64) -By default, the Peng-Robinson parameters are in mass basis for N2. +By default, the units for the Peng-Robinson parameters are in mass basis +(such as kg / m^3) as opposed to molar basis units (such as kg / mol). + +The default parameters are for N2. """ function PengRobinson(; RealT = Float64) Rgas = 8.31446261815324 From e4457b5b09fc4b9d6479424399fa908710a1c881 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Fri, 30 Jan 2026 08:52:04 -0600 Subject: [PATCH 170/360] fix references and change T0 to Tc for critical temperature T0 -> Tc --- ...ixir_euler_nonideal_transcritical_shock.jl | 2 +- ...lixir_euler_nonideal_transcritical_wave.jl | 2 +- .../equation_of_state_peng_robinson.jl | 38 ++++++++++--------- 3 files changed, 23 insertions(+), 19 deletions(-) diff --git a/examples/tree_1d_dgsem/elixir_euler_nonideal_transcritical_shock.jl b/examples/tree_1d_dgsem/elixir_euler_nonideal_transcritical_shock.jl index 6caa3aa6689..d5e44bff373 100644 --- a/examples/tree_1d_dgsem/elixir_euler_nonideal_transcritical_shock.jl +++ b/examples/tree_1d_dgsem/elixir_euler_nonideal_transcritical_shock.jl @@ -25,7 +25,7 @@ function initial_condition_transcritical_shock(x, t, V = inv(rho) # invert for temperature given p, V - T = eos.T0 + T = eos.Tc tol = 100 * eps(RealT) dp = pressure(V, T, eos) - p iter = 1 diff --git a/examples/tree_1d_dgsem/elixir_euler_nonideal_transcritical_wave.jl b/examples/tree_1d_dgsem/elixir_euler_nonideal_transcritical_wave.jl index e674a1205b3..d3c56e5e3f1 100644 --- a/examples/tree_1d_dgsem/elixir_euler_nonideal_transcritical_wave.jl +++ b/examples/tree_1d_dgsem/elixir_euler_nonideal_transcritical_wave.jl @@ -25,7 +25,7 @@ function initial_condition_transcritical_wave(x, t, V = inv(rho) # invert for temperature given p, V - T = eos.T0 + T = eos.Tc tol = 100 * eps(RealT) dp = pressure(V, T, eos) - p iter = 1 diff --git a/src/equations/equation_of_state_peng_robinson.jl b/src/equations/equation_of_state_peng_robinson.jl index f7cb8948655..e8e716fe1e9 100644 --- a/src/equations/equation_of_state_peng_robinson.jl +++ b/src/equations/equation_of_state_peng_robinson.jl @@ -20,13 +20,16 @@ K = \frac{1}{b 2\sqrt{2}} \log\left( \frac{V + (1 - b \sqrt{2})}{V + (1 + b\sqrt ``` Moreover, ``c_v = c_{v,0} - K T a''(T)``. -All expressions used here are taken from "An entropy-stable hybrid scheme for simulations -of transcritical real-fluid flows" by Ma, Lv, Ihme (2017). - +All expressions used here are taken from the following references: -See also "Towards a fully well-balanced and entropy-stable scheme for the Euler equations with -gravity: preserving isentropic steady solutions" by Berthon, Michel-Dansac, and Thomann (2024). - +- P. Ma, Y. Lv, M. Ihme (2017) + An entropy-stable hybrid scheme for simulations of transcritical real-fluid flows + [DOI: 10.1016/j.jcp.2017.03.022](https://doi.org/10.1016/j.jcp.2017.03.022) + +- V. Michel-Dansac, A. Thomann (2024) + Towards a fully well-balanced and entropy-stable scheme for the Euler equations with + gravity: preserving isentropic steady solutions + [DOI: 10.1016/j.compfluid.2025.106853](https://doi.org/10.1016/j.compfluid.2025.106853) """ struct PengRobinson{RealT <: Real} <: AbstractEquationOfState @@ -35,28 +38,29 @@ struct PengRobinson{RealT <: Real} <: AbstractEquationOfState b::RealT cv0::RealT kappa::RealT - T0::RealT + Tc::RealT inv2sqrt2b::RealT one_minus_sqrt2_b::RealT one_plus_sqrt2_b::RealT end """ - PengRobinson(a0, b, cv0, kappa, T0, R = 8.31446261815324) + PengRobinson(a0, b, cv0, kappa, Tc, R = 8.31446261815324) Initializes a Peng-Robinson equation of state given values for physical constants. -Here, ``R`` is the universal gas constant, and the constants ``a0, b, cv0, kappa, T0`` -follow the naming conventions in Section 2.2 of "Towards a fully well-balanced and -entropy-stable scheme for the Euler equations with gravity: General equations of -state" by Michel-Dansac and Thomann (2025). +Here, `R` is the universal gas constant, and the constants `a0, b, cv0, kappa, Tc` +follow the naming conventions in Section 2.2 of the following reference: - +- V. Michel-Dansac, A. Thomann (2025) + Towards a fully well-balanced and entropy-stable scheme for the Euler equations + with gravity: General equations of state + [DOI: 10.1016/j.compfluid.2025.106853](https://doi.org/10.1016/j.compfluid.2025.106853) """ -function PengRobinson(a0, b, cv0, kappa, T0, R = 8.31446261815324) +function PengRobinson(a0, b, cv0, kappa, Tc, R = 8.31446261815324) inv2sqrt2b = inv(2 * sqrt(2) * b) one_minus_sqrt2_b = (1 - sqrt(2)) * b one_plus_sqrt2_b = (1 + sqrt(2)) * b - return PengRobinson{typeof(a0)}(R, a0, b, cv0, kappa, T0, + return PengRobinson{typeof(a0)}(R, a0, b, cv0, kappa, Tc, inv2sqrt2b, one_minus_sqrt2_b, one_plus_sqrt2_b) end @@ -162,8 +166,8 @@ end # The following are auxiliary functions used in calculating the PR EOS @inline function peng_robinson_a(T, eos::PengRobinson) - (; a0, kappa, T0) = eos - return a0 * (1 + kappa * (1 - sqrt(T / T0)))^2 + (; a0, kappa, Tc) = eos + return a0 * (1 + kappa * (1 - sqrt(T / Tc)))^2 end @inline peng_robinson_da(T, eos) = ForwardDiff.derivative(T -> peng_robinson_a(T, eos), T) From 48add39f9bc96fd5b96f9edd09f769de9731ab9b Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Fri, 30 Jan 2026 08:59:46 -0600 Subject: [PATCH 171/360] change cons2prim -> cons2thermo, make cons2prim return (rho, v1, p) --- .../nonideal_compressible_euler_1d.jl | 89 ++++++++++--------- 1 file changed, 49 insertions(+), 40 deletions(-) diff --git a/src/equations/nonideal_compressible_euler_1d.jl b/src/equations/nonideal_compressible_euler_1d.jl index bf490838412..2808d034afc 100644 --- a/src/equations/nonideal_compressible_euler_1d.jl +++ b/src/equations/nonideal_compressible_euler_1d.jl @@ -54,19 +54,41 @@ end function varnames(::typeof(cons2cons), ::NonIdealCompressibleEulerEquations1D) return ("rho", "rho_v1", "rho_e_total") end -varnames(::typeof(cons2prim), ::NonIdealCompressibleEulerEquations1D) = ("V", "v1", "T") -# for plotting with PlotData1D(sol, solution_variables=density_velocity_pressure) -@inline function density_velocity_pressure(u, - equations::NonIdealCompressibleEulerEquations1D) +# returns density, velocity, and pressure +@inline function cons2prim(u, + equations::NonIdealCompressibleEulerEquations1D) eos = equations.equation_of_state rho, rho_v1, rho_e_total = u - V, v1, T = cons2prim(u, equations) + V, v1, T = cons2thermo(u, equations) return SVector(rho, v1, pressure(V, T, eos)) end -varnames(::typeof(density_velocity_pressure), ::NonIdealCompressibleEulerEquations1D) = ("rho", - "v1", - "p") + +varnames(::typeof(cons2prim), ::NonIdealCompressibleEulerEquations1D) = ("rho", + "v1", + "p") + +""" + function cons2thermo(u, equations::NonIdealCompressibleEulerEquations1D) + +Convert conservative variables to specific volume, velocity, and temperature +variables `V, v1, T`. These are referred to as "thermodynamic" variables since +equation of state routines are assumed to be evaluated in terms of `V` and `T`. +""" +@inline function cons2thermo(u, equations::NonIdealCompressibleEulerEquations1D) + eos = equations.equation_of_state + rho, rho_v1, rho_e_total = u + + V = inv(rho) + v1 = rho_v1 * V + e = (rho_e_total - 0.5f0 * rho_v1 * v1) * V + T = temperature(V, e, eos) + + return SVector(V, v1, T) +end + +varnames(::typeof(cons2thermo), ::NonIdealCompressibleEulerEquations1D) = ("V", "v1", + "T") # Calculate 1D flux for a single point @inline function flux(u, orientation::Integer, @@ -74,7 +96,7 @@ varnames(::typeof(density_velocity_pressure), ::NonIdealCompressibleEulerEquatio eos = equations.equation_of_state _, rho_v1, rho_e_total = u - V, v1, T = cons2prim(u, equations) + V, v1, T = cons2thermo(u, equations) p = pressure(V, T, eos) # Ignore orientation since it is always "1" in 1D @@ -97,8 +119,8 @@ by Terashima, Ly, Ihme (2025). @inline function flux_terashima_etal(u_ll, u_rr, orientation::Int, equations::NonIdealCompressibleEulerEquations1D) eos = equations.equation_of_state - V_ll, v1_ll, T_ll = cons2prim(u_ll, equations) - V_rr, v1_rr, T_rr = cons2prim(u_rr, equations) + V_ll, v1_ll, T_ll = cons2thermo(u_ll, equations) + V_rr, v1_rr, T_rr = cons2thermo(u_rr, equations) rho_ll = u_ll[1] rho_rr = u_rr[1] @@ -148,8 +170,8 @@ by Terashima, Ly, Ihme (2025). @inline function flux_central_terashima_etal(u_ll, u_rr, orientation::Int, equations::NonIdealCompressibleEulerEquations1D) eos = equations.equation_of_state - V_ll, v1_ll, T_ll = cons2prim(u_ll, equations) - V_rr, v1_rr, T_rr = cons2prim(u_rr, equations) + V_ll, v1_ll, T_ll = cons2thermo(u_ll, equations) + V_rr, v1_rr, T_rr = cons2thermo(u_rr, equations) rho_ll, rho_v1_ll, _ = u_ll rho_rr, rho_v1_rr, _ = u_rr @@ -193,8 +215,8 @@ end # Calculate estimates for minimum and maximum wave speeds for HLL-type fluxes @inline function min_max_speed_naive(u_ll, u_rr, orientation::Integer, equations::NonIdealCompressibleEulerEquations1D) - V_ll, v1_ll, T_ll = cons2prim(u_ll, equations) - V_rr, v1_rr, T_rr = cons2prim(u_rr, equations) + V_ll, v1_ll, T_ll = cons2thermo(u_ll, equations) + V_rr, v1_rr, T_rr = cons2thermo(u_rr, equations) eos = equations.equation_of_state c_ll = speed_of_sound(V_ll, T_ll, eos) @@ -208,8 +230,8 @@ end # Less "cautious", i.e., less overestimating `λ_max` compared to `max_abs_speed_naive` @inline function max_abs_speed(u_ll, u_rr, orientation::Integer, equations::NonIdealCompressibleEulerEquations1D) - V_ll, v1_ll, T_ll = cons2prim(u_ll, equations) - V_rr, v1_rr, T_rr = cons2prim(u_rr, equations) + V_ll, v1_ll, T_ll = cons2thermo(u_ll, equations) + V_rr, v1_rr, T_rr = cons2thermo(u_rr, equations) v_mag_ll = abs(v1_ll) v_mag_rr = abs(v1_rr) @@ -224,8 +246,8 @@ end @inline function max_abs_speed_naive(u_ll, u_rr, orientation::Integer, equations::NonIdealCompressibleEulerEquations1D) - V_ll, v1_ll, T_ll = cons2prim(u_ll, equations) - V_rr, v1_rr, T_rr = cons2prim(u_rr, equations) + V_ll, v1_ll, T_ll = cons2thermo(u_ll, equations) + V_rr, v1_rr, T_rr = cons2thermo(u_rr, equations) v_mag_ll = abs(v1_ll) v_mag_rr = abs(v1_rr) @@ -241,8 +263,8 @@ end # More refined estimates for minimum and maximum wave speeds for HLL-type fluxes @inline function min_max_speed_davis(u_ll, u_rr, orientation::Integer, equations::NonIdealCompressibleEulerEquations1D) - V_ll, v1_ll, T_ll = cons2prim(u_ll, equations) - V_rr, v1_rr, T_rr = cons2prim(u_rr, equations) + V_ll, v1_ll, T_ll = cons2thermo(u_ll, equations) + V_rr, v1_rr, T_rr = cons2thermo(u_rr, equations) # Calculate primitive variables and speed of sound eos = equations.equation_of_state @@ -256,7 +278,7 @@ end end @inline function max_abs_speeds(u, equations::NonIdealCompressibleEulerEquations1D) - V, v1, T = cons2prim(u, equations) + V, v1, T = cons2thermo(u, equations) # Calculate primitive variables and speed of sound eos = equations.equation_of_state @@ -265,22 +287,9 @@ end return (abs(v1) + c,) end -# Convert conservative variables to primitive -@inline function cons2prim(u, equations::NonIdealCompressibleEulerEquations1D) - eos = equations.equation_of_state - rho, rho_v1, rho_e_total = u - - V = inv(rho) - v1 = rho_v1 * V - e = (rho_e_total - 0.5f0 * rho_v1 * v1) * V - T = temperature(V, e, eos) - - return SVector(V, v1, T) -end - # Convert conservative variables to entropy @inline function cons2entropy(u, equations::NonIdealCompressibleEulerEquations1D) - V, v1, T = cons2prim(u, equations) + V, v1, T = cons2thermo(u, equations) eos = equations.equation_of_state gibbs = gibbs_free_energy(V, T, eos) return inv(T) * SVector(gibbs - 0.5f0 * v1^2, v1, -1) @@ -308,7 +317,7 @@ where `s` is the specific entropy determined by the equation of state. """ @inline function entropy_math(u, equations::AbstractNonIdealCompressibleEulerEquations) eos = equations.equation_of_state - q = cons2prim(u, equations) + q = cons2thermo(u, equations) V = first(q) T = last(q) rho = u[1] @@ -344,7 +353,7 @@ end @inline function pressure(u, equations::AbstractNonIdealCompressibleEulerEquations) eos = equations.equation_of_state - q = cons2prim(u, equations) + q = cons2thermo(u, equations) V = first(q) T = last(q) p = pressure(V, T, eos) @@ -355,7 +364,7 @@ end equations::AbstractNonIdealCompressibleEulerEquations) eos = equations.equation_of_state rho = u[1] - q = cons2prim(u, equations) + q = cons2thermo(u, equations) V = first(q) T = last(q) p = pressure(V, T, eos) @@ -365,7 +374,7 @@ end @inline function energy_internal(u, equations::AbstractNonIdealCompressibleEulerEquations) eos = equations.equation_of_state - q = cons2prim(u, equations) + q = cons2thermo(u, equations) V = first(q) T = last(q) e = energy_internal(V, T, eos) From ebb239d0e7160b5a90e8a4075998aca930b4ec2f Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Fri, 30 Jan 2026 09:00:52 -0600 Subject: [PATCH 172/360] rename q -> u_thermo --- .../nonideal_compressible_euler_1d.jl | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/equations/nonideal_compressible_euler_1d.jl b/src/equations/nonideal_compressible_euler_1d.jl index 2808d034afc..2f85bc85e58 100644 --- a/src/equations/nonideal_compressible_euler_1d.jl +++ b/src/equations/nonideal_compressible_euler_1d.jl @@ -317,9 +317,9 @@ where `s` is the specific entropy determined by the equation of state. """ @inline function entropy_math(u, equations::AbstractNonIdealCompressibleEulerEquations) eos = equations.equation_of_state - q = cons2thermo(u, equations) - V = first(q) - T = last(q) + u_thermo = cons2thermo(u, equations) + V = first(u_thermo) + T = last(u_thermo) rho = u[1] S = -rho * entropy_specific(V, T, eos) return S @@ -353,9 +353,9 @@ end @inline function pressure(u, equations::AbstractNonIdealCompressibleEulerEquations) eos = equations.equation_of_state - q = cons2thermo(u, equations) - V = first(q) - T = last(q) + u_thermo = cons2thermo(u, equations) + V = first(u_thermo) + T = last(u_thermo) p = pressure(V, T, eos) return p end @@ -364,9 +364,9 @@ end equations::AbstractNonIdealCompressibleEulerEquations) eos = equations.equation_of_state rho = u[1] - q = cons2thermo(u, equations) - V = first(q) - T = last(q) + u_thermo = cons2thermo(u, equations) + V = first(u_thermo) + T = last(u_thermo) p = pressure(V, T, eos) return rho * p end @@ -374,9 +374,9 @@ end @inline function energy_internal(u, equations::AbstractNonIdealCompressibleEulerEquations) eos = equations.equation_of_state - q = cons2thermo(u, equations) - V = first(q) - T = last(q) + u_thermo = cons2thermo(u, equations) + V = first(u_thermo) + T = last(u_thermo) e = energy_internal(V, T, eos) return e end From 23435ec68e915a81fe8300b832eb3d81c574ecee Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Fri, 30 Jan 2026 09:37:47 -0600 Subject: [PATCH 173/360] use `integrate_reference_element` to calculate volume entropy residual --- src/callbacks_step/analysis_dg1d.jl | 41 +++++++++------ src/callbacks_step/analysis_dg2d.jl | 61 ++++++++++++++--------- src/solvers/dgsem/calc_volume_integral.jl | 11 ++-- test/test_tree_1d_euler.jl | 16 +++--- test/test_tree_2d_euler.jl | 12 ++--- 5 files changed, 86 insertions(+), 55 deletions(-) diff --git a/src/callbacks_step/analysis_dg1d.jl b/src/callbacks_step/analysis_dg1d.jl index 7dab83a1664..21edcb43f5a 100644 --- a/src/callbacks_step/analysis_dg1d.jl +++ b/src/callbacks_step/analysis_dg1d.jl @@ -119,26 +119,39 @@ function calc_error_norms(func, u, t, analyzer, return l2_error, linf_error end +# used in `entropy_change_reference_element` and `integrate_against_entropy_variables` +function integrate_reference_element(func::Func, u, element, + mesh::AbstractMesh{1}, equations, dg::DGSEM, cache, + args...) where {Func} + @unpack weights = dg.basis + + # Initialize integral with zeros of the right shape + element_integral = zero(func(u, 1, 1, equations, dg, args...)) + + # Use quadrature to numerically integrate element. + # We do not multiply with the Jacobian to stay in reference space. + # This avoids the need to divide the RHS of the DG scheme by the Jacobian when computing + # the time derivative of entropy, see `entropy_change_reference_element`. + for i in eachnode(dg) + element_integral += weights[i] * + func(u, i, element, equations, dg, args...) + end + + return element_integral +end + # integrate `du_local` against the entropy variables evaluated at `u` function integrate_against_entropy_variables(du_local, u, element, mesh::AbstractMesh{1}, equations, dg::DGSEM, cache, args...) - (; weights) = dg.basis - - # we integrate over the reference element since `du` is calculated by - # a weak form-like volume integral, and should already be scaled by the - # Jacobian factor. - - integral = zero(eltype(u)) - - for i in eachnode(dg) - du_node = get_node_vars(du_local, equations, dg, i) + return integrate_reference_element(u, element, mesh, equations, dg, cache, + du_local) do u, i, element, equations, dg, + du_local u_node = get_node_vars(u, equations, dg, i, element) - integral = integral + - weights[i] * dot(cons2entropy(u_node, equations), du_node) - end + du_node = get_node_vars(du_local, equations, dg, i) - return integral + dot(cons2entropy(u_node, equations), du_node) + end end # calculate surface integral of func(u, orientation, equations) * normal diff --git a/src/callbacks_step/analysis_dg2d.jl b/src/callbacks_step/analysis_dg2d.jl index e52e18c2344..509a7f59d62 100644 --- a/src/callbacks_step/analysis_dg2d.jl +++ b/src/callbacks_step/analysis_dg2d.jl @@ -185,28 +185,7 @@ function calc_error_norms(func, u, t, analyzer, return l2_error, linf_error end -# integrate `du_local` against the entropy variables evaluated at `u` -function integrate_against_entropy_variables(du_local, u, element, - mesh::AbstractMesh{2}, - equations, dg::DGSEM, cache, args...) - (; weights) = dg.basis - - jacobian_1d = inv(cache.elements.inverse_jacobian[element]) # O(h) - - integral = zero(eltype(u)) - - for j in eachnode(dg), i in eachnode(dg) - du_node = get_node_vars(du_local, equations, dg, i, j) - u_node = get_node_vars(u, equations, dg, i, j, element) - integral = integral + - weights[i] * weights[j] * - dot(cons2entropy(u_node, equations), du_node) - end - - return integral * jacobian_1d -end - -# calculate surface integral of func(u, equations) * normal +# calculate surface integral of func(u, equations) * normal on the reference element. function surface_integral(func::Func, u, element, mesh::TreeMesh{2}, equations, dg::DGSEM, cache) where {Func} surface_integral = zero(real(dg)) @@ -226,8 +205,42 @@ function surface_integral(func::Func, u, element, mesh::TreeMesh{2}, equations, (func(u_right, 2, equations) - func(u_left, 2, equations)) end - jacobian_1d = inv(cache.elements.inverse_jacobian[element]) # O(h) - return surface_integral * jacobian_1d + return surface_integral +end + +# used in `entropy_change_reference_element` and `integrate_against_entropy_variables` +function integrate_reference_element(func::Func, u, element, + mesh::AbstractMesh{2}, equations, dg::DGSEM, cache, + args...) where {Func} + @unpack weights = dg.basis + + # Initialize integral with zeros of the right shape + element_integral = zero(func(u, 1, 1, 1, equations, dg, args...)) + + # Use quadrature to numerically integrate element. + # We do not multiply with the Jacobian to stay in reference space. + # This avoids the need to divide the RHS of the DG scheme by the Jacobian when computing + # the time derivative of entropy, see `entropy_change_reference_element`. + for j in eachnode(dg), i in eachnode(dg) + element_integral += weights[i] * weights[j] * + func(u, i, j, element, equations, dg, args...) + end + + return element_integral +end + +# integrate `du_local` against the entropy variables evaluated at `u` +function integrate_against_entropy_variables(du_local, u, element, + mesh::AbstractMesh{2}, + equations, dg::DGSEM, cache, args...) + return integrate_reference_element(u, element, mesh, equations, dg, cache, + du_local) do u, i, j, element, equations, dg, + du_local + u_node = get_node_vars(u, equations, dg, i, j, element) + du_node = get_node_vars(du_local, equations, dg, i, j) + + dot(cons2entropy(u_node, equations), du_node) + end end function integrate_via_indices(func::Func, u, diff --git a/src/solvers/dgsem/calc_volume_integral.jl b/src/solvers/dgsem/calc_volume_integral.jl index d18f6db5b31..5a5c7ef5340 100644 --- a/src/solvers/dgsem/calc_volume_integral.jl +++ b/src/solvers/dgsem/calc_volume_integral.jl @@ -171,7 +171,12 @@ function calc_volume_integral!(du, u, mesh, have_nonconservative_terms, equations, volume_flux_dg, dg, cache) - # check entropy production of "high order" volume integral + # Check entropy production of "high order" volume integral. + # + # Note that, for `TreeMesh`, both volume and surface integrals are calculated + # on the reference element. For other mesh types, because the volume integral + # incorporates the scaled contravariant vectors, the surface integral should + # be calculated on the physical element instead. volume_integral_entropy_vars = integrate_against_entropy_variables(view(du, .., element), u, element, @@ -182,8 +187,8 @@ function calc_volume_integral!(du, u, mesh, element, mesh, equations, dg, cache) - # this quantity should be ≤ 0 for an entropy stable volume integral, and - # exactly zero for an entropy conservative volume integral + # This quantity should be ≤ 0 for an entropy stable volume integral, and + # exactly zero for an entropy conservative volume integral. entropy_residual = -(volume_integral_entropy_vars + surface_integral_entropy_potential) diff --git a/test/test_tree_1d_euler.jl b/test/test_tree_1d_euler.jl index a3a30c8f3e2..bbeb93b7f83 100644 --- a/test/test_tree_1d_euler.jl +++ b/test/test_tree_1d_euler.jl @@ -624,8 +624,8 @@ end @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_modified_sod_entropy_correction.jl"), tspan=(0.0, 0.1), - l2=[0.17918606870085826, 0.30210796226660624, 0.5919229714363661], - linf=[0.6787210683498577, 0.8094457931331333, 1.939975952122447]) + l2=[0.17918606870048043, 0.3021079622668064, 0.591922971435801], + linf=[0.6787210684791858, 0.8094457932998395, 1.9399759521796582]) # Ensure that we do not have excessive memory allocations # (e.g., from type instabilities) @@ -643,14 +643,14 @@ end volume_flux_fv = flux_lax_friedrichs)), tspan=(0.0, 0.1), l2=[ - 0.0017122131198510152, - 0.00036348851462487426, - 0.07342375186998892 + 0.0017122131198515455, + 0.00036348851462562144, + 0.07342375187001934 ], linf=[ - 0.0031904717833848295, - 0.0018240656867681004, - 0.21543232235424625 + 0.003190471783387716, + 0.0018240656867738736, + 0.21543232235440257 ]) # Ensure that we do not have excessive memory allocations diff --git a/test/test_tree_2d_euler.jl b/test/test_tree_2d_euler.jl index 6d064b86614..edd815ed221 100644 --- a/test/test_tree_2d_euler.jl +++ b/test/test_tree_2d_euler.jl @@ -120,14 +120,14 @@ end volume_flux_fv = flux_lax_friedrichs)), tspan=(0.0, 0.1), l2=[ - 0.028712539480767976, - 0.0028712539480767085, - 0.00574250789615355, - 0.0007178134870171292 + 0.02871253948076796, + 0.0028712539480767046, + 0.00574250789615359, + 0.0007178134870180938 ], linf=[ - 0.07201294589822704, - 0.007201294589823409, + 0.07201294589822727, + 0.007201294589823548, 0.014402589179645153, 0.0018003236474513074 ]) From 2da05e48d2648784ff33e9d8751131daeebb470c Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Fri, 30 Jan 2026 09:41:45 -0600 Subject: [PATCH 174/360] fix cons2prim -> cons2thermo in tests --- test/test_unit.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_unit.jl b/test/test_unit.jl index 52c2c0b0bc8..02fabdb37d8 100644 --- a/test/test_unit.jl +++ b/test/test_unit.jl @@ -804,11 +804,11 @@ end # check that the fallback temperature and specialized temperature # return the same value - V, v1, T = cons2prim(u, equations) + V, v1, T = cons2thermo(u, equations) e = energy_internal(V, T, eos) @test temperature(V, e, eos) ≈ invoke(temperature, Tuple{Any, Any, Trixi.AbstractEquationOfState}, V, e, eos) - @test density_velocity_pressure(u, equations) ≈ + @test cons2prim(u, equations) ≈ SVector(u[1], v1, pressure(u, equations)) # check that fallback calc_pressure_derivatives matches specialized routines From afb7743ccfcf7cfa88d7bfc1197fa1202e67595c Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Fri, 30 Jan 2026 10:00:02 -0600 Subject: [PATCH 175/360] fix test --- test/test_unit.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_unit.jl b/test/test_unit.jl index 02fabdb37d8..e216cdc8bee 100644 --- a/test/test_unit.jl +++ b/test/test_unit.jl @@ -804,7 +804,7 @@ end # check that the fallback temperature and specialized temperature # return the same value - V, v1, T = cons2thermo(u, equations) + V, v1, T = Trixi.cons2thermo(u, equations) e = energy_internal(V, T, eos) @test temperature(V, e, eos) ≈ invoke(temperature, Tuple{Any, Any, Trixi.AbstractEquationOfState}, V, e, eos) From 1c5f2c37f19cb839fbe7b0201129e651fea12eb0 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Fri, 30 Jan 2026 11:05:52 -0600 Subject: [PATCH 176/360] remove unused export --- src/Trixi.jl | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Trixi.jl b/src/Trixi.jl index 2154dedce63..296f55c6fca 100644 --- a/src/Trixi.jl +++ b/src/Trixi.jl @@ -249,7 +249,6 @@ export initial_condition_eoc_test_coupled_euler_gravity, export cons2cons, cons2prim, prim2cons, cons2macroscopic, cons2state, cons2mean, cons2entropy, entropy2cons export density, pressure, density_pressure, velocity, temperature, - density_velocity_pressure, global_mean_vars, equilibrium_distribution, waterheight, waterheight_pressure From 1516f3ed507a4c64ba1b65215ba2eaec2e263d0d Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Fri, 30 Jan 2026 14:32:58 -0600 Subject: [PATCH 177/360] remove unused varnames --- src/equations/nonideal_compressible_euler_1d.jl | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/equations/nonideal_compressible_euler_1d.jl b/src/equations/nonideal_compressible_euler_1d.jl index 2f85bc85e58..c6f5a36c64b 100644 --- a/src/equations/nonideal_compressible_euler_1d.jl +++ b/src/equations/nonideal_compressible_euler_1d.jl @@ -87,9 +87,6 @@ equation of state routines are assumed to be evaluated in terms of `V` and `T`. return SVector(V, v1, T) end -varnames(::typeof(cons2thermo), ::NonIdealCompressibleEulerEquations1D) = ("V", "v1", - "T") - # Calculate 1D flux for a single point @inline function flux(u, orientation::Integer, equations::NonIdealCompressibleEulerEquations1D) From bc379767e357acdbaf75de58c7d69c0834d74fa8 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Fri, 30 Jan 2026 16:10:09 -0600 Subject: [PATCH 178/360] renaming cons2prim -> cons2thermo --- .../nonideal_compressible_euler_2d.jl | 81 ++++++++++--------- 1 file changed, 42 insertions(+), 39 deletions(-) diff --git a/src/equations/nonideal_compressible_euler_2d.jl b/src/equations/nonideal_compressible_euler_2d.jl index 391e93ada83..fa0235631e4 100644 --- a/src/equations/nonideal_compressible_euler_2d.jl +++ b/src/equations/nonideal_compressible_euler_2d.jl @@ -54,21 +54,18 @@ end function varnames(::typeof(cons2cons), ::NonIdealCompressibleEulerEquations2D) return ("rho", "rho_v1", "rho_v2", "rho_e_total") end -varnames(::typeof(cons2prim), ::NonIdealCompressibleEulerEquations2D) = ("V", "v1", - "v2", "T") # for plotting with PlotData1D(sol, solution_variables=density_velocity_pressure) -@inline function density_velocity_pressure(u, - equations::NonIdealCompressibleEulerEquations2D) +@inline function cons2prim(u, equations::NonIdealCompressibleEulerEquations2D) eos = equations.equation_of_state rho = u[1] - V, v1, v2, T = cons2prim(u, equations) + V, v1, v2, T = cons2thermo(u, equations) return SVector(rho, v1, v2, pressure(V, T, eos)) end -varnames(::typeof(density_velocity_pressure), ::NonIdealCompressibleEulerEquations2D) = ("rho", - "v1", - "v2", - "p") +varnames(::typeof(cons2prim), ::NonIdealCompressibleEulerEquations2D) = ("rho", + "v1", + "v2", + "p") # Calculate flux for a single point @inline function flux(u, orientation::Integer, @@ -76,7 +73,7 @@ varnames(::typeof(density_velocity_pressure), ::NonIdealCompressibleEulerEquatio eos = equations.equation_of_state _, rho_v1, rho_v2, rho_e_total = u - V, v1, v2, T = cons2prim(u, equations) + V, v1, v2, T = cons2thermo(u, equations) p = pressure(V, T, eos) if orientation == 1 @@ -100,7 +97,7 @@ end rho = first(u) rho_e_total = last(u) - V, v1, v2, T = cons2prim(u, equations) + V, v1, v2, T = cons2thermo(u, equations) p = pressure(V, T, eos) v_normal = v1 * normal_direction[1] + v2 * normal_direction[2] @@ -230,8 +227,8 @@ by Terashima, Ly, Ihme (2025). https://doi.org/10.1016/j.jcp.2024.11370 1 function flux_terashima_etal(u_ll, u_rr, orientation::Int, equations::NonIdealCompressibleEulerEquations2D) eos = equations.equation_of_state - V_ll, v1_ll, v2_ll, T_ll = cons2prim(u_ll, equations) - V_rr, v1_rr, v2_rr, T_rr = cons2prim(u_rr, equations) + V_ll, v1_ll, v2_ll, T_ll = cons2thermo(u_ll, equations) + V_rr, v1_rr, v2_rr, T_rr = cons2thermo(u_rr, equations) rho_ll = u_ll[1] rho_rr = u_rr[1] @@ -274,8 +271,8 @@ end function flux_terashima_etal(u_ll, u_rr, normal_direction::AbstractVector, equations::NonIdealCompressibleEulerEquations2D) eos = equations.equation_of_state - V_ll, v1_ll, v2_ll, T_ll = cons2prim(u_ll, equations) - V_rr, v1_rr, v2_rr, T_rr = cons2prim(u_rr, equations) + V_ll, v1_ll, v2_ll, T_ll = cons2thermo(u_ll, equations) + V_rr, v1_rr, v2_rr, T_rr = cons2thermo(u_rr, equations) rho_ll = u_ll[1] rho_rr = u_rr[1] @@ -324,8 +321,8 @@ by Terashima, Ly, Ihme (2025). https://doi.org/10.1016/j.jcp.2024.11370 function flux_central_terashima_etal(u_ll, u_rr, orientation::Int, equations::NonIdealCompressibleEulerEquations2D) eos = equations.equation_of_state - V_ll, v1_ll, v2_ll, T_ll = cons2prim(u_ll, equations) - V_rr, v1_rr, v2_rr, T_rr = cons2prim(u_rr, equations) + V_ll, v1_ll, v2_ll, T_ll = cons2thermo(u_ll, equations) + V_rr, v1_rr, v2_rr, T_rr = cons2thermo(u_rr, equations) rho_ll, rho_v1_ll, rho_v2_ll, rho_e_total_ll = u_ll rho_rr, rho_v1_rr, rho_v2_rr, rho_e_total_rr = u_rr @@ -373,8 +370,8 @@ end function flux_central_terashima_etal(u_ll, u_rr, normal_direction::AbstractVector, equations::NonIdealCompressibleEulerEquations2D) eos = equations.equation_of_state - V_ll, v1_ll, v2_ll, T_ll = cons2prim(u_ll, equations) - V_rr, v1_rr, v2_rr, T_rr = cons2prim(u_rr, equations) + V_ll, v1_ll, v2_ll, T_ll = cons2thermo(u_ll, equations) + V_rr, v1_rr, v2_rr, T_rr = cons2thermo(u_rr, equations) rho_ll, rho_v1_ll, rho_v2_ll, rho_e_total_ll = u_ll rho_rr, rho_v1_rr, rho_v2_rr, rho_e_total_rr = u_rr @@ -422,8 +419,8 @@ end # Calculate estimates for minimum and maximum wave speeds for HLL-type fluxes @inline function min_max_speed_naive(u_ll, u_rr, orientation::Integer, equations::NonIdealCompressibleEulerEquations2D) - V_ll, v1_ll, v2_ll, T_ll = cons2prim(u_ll, equations) - V_rr, v1_rr, v2_rr, T_rr = cons2prim(u_rr, equations) + V_ll, v1_ll, v2_ll, T_ll = cons2thermo(u_ll, equations) + V_rr, v1_rr, v2_rr, T_rr = cons2thermo(u_rr, equations) eos = equations.equation_of_state c_ll = speed_of_sound(V_ll, T_ll, eos) @@ -442,8 +439,8 @@ end @inline function min_max_speed_naive(u_ll, u_rr, normal_direction::AbstractVector, equations::NonIdealCompressibleEulerEquations2D) - V_ll, v1_ll, v2_ll, T_ll = cons2prim(u_ll, equations) - V_rr, v1_rr, v2_rr, T_rr = cons2prim(u_rr, equations) + V_ll, v1_ll, v2_ll, T_ll = cons2thermo(u_ll, equations) + V_rr, v1_rr, v2_rr, T_rr = cons2thermo(u_rr, equations) v_normal_ll = v1_ll * normal_direction[1] + v2_ll * normal_direction[2] v_normal_rr = v1_rr * normal_direction[1] + v2_rr * normal_direction[2] @@ -463,8 +460,8 @@ end # Less "cautious", i.e., less overestimating `λ_max` compared to `max_abs_speed_naive` @inline function max_abs_speed(u_ll, u_rr, orientation::Integer, equations::NonIdealCompressibleEulerEquations2D) - V_ll, v1_ll, v2_ll, T_ll = cons2prim(u_ll, equations) - V_rr, v1_rr, v2_rr, T_rr = cons2prim(u_rr, equations) + V_ll, v1_ll, v2_ll, T_ll = cons2thermo(u_ll, equations) + V_rr, v1_rr, v2_rr, T_rr = cons2thermo(u_rr, equations) # Get the velocity value in the appropriate direction if orientation == 1 @@ -487,8 +484,8 @@ end @inline function max_abs_speed(u_ll, u_rr, normal_direction::AbstractVector, equations::NonIdealCompressibleEulerEquations2D) - V_ll, v1_ll, v2_ll, T_ll = cons2prim(u_ll, equations) - V_rr, v1_rr, v2_rr, T_rr = cons2prim(u_rr, equations) + V_ll, v1_ll, v2_ll, T_ll = cons2thermo(u_ll, equations) + V_rr, v1_rr, v2_rr, T_rr = cons2thermo(u_rr, equations) # Calculate normal velocities and sound speeds # left @@ -513,8 +510,8 @@ end @inline function max_abs_speed_naive(u_ll, u_rr, orientation::Integer, equations::NonIdealCompressibleEulerEquations2D) - V_ll, v1_ll, v2_ll, T_ll = cons2prim(u_ll, equations) - V_rr, v1_rr, v2_rr, T_rr = cons2prim(u_rr, equations) + V_ll, v1_ll, v2_ll, T_ll = cons2thermo(u_ll, equations) + V_rr, v1_rr, v2_rr, T_rr = cons2thermo(u_rr, equations) # Get the velocity value in the appropriate direction if orientation == 1 @@ -538,8 +535,8 @@ end @inline function max_abs_speed_naive(u_ll, u_rr, normal_direction::AbstractVector, equations::NonIdealCompressibleEulerEquations2D) - V_ll, v1_ll, v2_ll, T_ll = cons2prim(u_ll, equations) - V_rr, v1_rr, v2_rr, T_rr = cons2prim(u_rr, equations) + V_ll, v1_ll, v2_ll, T_ll = cons2thermo(u_ll, equations) + V_rr, v1_rr, v2_rr, T_rr = cons2thermo(u_rr, equations) # Get the velocity value in the appropriate direction v_dot_n_ll = v1_ll * normal_direction[1] + v2_ll * normal_direction[2] @@ -559,8 +556,8 @@ end # More refined estimates for minimum and maximum wave speeds for HLL-type fluxes @inline function min_max_speed_davis(u_ll, u_rr, orientation::Integer, equations::NonIdealCompressibleEulerEquations2D) - V_ll, v1_ll, v2_ll, T_ll = cons2prim(u_ll, equations) - V_rr, v1_rr, v2_rr, T_rr = cons2prim(u_rr, equations) + V_ll, v1_ll, v2_ll, T_ll = cons2thermo(u_ll, equations) + V_rr, v1_rr, v2_rr, T_rr = cons2thermo(u_rr, equations) # Calculate primitive variables and speed of sound eos = equations.equation_of_state @@ -581,8 +578,8 @@ end # More refined estimates for minimum and maximum wave speeds for HLL-type fluxes @inline function min_max_speed_davis(u_ll, u_rr, normal_direction::AbstractVector, equations::NonIdealCompressibleEulerEquations2D) - V_ll, v1_ll, v2_ll, T_ll = cons2prim(u_ll, equations) - V_rr, v1_rr, v2_rr, T_rr = cons2prim(u_rr, equations) + V_ll, v1_ll, v2_ll, T_ll = cons2thermo(u_ll, equations) + V_rr, v1_rr, v2_rr, T_rr = cons2thermo(u_rr, equations) norm_ = norm(normal_direction) @@ -602,7 +599,7 @@ end end @inline function max_abs_speeds(u, equations::NonIdealCompressibleEulerEquations2D) - V, v1, v2, T = cons2prim(u, equations) + V, v1, v2, T = cons2thermo(u, equations) # Calculate primitive variables and speed of sound eos = equations.equation_of_state @@ -611,8 +608,14 @@ end return (abs(v1) + c, abs(v2) + c) end -# Convert conservative variables to primitive -@inline function cons2prim(u, equations::NonIdealCompressibleEulerEquations2D) +""" + function cons2thermo(u, equations::NonIdealCompressibleEulerEquations2D) + +Convert conservative variables to specific volume, velocity, and temperature +variables `V, v1, v2, T`. These are referred to as "thermodynamic" variables since +equation of state routines are assumed to be evaluated in terms of `V` and `T`. +""" +@inline function cons2thermo(u, equations::NonIdealCompressibleEulerEquations2D) eos = equations.equation_of_state rho, rho_v1, rho_v2, rho_e_total = u @@ -627,7 +630,7 @@ end # Convert conservative variables to entropy @inline function cons2entropy(u, equations::NonIdealCompressibleEulerEquations2D) - V, v1, v2, T = cons2prim(u, equations) + V, v1, v2, T = cons2thermo(u, equations) eos = equations.equation_of_state gibbs = gibbs_free_energy(V, T, eos) return inv(T) * SVector(gibbs - 0.5f0 * (v1^2 + v2^2), v1, v2, -1) From 0676c63e4eb670e53838c5c5855c479d6c1c1ec2 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Fri, 30 Jan 2026 21:04:07 -0600 Subject: [PATCH 179/360] remove initial conditions --- .../nonideal_compressible_euler_1d.jl | 138 ------------------ 1 file changed, 138 deletions(-) diff --git a/src/equations/nonideal_compressible_euler_1d.jl b/src/equations/nonideal_compressible_euler_1d.jl index 2cf9258e999..c6f5a36c64b 100644 --- a/src/equations/nonideal_compressible_euler_1d.jl +++ b/src/equations/nonideal_compressible_euler_1d.jl @@ -384,142 +384,4 @@ end rho_e = rho_e_total - 0.5f0 * rho_v1^2 / rho return rho_e end - -# The default amplitude and frequency k are consistent with initial_condition_density_wave -# for CompressibleEulerEquations1D. Note that this initial condition may not define admissible -# solution states for all non-ideal equations of state! -function initial_condition_density_wave(x, t, - equations::NonIdealCompressibleEulerEquations1D; - amplitude = 0.98, k = 2) - RealT = eltype(x) - eos = equations.equation_of_state - - v1 = convert(RealT, 0.1) - rho = 1 + convert(RealT, amplitude) * sinpi(k * (x[1] - v1 * t)) - p = 20 - - V = inv(rho) - - # invert for temperature given p, V - T = 1 - tol = 100 * eps(RealT) - dp = pressure(V, T, eos) - p - iter = 1 - while abs(dp) / abs(p) > tol && iter < 100 - dp = pressure(V, T, eos) - p - dpdT_V = ForwardDiff.derivative(T -> pressure(V, T, eos), T) - T = max(tol, T - dp / dpdT_V) - iter += 1 - end - if iter == 100 - println("Warning: solver for temperature(V, p) did not converge") - end - - return prim2cons(SVector(V, v1, T), equations) -end - -# The 1D Riemann problem from "An oscillation free shock-capturing method -# for compressible van der Waals supercritical fluid flows" by Pantano, -# Saurel, and Schmitt (2017). -# -# This is intended to be run with the `VanDerWaals(; gamma=1.4)` eos until -# final time 14e-3 on the domain [-12, 12]. -function initial_condition_Riemann_problem(x, t, - equations::NonIdealCompressibleEulerEquations1D{<:VanDerWaals}) - RealT = eltype(x) - eos = equations.equation_of_state - - if x[1] < 0 - rho, v1, p = SVector(497.417, 0, 4e7) - else - rho, v1, p = SVector(13.33117, 0, 4e6) # 6e6 - end - - V = inv(rho) - - # invert for temperature given p, V - T = RealT(1.0) - tol = 100 * eps(RealT) - dp = pressure(V, T, eos) - p - iter = 1 - while abs(dp) / abs(p) > tol && iter < 100 - dp = pressure(V, T, eos) - p - dpdT_V = ForwardDiff.derivative(T -> pressure(V, T, eos), T) - T = max(tol, T - dp / dpdT_V) - iter += 1 - end - if iter == 100 - println("Warning: solver for temperature(V, p) did not converge") - end - - return prim2cons(SVector(V, v1, T), equations) -end - -# the smooth Peng-Robinson N2 transcritical wave taken from "An entropy-stable hybrid scheme -# for simulations of transcritical real-fluid flows" by Ma, Ihme (2017). -# -function initial_condition_transcritical_wave(x, t, - equations::NonIdealCompressibleEulerEquations1D{<:PengRobinson}) - RealT = eltype(x) - eos = equations.equation_of_state - - rho_min, rho_max = 56.9, 793.1 - v1 = 100 - rho = 0.5 * (rho_min + rho_max) + - 0.5 * (rho_max - rho_min) * sin(2 * pi * (x[1] - v1 * t)) - p = 5e6 - - V = inv(rho) - - # invert for temperature given p, V - T = eos.T0 - tol = 100 * eps(RealT) - dp = pressure(V, T, eos) - p - iter = 1 - while abs(dp) / abs(p) > tol && iter < 100 - dp = pressure(V, T, eos) - p - dpdT_V = ForwardDiff.derivative(T -> pressure(V, T, eos), T) - T = max(tol, T - dp / dpdT_V) - iter += 1 - end - if iter == 100 - println("Warning: solver for temperature(V, p) did not converge") - end - - return prim2cons(SVector(V, v1, T), equations) -end - -# the Peng-Robinson N2 transcritical shock taken from "An entropy-stable hybrid scheme -# for simulations of transcritical real-fluid flows" by Ma, Ihme (2017). -# -function initial_condition_transcritical_shock(x, t, - equations::NonIdealCompressibleEulerEquations1D{<:PengRobinson}) - RealT = eltype(x) - eos = equations.equation_of_state - - if x[1] < 0 - rho, v1, p = SVector(800, 0, 60e6) - else - rho, v1, p = SVector(80, 0, 6e6) - end - - V = inv(rho) - - # invert for temperature given p, V - T = eos.T0 - tol = 100 * eps(RealT) - dp = pressure(V, T, eos) - p - iter = 1 - while abs(dp) / abs(p) > tol && iter < 100 - dp = pressure(V, T, eos) - p - dpdT_V = ForwardDiff.derivative(T -> pressure(V, T, eos), T) - T = max(tol, T - dp / dpdT_V) - iter += 1 - end - if iter == 100 - println("Warning: solver for temperature(V, p) did not converge") - end - - return prim2cons(SVector(V, v1, T), equations) -end end # @muladd From 47c1f08eed040c9c953d8d7b34c784b265f6d57b Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Sat, 31 Jan 2026 17:37:49 -0600 Subject: [PATCH 180/360] add transcritical comment --- .../elixir_euler_nonideal_transcritical_wave.jl | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/examples/tree_1d_dgsem/elixir_euler_nonideal_transcritical_wave.jl b/examples/tree_1d_dgsem/elixir_euler_nonideal_transcritical_wave.jl index d3c56e5e3f1..d419cc7f9a8 100644 --- a/examples/tree_1d_dgsem/elixir_euler_nonideal_transcritical_wave.jl +++ b/examples/tree_1d_dgsem/elixir_euler_nonideal_transcritical_wave.jl @@ -8,8 +8,11 @@ using Trixi: ForwardDiff eos = PengRobinson() equations = NonIdealCompressibleEulerEquations1D(eos) -# the smooth Peng-Robinson N2 transcritical wave taken from "An entropy-stable hybrid scheme -# for simulations of transcritical real-fluid flows" by Ma, Ihme (2017). +# the smooth Peng-Robinson N2 transcritical wave taken from "An entropy-stable hybrid +# scheme for simulations of transcritical real-fluid flows" by Ma, Ihme (2017). In this +# context, the wave is "transcritical" because the solution involves both subcritical +# and supercritical density and temperature values. +# # function initial_condition_transcritical_wave(x, t, equations::NonIdealCompressibleEulerEquations1D{<:PengRobinson}) From 4064b504cdcdde407768722b55d68e961d463f35 Mon Sep 17 00:00:00 2001 From: Jesse Chan <1156048+jlchan@users.noreply.github.com> Date: Sat, 31 Jan 2026 17:45:36 -0600 Subject: [PATCH 181/360] Apply suggestions from code review Co-authored-by: Daniel Doehring --- src/equations/equation_of_state_peng_robinson.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/equations/equation_of_state_peng_robinson.jl b/src/equations/equation_of_state_peng_robinson.jl index e8e716fe1e9..d2f97ae5dd2 100644 --- a/src/equations/equation_of_state_peng_robinson.jl +++ b/src/equations/equation_of_state_peng_robinson.jl @@ -103,7 +103,7 @@ function pressure(V, T, eos::PengRobinson) return p end -""" +@doc raw""" energy_internal(V, T, eos::PengRobinson) Computes internal energy for a Peng-Robinson gas from specific volume `V` and temperature `T` as From 7272fa58a05922b8661430c87e109d48dd19648c Mon Sep 17 00:00:00 2001 From: Daniel_Doehring Date: Mon, 2 Feb 2026 08:53:00 +0100 Subject: [PATCH 182/360] move down --- src/callbacks_step/analysis_dg2d.jl | 46 ++++++++++++++--------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/src/callbacks_step/analysis_dg2d.jl b/src/callbacks_step/analysis_dg2d.jl index 509a7f59d62..46ad6cb34eb 100644 --- a/src/callbacks_step/analysis_dg2d.jl +++ b/src/callbacks_step/analysis_dg2d.jl @@ -185,29 +185,6 @@ function calc_error_norms(func, u, t, analyzer, return l2_error, linf_error end -# calculate surface integral of func(u, equations) * normal on the reference element. -function surface_integral(func::Func, u, element, mesh::TreeMesh{2}, equations, - dg::DGSEM, cache) where {Func} - surface_integral = zero(real(dg)) - for ii in eachnode(dg) - # x direction - u_left = get_node_vars(u, equations, dg, 1, ii, element) - u_right = get_node_vars(u, equations, dg, nnodes(dg), ii, element) - surface_integral = surface_integral + - dg.basis.weights[ii] * - (func(u_right, 1, equations) - func(u_left, 1, equations)) - - # y direction - u_left = get_node_vars(u, equations, dg, ii, 1, element) - u_right = get_node_vars(u, equations, dg, ii, nnodes(dg), element) - surface_integral = surface_integral + - dg.basis.weights[ii] * - (func(u_right, 2, equations) - func(u_left, 2, equations)) - end - - return surface_integral -end - # used in `entropy_change_reference_element` and `integrate_against_entropy_variables` function integrate_reference_element(func::Func, u, element, mesh::AbstractMesh{2}, equations, dg::DGSEM, cache, @@ -243,6 +220,29 @@ function integrate_against_entropy_variables(du_local, u, element, end end +# calculate surface integral of func(u, equations) * normal on the reference element. +function surface_integral(func::Func, u, element, mesh::TreeMesh{2}, equations, + dg::DGSEM, cache) where {Func} + surface_integral = zero(real(dg)) + for ii in eachnode(dg) + # x direction + u_left = get_node_vars(u, equations, dg, 1, ii, element) + u_right = get_node_vars(u, equations, dg, nnodes(dg), ii, element) + surface_integral = surface_integral + + dg.basis.weights[ii] * + (func(u_right, 1, equations) - func(u_left, 1, equations)) + + # y direction + u_left = get_node_vars(u, equations, dg, ii, 1, element) + u_right = get_node_vars(u, equations, dg, ii, nnodes(dg), element) + surface_integral = surface_integral + + dg.basis.weights[ii] * + (func(u_right, 2, equations) - func(u_left, 2, equations)) + end + + return surface_integral +end + function integrate_via_indices(func::Func, u, mesh::TreeMesh{2}, equations, dg::DGSEM, cache, args...; normalize = true) where {Func} From 0bcd7055389fbd6d0189e030a3cd1df3516deab8 Mon Sep 17 00:00:00 2001 From: Daniel_Doehring Date: Mon, 2 Feb 2026 08:54:36 +0100 Subject: [PATCH 183/360] make similar --- src/callbacks_step/analysis_dg2d.jl | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/callbacks_step/analysis_dg2d.jl b/src/callbacks_step/analysis_dg2d.jl index 46ad6cb34eb..db231e0c415 100644 --- a/src/callbacks_step/analysis_dg2d.jl +++ b/src/callbacks_step/analysis_dg2d.jl @@ -185,7 +185,10 @@ function calc_error_norms(func, u, t, analyzer, return l2_error, linf_error end -# used in `entropy_change_reference_element` and `integrate_against_entropy_variables` +# Use quadrature to numerically integrate a single element. +# We do not multiply with the Jacobian to stay in reference space. +# This avoids the need to divide the RHS of the DG scheme by the Jacobian when computing +# the time derivative of entropy, see `entropy_change_reference_element`. function integrate_reference_element(func::Func, u, element, mesh::AbstractMesh{2}, equations, dg::DGSEM, cache, args...) where {Func} @@ -194,10 +197,6 @@ function integrate_reference_element(func::Func, u, element, # Initialize integral with zeros of the right shape element_integral = zero(func(u, 1, 1, 1, equations, dg, args...)) - # Use quadrature to numerically integrate element. - # We do not multiply with the Jacobian to stay in reference space. - # This avoids the need to divide the RHS of the DG scheme by the Jacobian when computing - # the time derivative of entropy, see `entropy_change_reference_element`. for j in eachnode(dg), i in eachnode(dg) element_integral += weights[i] * weights[j] * func(u, i, j, element, equations, dg, args...) @@ -206,10 +205,11 @@ function integrate_reference_element(func::Func, u, element, return element_integral end -# integrate `du_local` against the entropy variables evaluated at `u` -function integrate_against_entropy_variables(du_local, u, element, - mesh::AbstractMesh{2}, - equations, dg::DGSEM, cache, args...) +# Calculate ∫_e (∂S/∂u ⋅ ∂u/∂t) dΩ_e where the result on element 'e' is kept in reference space +# Note that ∂S/∂u = w(u) with entropy variables w +function entropy_change_reference_element(du_local, u, element, + mesh::AbstractMesh{2}, + equations, dg::DGSEM, cache, args...) return integrate_reference_element(u, element, mesh, equations, dg, cache, du_local) do u, i, j, element, equations, dg, du_local From 62bfe3ae0c25d76846632515d474d1147f9a6728 Mon Sep 17 00:00:00 2001 From: Daniel_Doehring Date: Mon, 2 Feb 2026 08:56:01 +0100 Subject: [PATCH 184/360] consistent --- src/callbacks_step/analysis_dg1d.jl | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/callbacks_step/analysis_dg1d.jl b/src/callbacks_step/analysis_dg1d.jl index 21edcb43f5a..d7b28eb0326 100644 --- a/src/callbacks_step/analysis_dg1d.jl +++ b/src/callbacks_step/analysis_dg1d.jl @@ -119,7 +119,10 @@ function calc_error_norms(func, u, t, analyzer, return l2_error, linf_error end -# used in `entropy_change_reference_element` and `integrate_against_entropy_variables` +# Use quadrature to numerically integrate a single element. +# We do not multiply with the Jacobian to stay in reference space. +# This avoids the need to divide the RHS of the DG scheme by the Jacobian when computing +# the time derivative of entropy, see `entropy_change_reference_element`. function integrate_reference_element(func::Func, u, element, mesh::AbstractMesh{1}, equations, dg::DGSEM, cache, args...) where {Func} @@ -128,10 +131,6 @@ function integrate_reference_element(func::Func, u, element, # Initialize integral with zeros of the right shape element_integral = zero(func(u, 1, 1, equations, dg, args...)) - # Use quadrature to numerically integrate element. - # We do not multiply with the Jacobian to stay in reference space. - # This avoids the need to divide the RHS of the DG scheme by the Jacobian when computing - # the time derivative of entropy, see `entropy_change_reference_element`. for i in eachnode(dg) element_integral += weights[i] * func(u, i, element, equations, dg, args...) @@ -140,10 +139,11 @@ function integrate_reference_element(func::Func, u, element, return element_integral end -# integrate `du_local` against the entropy variables evaluated at `u` -function integrate_against_entropy_variables(du_local, u, element, - mesh::AbstractMesh{1}, - equations, dg::DGSEM, cache, args...) +# Calculate ∫_e (∂S/∂u ⋅ ∂u/∂t) dΩ_e where the result on element 'e' is kept in reference space +# Note that ∂S/∂u = w(u) with entropy variables w +function entropy_change_reference_element(du_local, u, element, + mesh::AbstractMesh{1}, + equations, dg::DGSEM, cache, args...) return integrate_reference_element(u, element, mesh, equations, dg, cache, du_local) do u, i, element, equations, dg, du_local From a69c83dc342c47baaf6b46da4f935267c6060a3d Mon Sep 17 00:00:00 2001 From: Daniel_Doehring Date: Mon, 2 Feb 2026 09:07:44 +0100 Subject: [PATCH 185/360] more --- src/callbacks_step/analysis_dg1d.jl | 10 +++++---- src/callbacks_step/analysis_dg2d.jl | 27 ++++++++++++----------- src/equations/compressible_euler_1d.jl | 11 +++++---- src/equations/compressible_euler_2d.jl | 9 +++++--- src/solvers/dgsem/calc_volume_integral.jl | 23 ++++++++++--------- 5 files changed, 45 insertions(+), 35 deletions(-) diff --git a/src/callbacks_step/analysis_dg1d.jl b/src/callbacks_step/analysis_dg1d.jl index d7b28eb0326..dba6a6b3b4a 100644 --- a/src/callbacks_step/analysis_dg1d.jl +++ b/src/callbacks_step/analysis_dg1d.jl @@ -154,12 +154,14 @@ function entropy_change_reference_element(du_local, u, element, end end -# calculate surface integral of func(u, orientation, equations) * normal -function surface_integral(func::Func, u, element, mesh::TreeMesh{1}, equations, - dg::DGSEM, cache) where {Func} +# calculate surface integral of func(u, equations) * normal on the reference element. +function surface_integral(func::Func, u, element, + mesh::TreeMesh{1}, equations, dg::DGSEM, cache, + args...) where {Func} u_left = get_node_vars(u, equations, dg, 1, element) u_right = get_node_vars(u, equations, dg, nnodes(dg), element) - surface_integral = (func(u_right, 1, equations) - func(u_left, 1, equations)) + + surface_integral = func(u_right, 1, equations) - func(u_left, 1, equations) return surface_integral end diff --git a/src/callbacks_step/analysis_dg2d.jl b/src/callbacks_step/analysis_dg2d.jl index db231e0c415..d5dcce8cfe8 100644 --- a/src/callbacks_step/analysis_dg2d.jl +++ b/src/callbacks_step/analysis_dg2d.jl @@ -221,23 +221,24 @@ function entropy_change_reference_element(du_local, u, element, end # calculate surface integral of func(u, equations) * normal on the reference element. -function surface_integral(func::Func, u, element, mesh::TreeMesh{2}, equations, - dg::DGSEM, cache) where {Func} +function surface_integral(func::Func, u, element, + mesh::TreeMesh{2}, equations, dg::DGSEM, cache, + args...) where {Func} surface_integral = zero(real(dg)) for ii in eachnode(dg) - # x direction + # integrate along x direction + u_bottom = get_node_vars(u, equations, dg, ii, 1, element) + u_top = get_node_vars(u, equations, dg, ii, nnodes(dg), element) + + surface_integral += dg.basis.weights[ii] * + (func(u_top, 2, equations) - func(u_bottom, 2, equations)) + + # integrate along y direction u_left = get_node_vars(u, equations, dg, 1, ii, element) u_right = get_node_vars(u, equations, dg, nnodes(dg), ii, element) - surface_integral = surface_integral + - dg.basis.weights[ii] * - (func(u_right, 1, equations) - func(u_left, 1, equations)) - - # y direction - u_left = get_node_vars(u, equations, dg, ii, 1, element) - u_right = get_node_vars(u, equations, dg, ii, nnodes(dg), element) - surface_integral = surface_integral + - dg.basis.weights[ii] * - (func(u_right, 2, equations) - func(u_left, 2, equations)) + + surface_integral += dg.basis.weights[ii] * + (func(u_right, 1, equations) - func(u_left, 1, equations)) end return surface_integral diff --git a/src/equations/compressible_euler_1d.jl b/src/equations/compressible_euler_1d.jl index 98c3448ac44..ceb321bca9c 100644 --- a/src/equations/compressible_euler_1d.jl +++ b/src/equations/compressible_euler_1d.jl @@ -1153,10 +1153,13 @@ end Calculate the entropy potential, which for the compressible Euler equations is simply momentum for the choice of entropy ``S(u) = \frac{\rho s}{\gamma - 1}``. - -"Entropy stable high order discontinuous Galerkin methods with suitable quadrature rules -for hyperbolic conservation laws" by Chen and Shu (2017). - + +## References + +- Chen, Shu (2017) + Entropy stable high order discontinuous Galerkin methods with suitable quadrature rules + for hyperbolic conservation laws + [DOI: 10.1016/j.jcp.2017.05.025](https://doi.org/10.1016/j.jcp.2017.05.025) """ @inline function entropy_potential(u, orientation::Int, equations::CompressibleEulerEquations1D) diff --git a/src/equations/compressible_euler_2d.jl b/src/equations/compressible_euler_2d.jl index 55642aff4dd..ed0ff8aa8f5 100644 --- a/src/equations/compressible_euler_2d.jl +++ b/src/equations/compressible_euler_2d.jl @@ -2236,9 +2236,12 @@ end Calculate the entropy potential, which for the compressible Euler equations is simply the momentum for the choice of entropy ``S(u) = \frac{\rho s}{\gamma - 1}``. -"Entropy stable high order discontinuous Galerkin methods with suitable quadrature rules -for hyperbolic conservation laws" by Chen and Shu (2017). - +## References + +- Chen, Shu (2017) + Entropy stable high order discontinuous Galerkin methods with suitable quadrature rules + for hyperbolic conservation laws + [DOI: 10.1016/j.jcp.2017.05.025](https://doi.org/10.1016/j.jcp.2017.05.025) """ @inline function entropy_potential(u, orientation::Int, equations::CompressibleEulerEquations2D) diff --git a/src/solvers/dgsem/calc_volume_integral.jl b/src/solvers/dgsem/calc_volume_integral.jl index 5a5c7ef5340..d0845b173c7 100644 --- a/src/solvers/dgsem/calc_volume_integral.jl +++ b/src/solvers/dgsem/calc_volume_integral.jl @@ -161,9 +161,10 @@ function calc_volume_integral!(du, u, mesh, volume_integral::VolumeIntegralEntropyCorrection, dg::DGSEM, cache) (; volume_flux_dg, volume_flux_fv, indicator) = volume_integral - du_element_threaded = indicator.cache.volume_integral_values_threaded (; scaling) = indicator (; alpha) = indicator.cache + du_element_threaded = indicator.cache.volume_integral_values_threaded + resize!(alpha, nelements(dg, cache)) @threaded for element in eachelement(dg, cache) @@ -177,12 +178,12 @@ function calc_volume_integral!(du, u, mesh, # on the reference element. For other mesh types, because the volume integral # incorporates the scaled contravariant vectors, the surface integral should # be calculated on the physical element instead. - volume_integral_entropy_vars = integrate_against_entropy_variables(view(du, .., - element), - u, element, - mesh, - equations, - dg, cache) + volume_integral_entropy_vars = entropy_change_reference_element(view(du, .., + element), + u, element, + mesh, + equations, + dg, cache) surface_integral_entropy_potential = surface_integral(entropy_potential, u, element, mesh, equations, dg, cache) @@ -209,10 +210,10 @@ function calc_volume_integral!(du, u, mesh, # this should be entropy dissipative if entropy_residual > 0. @views du_element .= (du_element .- du[.., element]) - entropy_dissipation = integrate_against_entropy_variables(du_element, u, - element, - mesh, equations, - dg, cache) + entropy_dissipation = entropy_change_reference_element(du_element, u, + element, + mesh, equations, + dg, cache) # calculate blending factor ratio = regularized_ratio(-entropy_residual, entropy_dissipation) From 65b6f2e0923735b68abbf337a2e571f9b8b938b9 Mon Sep 17 00:00:00 2001 From: Daniel_Doehring Date: Mon, 2 Feb 2026 09:20:46 +0100 Subject: [PATCH 186/360] docstring --- src/solvers/dg.jl | 8 +++++--- src/solvers/dgsem/calc_volume_integral.jl | 17 +++++++++-------- src/solvers/dgsem_tree/indicators.jl | 9 +++++---- 3 files changed, 19 insertions(+), 15 deletions(-) diff --git a/src/solvers/dg.jl b/src/solvers/dg.jl index 1bdca60d8a1..cbf9871e029 100644 --- a/src/solvers/dg.jl +++ b/src/solvers/dg.jl @@ -292,9 +292,11 @@ the **first-order** finite volume method with numerical flux `volume_flux_fv` an The amount of blending is determined by the violation of a cell entropy equality by the volume integral. -`scaling ≥ 1` arbitrarily scales the blending parameter by a constant, increasing -the amount of the subcell FV added in. This can be used to add shock capturing-like -behavior. +`scaling ≥ 1` scales the DG-FV blending parameter ``\\alpha``(see the +[tutorial on shock-capturing](https://trixi-framework.github.io/TrixiDocumentation/stable/tutorials/shock_capturing/#Shock-capturing-with-flux-differencing)) +by a constant, increasing the amount of the subcell FV added in (up to 1, i.e., pure subcell FV). +This can be used to add shock capturing-like behavior. +Note though that ``\\alpha`` is computed here from the entropy defect, **not** using [`IndicatorHennemannGassner`](@ref). The use of `VolumeIntegralEntropyCorrection` requires either `entropy_potential(u, orientation, equations)` for TreeMesh, or diff --git a/src/solvers/dgsem/calc_volume_integral.jl b/src/solvers/dgsem/calc_volume_integral.jl index d0845b173c7..feb17f9ab9b 100644 --- a/src/solvers/dgsem/calc_volume_integral.jl +++ b/src/solvers/dgsem/calc_volume_integral.jl @@ -206,8 +206,8 @@ function calc_volume_integral!(du, u, mesh, have_nonconservative_terms, equations, volume_flux_fv, dg, cache, element) - # calculate difference between high and low order FV integral; - # this should be entropy dissipative if entropy_residual > 0. + # Calculate difference between high and low order FV integral; + # this should be made entropy dissipative if entropy_residual > 0. @views du_element .= (du_element .- du[.., element]) entropy_dissipation = entropy_change_reference_element(du_element, u, @@ -215,15 +215,16 @@ function calc_volume_integral!(du, u, mesh, mesh, equations, dg, cache) - # calculate blending factor + # Calculate DG-FV blending factor ratio = regularized_ratio(-entropy_residual, entropy_dissipation) - theta = min(1, scaling * ratio) # TODO: replacing this with a differentiable version of `min` + alpha_element = min(1, scaling * ratio) # TODO: replacing this with a differentiable version of `min` - # save blending coefficient for visualization - alpha[element] = theta + # Save blending coefficient for visualization + alpha[element] = alpha_element - # blend the high order method back in - @views du[.., element] .= du[.., element] .+ (1 - theta) .* du_element + # Blend the high order method back in + @views du[.., element] .= du[.., element] .+ + (1 - alpha_element) .* du_element end end diff --git a/src/solvers/dgsem_tree/indicators.jl b/src/solvers/dgsem_tree/indicators.jl index c8f0ef97216..92c727beaaa 100644 --- a/src/solvers/dgsem_tree/indicators.jl +++ b/src/solvers/dgsem_tree/indicators.jl @@ -281,15 +281,16 @@ Indicator used for entropy correction using subcell FV schemes, where the blending is determined so that the volume integral entropy production is the same or more than that of an EC scheme. -`scaling ≥ 1` arbitrarily scales the blending parameter by a constant, increasing -the amount of the subcell FV added in. This can be used to add shock capturing-like -behavior. +`scaling ≥ 1` scales the DG-FV blending parameter ``\\alpha``(see the +[tutorial on shock-capturing](https://trixi-framework.github.io/TrixiDocumentation/stable/tutorials/shock_capturing/#Shock-capturing-with-flux-differencing)) +by a constant, increasing the amount of the subcell FV added in (up to 1, i.e., pure subcell FV). +This can be used to add shock capturing-like behavior. See also [`VolumeIntegralEntropyCorrection`](@ref). """ struct IndicatorEntropyCorrection{Cache, ScalingT} <: AbstractIndicator cache::Cache - scaling::ScalingT + scaling::ScalingT # either Bool or Real end # this method is used when the indicator is constructed as for shock-capturing volume integrals From e7d05caab3ebe6ea32be2b87ec4aad0638d61c1b Mon Sep 17 00:00:00 2001 From: Daniel_Doehring Date: Mon, 2 Feb 2026 09:40:33 +0100 Subject: [PATCH 187/360] bf --- src/callbacks_step/analysis_dg1d.jl | 22 ++++++++++++++++++---- src/callbacks_step/analysis_dg2d.jl | 22 ++++++++++++++++++---- src/solvers/dgsem/calc_volume_integral.jl | 14 ++++++-------- 3 files changed, 42 insertions(+), 16 deletions(-) diff --git a/src/callbacks_step/analysis_dg1d.jl b/src/callbacks_step/analysis_dg1d.jl index dba6a6b3b4a..c5a325bf153 100644 --- a/src/callbacks_step/analysis_dg1d.jl +++ b/src/callbacks_step/analysis_dg1d.jl @@ -141,14 +141,28 @@ end # Calculate ∫_e (∂S/∂u ⋅ ∂u/∂t) dΩ_e where the result on element 'e' is kept in reference space # Note that ∂S/∂u = w(u) with entropy variables w -function entropy_change_reference_element(du_local, u, element, +function entropy_change_reference_element(du::AbstractArray{<:Any, 3}, + u, element, mesh::AbstractMesh{1}, equations, dg::DGSEM, cache, args...) return integrate_reference_element(u, element, mesh, equations, dg, cache, - du_local) do u, i, element, equations, dg, - du_local + du) do u, i, element, equations, dg, du u_node = get_node_vars(u, equations, dg, i, element) - du_node = get_node_vars(du_local, equations, dg, i) + du_node = get_node_vars(du, equations, dg, i, element) + + dot(cons2entropy(u_node, equations), du_node) + end +end + +function entropy_change_reference_element(du_element::AbstractArray{<:Any, 2}, + u, element, + mesh::AbstractMesh{1}, + equations, dg::DGSEM, cache, args...) + return integrate_reference_element(u, element, mesh, equations, dg, cache, + du_element) do u, i, element, equations, dg, + du_element + u_node = get_node_vars(u, equations, dg, i, element) + du_node = get_node_vars(du_element, equations, dg, i, element) dot(cons2entropy(u_node, equations), du_node) end diff --git a/src/callbacks_step/analysis_dg2d.jl b/src/callbacks_step/analysis_dg2d.jl index d5dcce8cfe8..68ae5c7467d 100644 --- a/src/callbacks_step/analysis_dg2d.jl +++ b/src/callbacks_step/analysis_dg2d.jl @@ -207,14 +207,28 @@ end # Calculate ∫_e (∂S/∂u ⋅ ∂u/∂t) dΩ_e where the result on element 'e' is kept in reference space # Note that ∂S/∂u = w(u) with entropy variables w -function entropy_change_reference_element(du_local, u, element, +function entropy_change_reference_element(du::AbstractArray{<:Any, 4}, u, + element, mesh::AbstractMesh{2}, equations, dg::DGSEM, cache, args...) return integrate_reference_element(u, element, mesh, equations, dg, cache, - du_local) do u, i, j, element, equations, dg, - du_local + du) do u, i, j, element, equations, dg, du u_node = get_node_vars(u, equations, dg, i, j, element) - du_node = get_node_vars(du_local, equations, dg, i, j) + du_node = get_node_vars(du, equations, dg, i, j, element) + + dot(cons2entropy(u_node, equations), du_node) + end +end + +function entropy_change_reference_element(du_element::AbstractArray{<:Any, 3}, u, + element, + mesh::AbstractMesh{2}, + equations, dg::DGSEM, cache, args...) + return integrate_reference_element(u, element, mesh, equations, dg, cache, + du_element) do u, i, j, element, equations, dg, + du_element + u_node = get_node_vars(u, equations, dg, i, j, element) + du_node = get_node_vars(du_element, equations, dg, i, j, element) dot(cons2entropy(u_node, equations), du_node) end diff --git a/src/solvers/dgsem/calc_volume_integral.jl b/src/solvers/dgsem/calc_volume_integral.jl index feb17f9ab9b..ba5fe1fa93c 100644 --- a/src/solvers/dgsem/calc_volume_integral.jl +++ b/src/solvers/dgsem/calc_volume_integral.jl @@ -178,9 +178,7 @@ function calc_volume_integral!(du, u, mesh, # on the reference element. For other mesh types, because the volume integral # incorporates the scaled contravariant vectors, the surface integral should # be calculated on the physical element instead. - volume_integral_entropy_vars = entropy_change_reference_element(view(du, .., - element), - u, element, + volume_integral_entropy_vars = entropy_change_reference_element(du, u, element, mesh, equations, dg, cache) @@ -195,8 +193,8 @@ function calc_volume_integral!(du, u, mesh, if entropy_residual > 0 # Store "high order" result - du_element = du_element_threaded[Threads.threadid()] - @views du_element .= du[.., element] + du_FD_element = du_element_threaded[Threads.threadid()] + @views du_FD_element .= du[.., element] # Reset pure flux-differencing volume integral du[.., element] .= zero(eltype(du)) @@ -208,9 +206,9 @@ function calc_volume_integral!(du, u, mesh, # Calculate difference between high and low order FV integral; # this should be made entropy dissipative if entropy_residual > 0. - @views du_element .= (du_element .- du[.., element]) + @views du_FD_element .= (du_FD_element .- du[.., element]) - entropy_dissipation = entropy_change_reference_element(du_element, u, + entropy_dissipation = entropy_change_reference_element(du_FD_element, u, element, mesh, equations, dg, cache) @@ -224,7 +222,7 @@ function calc_volume_integral!(du, u, mesh, # Blend the high order method back in @views du[.., element] .= du[.., element] .+ - (1 - alpha_element) .* du_element + (1 - alpha_element) .* du_FD_element end end From 912ee8985bc5b8d50015e1c74c01d119d90bc646 Mon Sep 17 00:00:00 2001 From: Daniel_Doehring Date: Mon, 2 Feb 2026 09:41:23 +0100 Subject: [PATCH 188/360] rev --- src/callbacks_step/analysis_dg1d.jl | 2 +- src/callbacks_step/analysis_dg2d.jl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/callbacks_step/analysis_dg1d.jl b/src/callbacks_step/analysis_dg1d.jl index c5a325bf153..b40203419fb 100644 --- a/src/callbacks_step/analysis_dg1d.jl +++ b/src/callbacks_step/analysis_dg1d.jl @@ -162,7 +162,7 @@ function entropy_change_reference_element(du_element::AbstractArray{<:Any, 2}, du_element) do u, i, element, equations, dg, du_element u_node = get_node_vars(u, equations, dg, i, element) - du_node = get_node_vars(du_element, equations, dg, i, element) + du_node = get_node_vars(du_element, equations, dg, i) dot(cons2entropy(u_node, equations), du_node) end diff --git a/src/callbacks_step/analysis_dg2d.jl b/src/callbacks_step/analysis_dg2d.jl index 68ae5c7467d..8258ed32ecc 100644 --- a/src/callbacks_step/analysis_dg2d.jl +++ b/src/callbacks_step/analysis_dg2d.jl @@ -228,7 +228,7 @@ function entropy_change_reference_element(du_element::AbstractArray{<:Any, 3}, u du_element) do u, i, j, element, equations, dg, du_element u_node = get_node_vars(u, equations, dg, i, j, element) - du_node = get_node_vars(du_element, equations, dg, i, j, element) + du_node = get_node_vars(du_element, equations, dg, i, j) dot(cons2entropy(u_node, equations), du_node) end From 7b076d7672bf0e519551ed8f97498f48a0bf393a Mon Sep 17 00:00:00 2001 From: Daniel Doehring Date: Tue, 3 Feb 2026 14:34:23 +0100 Subject: [PATCH 189/360] Update src/equations/compressible_euler_2d.jl --- src/equations/compressible_euler_2d.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/equations/compressible_euler_2d.jl b/src/equations/compressible_euler_2d.jl index ed0ff8aa8f5..29924936a56 100644 --- a/src/equations/compressible_euler_2d.jl +++ b/src/equations/compressible_euler_2d.jl @@ -2231,7 +2231,7 @@ end end @doc raw""" - entropy_potential(u, orientation::Int, equations::CompressibleEulerEquations1D) + entropy_potential(u, orientation::Int, equations::CompressibleEulerEquations2D) Calculate the entropy potential, which for the compressible Euler equations is simply the momentum for the choice of entropy ``S(u) = \frac{\rho s}{\gamma - 1}``. From 178fde8037f783777c1c4c2a2d53e8826e89cb3d Mon Sep 17 00:00:00 2001 From: Daniel_Doehring Date: Tue, 3 Feb 2026 15:06:04 +0100 Subject: [PATCH 190/360] exports --- src/Trixi.jl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Trixi.jl b/src/Trixi.jl index fde5113cb23..3feecc3ed25 100644 --- a/src/Trixi.jl +++ b/src/Trixi.jl @@ -252,7 +252,8 @@ export density, pressure, density_pressure, velocity, temperature, equilibrium_distribution, waterheight, waterheight_pressure export entropy, entropy_thermodynamic, entropy_math, entropy_guermond_etal, - energy_total, energy_kinetic, energy_internal, entropy_potential, + entropy_potential, + energy_total, energy_kinetic, energy_internal, energy_magnetic, cross_helicity, magnetic_field, divergence_cleaning_field, enstrophy, vorticity export lake_at_rest_error @@ -271,7 +272,7 @@ export DG, VolumeIntegralShockCapturingHG, VolumeIntegralShockCapturingRRG, IndicatorHennemannGassner, VolumeIntegralUpwind, - VolumeIntegralEntropyCorrection, + VolumeIntegralEntropyCorrection, IndicatorEntropyCorrection, SurfaceIntegralWeakForm, SurfaceIntegralStrongForm, SurfaceIntegralUpwind, MortarL2 @@ -318,8 +319,7 @@ export load_mesh, load_time, load_timestep, load_timestep!, load_dt, load_adaptive_time_integrator! export ControllerThreeLevel, ControllerThreeLevelCombined, - IndicatorLöhner, IndicatorLoehner, IndicatorMax, - IndicatorEntropyCorrection + IndicatorLöhner, IndicatorLoehner, IndicatorMax export PositivityPreservingLimiterZhangShu, EntropyBoundedLimiter From f3a3ce3243d19699e9b60afd2b651f2423253772 Mon Sep 17 00:00:00 2001 From: Daniel_Doehring Date: Tue, 3 Feb 2026 15:06:45 +0100 Subject: [PATCH 191/360] dos --- src/equations/compressible_euler_1d.jl | 13 ++++++------- src/equations/compressible_euler_2d.jl | 13 ++++++------- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/src/equations/compressible_euler_1d.jl b/src/equations/compressible_euler_1d.jl index ceb321bca9c..eae327d0920 100644 --- a/src/equations/compressible_euler_1d.jl +++ b/src/equations/compressible_euler_1d.jl @@ -1152,14 +1152,13 @@ end entropy_potential(u, orientation::Int, equations::CompressibleEulerEquations1D) Calculate the entropy potential, which for the compressible Euler equations is simply -momentum for the choice of entropy ``S(u) = \frac{\rho s}{\gamma - 1}``. - +the momentum for the choice of mathematical entropy ``S(u) = \frac{\rho s}{\gamma - 1}`` +with thermodynamic entropy ``s = \ln(p) - \gamma \ln(\rho)``. + ## References - -- Chen, Shu (2017) - Entropy stable high order discontinuous Galerkin methods with suitable quadrature rules - for hyperbolic conservation laws - [DOI: 10.1016/j.jcp.2017.05.025](https://doi.org/10.1016/j.jcp.2017.05.025) +- Eitan Tadmor (2003) + Entropy stability theory for difference approximations of nonlinear conservation laws and related time-dependent problems + [DOI: 10.1017/S0962492902000156](https://doi.org/10.1017/S0962492902000156) """ @inline function entropy_potential(u, orientation::Int, equations::CompressibleEulerEquations1D) diff --git a/src/equations/compressible_euler_2d.jl b/src/equations/compressible_euler_2d.jl index ed0ff8aa8f5..3407e4a39a5 100644 --- a/src/equations/compressible_euler_2d.jl +++ b/src/equations/compressible_euler_2d.jl @@ -2231,17 +2231,16 @@ end end @doc raw""" - entropy_potential(u, orientation::Int, equations::CompressibleEulerEquations1D) + entropy_potential(u, orientation::Int, equations::CompressibleEulerEquations2D) Calculate the entropy potential, which for the compressible Euler equations is simply -the momentum for the choice of entropy ``S(u) = \frac{\rho s}{\gamma - 1}``. +the momentum for the choice of mathematical entropy ``S(u) = \frac{\rho s}{\gamma - 1}`` +with thermodynamic entropy ``s = \ln(p) - \gamma \ln(\rho)``. ## References - -- Chen, Shu (2017) - Entropy stable high order discontinuous Galerkin methods with suitable quadrature rules - for hyperbolic conservation laws - [DOI: 10.1016/j.jcp.2017.05.025](https://doi.org/10.1016/j.jcp.2017.05.025) +- Eitan Tadmor (2003) + Entropy stability theory for difference approximations of nonlinear conservation laws and related time-dependent problems + [DOI: 10.1017/S0962492902000156](https://doi.org/10.1017/S0962492902000156) """ @inline function entropy_potential(u, orientation::Int, equations::CompressibleEulerEquations2D) From 62e95427a943f026b9f1451cd464cfe887f6891b Mon Sep 17 00:00:00 2001 From: Daniel_Doehring Date: Tue, 3 Feb 2026 15:07:17 +0100 Subject: [PATCH 192/360] ds --- src/equations/nonideal_compressible_euler_1d.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/equations/nonideal_compressible_euler_1d.jl b/src/equations/nonideal_compressible_euler_1d.jl index d7e9d14c4a3..c28fc70a763 100644 --- a/src/equations/nonideal_compressible_euler_1d.jl +++ b/src/equations/nonideal_compressible_euler_1d.jl @@ -353,7 +353,7 @@ end end """ - entropy_potential(u, orientation::Int, equations::CompressibleEulerEquations1D) + entropy_potential(u, orientation::Int, equations::NonIdealCompressibleEulerEquations1D) Calculate the entropy potential, which for the compressible Euler equations with general EOS is ``p v_i / T`` for the choice of entropy ``S(u) = \rho s``. From bb4656a35aad74e460589af384a84bb743c4ddce Mon Sep 17 00:00:00 2001 From: Daniel_Doehring Date: Tue, 3 Feb 2026 15:09:14 +0100 Subject: [PATCH 193/360] comment --- src/solvers/dgsem/calc_volume_integral.jl | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/solvers/dgsem/calc_volume_integral.jl b/src/solvers/dgsem/calc_volume_integral.jl index ba5fe1fa93c..b98edc9ed76 100644 --- a/src/solvers/dgsem/calc_volume_integral.jl +++ b/src/solvers/dgsem/calc_volume_integral.jl @@ -177,8 +177,12 @@ function calc_volume_integral!(du, u, mesh, # Note that, for `TreeMesh`, both volume and surface integrals are calculated # on the reference element. For other mesh types, because the volume integral # incorporates the scaled contravariant vectors, the surface integral should - # be calculated on the physical element instead. - volume_integral_entropy_vars = entropy_change_reference_element(du, u, element, + # be calculated on the physical element instead. + # + # Minus sign because of the flipped sign of the volume term in the DG RHS. + # No scaling by inverse Jacobian here, as there is no Jacobian multiplication + # in `integrate_reference_element`. + volume_integral_entropy_vars = -entropy_change_reference_element(du, u, element, mesh, equations, dg, cache) From 120924f24a19d8c261ffa5309e21068ffcf65599 Mon Sep 17 00:00:00 2001 From: Daniel_Doehring Date: Tue, 3 Feb 2026 15:10:24 +0100 Subject: [PATCH 194/360] comment --- src/solvers/dgsem/calc_volume_integral.jl | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/solvers/dgsem/calc_volume_integral.jl b/src/solvers/dgsem/calc_volume_integral.jl index b98edc9ed76..ecfa2de5e46 100644 --- a/src/solvers/dgsem/calc_volume_integral.jl +++ b/src/solvers/dgsem/calc_volume_integral.jl @@ -186,14 +186,15 @@ function calc_volume_integral!(du, u, mesh, mesh, equations, dg, cache) - surface_integral_entropy_potential = surface_integral(entropy_potential, u, - element, mesh, equations, - dg, cache) + + # Compute true entropy change given by surface integral of the entropy potential + entropy_change_true = surface_integral(entropy_potential, u, + element, mesh, equations, + dg, cache) # This quantity should be ≤ 0 for an entropy stable volume integral, and # exactly zero for an entropy conservative volume integral. - entropy_residual = -(volume_integral_entropy_vars + - surface_integral_entropy_potential) + entropy_residual = volume_integral_entropy_vars - entropy_change_true if entropy_residual > 0 # Store "high order" result From 8133c1038f7bbf3b1c6114691c38a771e17fa041 Mon Sep 17 00:00:00 2001 From: Daniel_Doehring Date: Tue, 3 Feb 2026 15:14:40 +0100 Subject: [PATCH 195/360] change --- src/solvers/dgsem/calc_volume_integral.jl | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/solvers/dgsem/calc_volume_integral.jl b/src/solvers/dgsem/calc_volume_integral.jl index ecfa2de5e46..d55b2a27c49 100644 --- a/src/solvers/dgsem/calc_volume_integral.jl +++ b/src/solvers/dgsem/calc_volume_integral.jl @@ -182,19 +182,17 @@ function calc_volume_integral!(du, u, mesh, # Minus sign because of the flipped sign of the volume term in the DG RHS. # No scaling by inverse Jacobian here, as there is no Jacobian multiplication # in `integrate_reference_element`. - volume_integral_entropy_vars = -entropy_change_reference_element(du, u, element, - mesh, - equations, - dg, cache) + dS_volume_integral = -entropy_change_reference_element(du, u, element, + mesh, equations, + dg, cache) # Compute true entropy change given by surface integral of the entropy potential - entropy_change_true = surface_integral(entropy_potential, u, - element, mesh, equations, - dg, cache) + dS_true = surface_integral(entropy_potential, u, element, + mesh, equations, dg, cache) # This quantity should be ≤ 0 for an entropy stable volume integral, and # exactly zero for an entropy conservative volume integral. - entropy_residual = volume_integral_entropy_vars - entropy_change_true + entropy_residual = dS_volume_integral - dS_true if entropy_residual > 0 # Store "high order" result From 2955ddbc5ee34b975a5f52a6272133ef7daae4ca Mon Sep 17 00:00:00 2001 From: Daniel_Doehring Date: Tue, 3 Feb 2026 17:41:59 +0100 Subject: [PATCH 196/360] comment --- src/callbacks_step/analysis_dg2d.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/callbacks_step/analysis_dg2d.jl b/src/callbacks_step/analysis_dg2d.jl index 8258ed32ecc..6fa21d9fa43 100644 --- a/src/callbacks_step/analysis_dg2d.jl +++ b/src/callbacks_step/analysis_dg2d.jl @@ -240,14 +240,14 @@ function surface_integral(func::Func, u, element, args...) where {Func} surface_integral = zero(real(dg)) for ii in eachnode(dg) - # integrate along x direction + # integrate along x direction, normal in y (2) direction u_bottom = get_node_vars(u, equations, dg, ii, 1, element) u_top = get_node_vars(u, equations, dg, ii, nnodes(dg), element) surface_integral += dg.basis.weights[ii] * (func(u_top, 2, equations) - func(u_bottom, 2, equations)) - # integrate along y direction + # integrate along y direction, normal in x (1) direction u_left = get_node_vars(u, equations, dg, 1, ii, element) u_right = get_node_vars(u, equations, dg, nnodes(dg), ii, element) From 98319023727789db5622636cea7e826fe6a4eb56 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Wed, 4 Feb 2026 14:46:16 -0600 Subject: [PATCH 197/360] rename e -> e_internal --- src/equations/nonideal_compressible_euler_1d.jl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/equations/nonideal_compressible_euler_1d.jl b/src/equations/nonideal_compressible_euler_1d.jl index c6f5a36c64b..d885f9cc107 100644 --- a/src/equations/nonideal_compressible_euler_1d.jl +++ b/src/equations/nonideal_compressible_euler_1d.jl @@ -34,7 +34,7 @@ by some user-specified equation of state (EOS) p = p(V, T) ``` -Similarly, the internal energy is specified by `e = energy_internal(V, T, eos)`, see +Similarly, the internal energy is specified by `e_internal = energy_internal(V, T, eos)`, see [`energy_internal(V, T, eos::IdealGas)`](@ref), [`energy_internal(V, T, eos::VanDerWaals)`](@ref). Because of this, the primitive variables are also defined to be `V, v1, T` (instead of @@ -298,8 +298,8 @@ end V, v1, T = prim rho = inv(V) rho_v1 = rho * v1 - e = energy_internal(V, T, eos) - rho_e_total = rho * e + 0.5f0 * rho_v1 * v1 + e_internal = energy_internal(V, T, eos) + rho_e_total = rho * e_internal + 0.5f0 * rho_v1 * v1 return SVector(rho, rho_v1, rho_e_total) end @@ -374,7 +374,7 @@ end u_thermo = cons2thermo(u, equations) V = first(u_thermo) T = last(u_thermo) - e = energy_internal(V, T, eos) + e_internal = energy_internal(V, T, eos) return e end From a7d6d8c3e7de7b534ee2bb1ac9b9083c23a9a64f Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Wed, 4 Feb 2026 14:46:28 -0600 Subject: [PATCH 198/360] fix e_total -> e_{\text{total}} formatting --- src/equations/nonideal_compressible_euler_1d.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/equations/nonideal_compressible_euler_1d.jl b/src/equations/nonideal_compressible_euler_1d.jl index d885f9cc107..618f2525bbe 100644 --- a/src/equations/nonideal_compressible_euler_1d.jl +++ b/src/equations/nonideal_compressible_euler_1d.jl @@ -12,12 +12,12 @@ The compressible Euler equations ```math \frac{\partial}{\partial t} \begin{pmatrix} - \rho \\ \rho v_1 \\ \rho e_{total} + \rho \\ \rho v_1 \\ \rho e_{\text{total}} \end{pmatrix} + \frac{\partial}{\partial x} \begin{pmatrix} - \rho v_1 \\ \rho v_1^2 + p \\ (\rho e_{total} + p) v_1 + \rho v_1 \\ \rho v_1^2 + p \\ (\rho e_{\text{total}} + p) v_1 \end{pmatrix} = \begin{pmatrix} @@ -26,7 +26,7 @@ The compressible Euler equations ``` for a gas with pressure ``p`` specified by some equation of state in one space dimension. -Here, ``\rho`` is the density, ``v_1`` the velocity, ``e_{total}`` the specific total energy, +Here, ``\rho`` is the density, ``v_1`` the velocity, ``e_{\text{total}}`` the specific total energy, and the pressure ``p`` is given in terms of specific volume ``V = 1/\rho`` and temperature ``T`` by some user-specified equation of state (EOS) (see [`pressure(V, T, eos::IdealGas)`](@ref), [`pressure(V, T, eos::VanDerWaals)`](@ref)) as From bbd81ee49c4e5c26ea828e8a6012c8524a1d4bce Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Wed, 4 Feb 2026 14:49:48 -0600 Subject: [PATCH 199/360] drop max EOS Newton iterations to 20 --- src/equations/equations_of_state.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/equations/equations_of_state.jl b/src/equations/equations_of_state.jl index c653c8dfc4c..add57c9ae3b 100644 --- a/src/equations/equations_of_state.jl +++ b/src/equations/equations_of_state.jl @@ -58,7 +58,7 @@ end # for the Newton solver for temperature. eos_newton_tol(eos::AbstractEquationOfState) = 10 * eps() eos_initial_temperature(V, e, eos::AbstractEquationOfState) = 1 -eos_newton_maxiter(eos) = 100 +eos_newton_maxiter(eos) = 20 """ temperature(V, e, eos::AbstractEquationOfState; initial_T = 1.0, tol = eos_newton_tol(eos), From 2a6221c95d57ea6fe87eac3b96153bd87ab3a1ff Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Wed, 4 Feb 2026 16:42:46 -0600 Subject: [PATCH 200/360] fix dropped "_internal" --- src/equations/nonideal_compressible_euler_1d.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/equations/nonideal_compressible_euler_1d.jl b/src/equations/nonideal_compressible_euler_1d.jl index 618f2525bbe..32e90873571 100644 --- a/src/equations/nonideal_compressible_euler_1d.jl +++ b/src/equations/nonideal_compressible_euler_1d.jl @@ -375,7 +375,7 @@ end V = first(u_thermo) T = last(u_thermo) e_internal = energy_internal(V, T, eos) - return e + return e_internal end @inline function internal_energy_density(u, From 9654a01e9ae333c56092340eb2bf0ef519b572cd Mon Sep 17 00:00:00 2001 From: Jesse Chan <1156048+jlchan@users.noreply.github.com> Date: Thu, 5 Feb 2026 11:59:27 -0600 Subject: [PATCH 201/360] Apply suggestion from @ranocha Co-authored-by: Hendrik Ranocha --- .../tree_1d_dgsem/elixir_euler_nonideal_transcritical_shock.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/tree_1d_dgsem/elixir_euler_nonideal_transcritical_shock.jl b/examples/tree_1d_dgsem/elixir_euler_nonideal_transcritical_shock.jl index d5e44bff373..4e53f2d00a3 100644 --- a/examples/tree_1d_dgsem/elixir_euler_nonideal_transcritical_shock.jl +++ b/examples/tree_1d_dgsem/elixir_euler_nonideal_transcritical_shock.jl @@ -36,7 +36,7 @@ function initial_condition_transcritical_shock(x, t, iter += 1 end if iter == 100 - println("Warning: solver for temperature(V, p) did not converge") + @warn "Solver for temperature(V, p) did not converge" end return prim2cons(SVector(V, v1, T), equations) From d271638a33ae22cfd906d02ca46ea4df1cd6e3cf Mon Sep 17 00:00:00 2001 From: Jesse Chan <1156048+jlchan@users.noreply.github.com> Date: Thu, 5 Feb 2026 11:59:49 -0600 Subject: [PATCH 202/360] Apply suggestion from @ranocha Co-authored-by: Hendrik Ranocha --- .../tree_1d_dgsem/elixir_euler_nonideal_transcritical_wave.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/tree_1d_dgsem/elixir_euler_nonideal_transcritical_wave.jl b/examples/tree_1d_dgsem/elixir_euler_nonideal_transcritical_wave.jl index d419cc7f9a8..3973479a017 100644 --- a/examples/tree_1d_dgsem/elixir_euler_nonideal_transcritical_wave.jl +++ b/examples/tree_1d_dgsem/elixir_euler_nonideal_transcritical_wave.jl @@ -39,7 +39,7 @@ function initial_condition_transcritical_wave(x, t, iter += 1 end if iter == 100 - println("Warning: solver for temperature(V, p) did not converge") + @warn "Solver for temperature(V, p) did not converge" end return prim2cons(SVector(V, v1, T), equations) From 3607a51a60d0a70e3ec0df0809e30734ee6684c3 Mon Sep 17 00:00:00 2001 From: Jesse Chan <1156048+jlchan@users.noreply.github.com> Date: Thu, 5 Feb 2026 12:01:42 -0600 Subject: [PATCH 203/360] Apply suggestion from @ranocha Co-authored-by: Hendrik Ranocha --- src/equations/equation_of_state_peng_robinson.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/equations/equation_of_state_peng_robinson.jl b/src/equations/equation_of_state_peng_robinson.jl index d2f97ae5dd2..421b466ae35 100644 --- a/src/equations/equation_of_state_peng_robinson.jl +++ b/src/equations/equation_of_state_peng_robinson.jl @@ -147,8 +147,8 @@ function speed_of_sound(V, T, eos::PengRobinson) # calculate bulk modulus, which should be positive # for admissible thermodynamic states. - kappa_T = -inv(V * dpdV_T) - c2 = gamma * V / kappa_T + inv_kappa_T = -(V * dpdV_T) + c2 = gamma * V * inv_kappa_T return sqrt(c2) end From 0a570624db32ce3a40a740771a4bfa3ff8179db2 Mon Sep 17 00:00:00 2001 From: Jesse Chan <1156048+jlchan@users.noreply.github.com> Date: Thu, 5 Feb 2026 12:02:17 -0600 Subject: [PATCH 204/360] Apply suggestion from @ranocha Co-authored-by: Hendrik Ranocha --- src/equations/nonideal_compressible_euler_1d.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/equations/nonideal_compressible_euler_1d.jl b/src/equations/nonideal_compressible_euler_1d.jl index 32e90873571..d9ebf3c8d56 100644 --- a/src/equations/nonideal_compressible_euler_1d.jl +++ b/src/equations/nonideal_compressible_euler_1d.jl @@ -69,7 +69,7 @@ varnames(::typeof(cons2prim), ::NonIdealCompressibleEulerEquations1D) = ("rho", "p") """ - function cons2thermo(u, equations::NonIdealCompressibleEulerEquations1D) + cons2thermo(u, equations::NonIdealCompressibleEulerEquations1D) Convert conservative variables to specific volume, velocity, and temperature variables `V, v1, T`. These are referred to as "thermodynamic" variables since From 32ae354dc160f5dc4f80d4972a9f24ea05e3298c Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Thu, 5 Feb 2026 12:05:02 -0600 Subject: [PATCH 205/360] test cons2thermo type stability --- test/test_type.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/test/test_type.jl b/test/test_type.jl index 6330fecd4d2..af0336bbda4 100644 --- a/test/test_type.jl +++ b/test/test_type.jl @@ -248,6 +248,7 @@ isdir(outdir) && rm(outdir, recursive = true) @test eltype(@inferred Trixi.max_abs_speeds(u, equations)) == RealT @test eltype(@inferred cons2prim(u, equations)) == RealT + @test eltype(@inferred cons2thermo(u, equations)) == RealT @test eltype(@inferred prim2cons(u, equations)) == RealT @test eltype(@inferred cons2entropy(u, equations)) == RealT # TODO: if entropy2cons is implemented, add a test From 92f57e3f30f58cdea40a75d8397f0105f6f68858 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Thu, 5 Feb 2026 12:07:01 -0600 Subject: [PATCH 206/360] format test --- test/test_type.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_type.jl b/test/test_type.jl index af0336bbda4..95fcb153b6a 100644 --- a/test/test_type.jl +++ b/test/test_type.jl @@ -248,7 +248,7 @@ isdir(outdir) && rm(outdir, recursive = true) @test eltype(@inferred Trixi.max_abs_speeds(u, equations)) == RealT @test eltype(@inferred cons2prim(u, equations)) == RealT - @test eltype(@inferred cons2thermo(u, equations)) == RealT + @test eltype(@inferred cons2thermo(u, equations)) == RealT @test eltype(@inferred prim2cons(u, equations)) == RealT @test eltype(@inferred cons2entropy(u, equations)) == RealT # TODO: if entropy2cons is implemented, add a test From cc117bfa4fbf7407a9da1f2f852389564a4fa9d4 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Thu, 5 Feb 2026 12:07:35 -0600 Subject: [PATCH 207/360] use @warn instead --- examples/tree_1d_dgsem/elixir_euler_nonideal_density_wave.jl | 2 +- .../tree_1d_dgsem/elixir_euler_nonideal_transcritical_wave.jl | 2 +- src/equations/equations_of_state.jl | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/tree_1d_dgsem/elixir_euler_nonideal_density_wave.jl b/examples/tree_1d_dgsem/elixir_euler_nonideal_density_wave.jl index 83dec9001cb..e5705232c06 100644 --- a/examples/tree_1d_dgsem/elixir_euler_nonideal_density_wave.jl +++ b/examples/tree_1d_dgsem/elixir_euler_nonideal_density_wave.jl @@ -35,7 +35,7 @@ function Trixi.initial_condition_density_wave(x, t, iter += 1 end if iter == 100 - println("Warning: solver for temperature(V, p) did not converge") + @warn "Solver for temperature(V, p) did not converge" end return prim2cons(SVector(V, v1, T), equations) diff --git a/examples/tree_1d_dgsem/elixir_euler_nonideal_transcritical_wave.jl b/examples/tree_1d_dgsem/elixir_euler_nonideal_transcritical_wave.jl index 3973479a017..2ed8b38dd88 100644 --- a/examples/tree_1d_dgsem/elixir_euler_nonideal_transcritical_wave.jl +++ b/examples/tree_1d_dgsem/elixir_euler_nonideal_transcritical_wave.jl @@ -39,7 +39,7 @@ function initial_condition_transcritical_wave(x, t, iter += 1 end if iter == 100 - @warn "Solver for temperature(V, p) did not converge" + @warn "Solver for temperature(V, p) did not converge." end return prim2cons(SVector(V, v1, T), equations) diff --git a/src/equations/equations_of_state.jl b/src/equations/equations_of_state.jl index add57c9ae3b..51bbcfcebad 100644 --- a/src/equations/equations_of_state.jl +++ b/src/equations/equations_of_state.jl @@ -86,8 +86,8 @@ function temperature(V, e, eos::AbstractEquationOfState; iter += 1 end if iter == maxiter - println("Warning: nonlinear solve in `temperature(V, T, eos)` did not converge within $maxiter iterations. " * - "Final states: iter = $iter, V, e = $V, $e with de = $de") + @warn "Solver in `temperature(V, T, eos)` did not converge within $maxiter iterations. " * + "Final states: iter = $iter, V, e = $V, $e with de = $de" end return T end From 80dd2f847d179188282a5f6ca6415baad111b64e Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Thu, 5 Feb 2026 12:51:10 -0600 Subject: [PATCH 208/360] fix test --- test/test_type.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_type.jl b/test/test_type.jl index 95fcb153b6a..695f7d4373e 100644 --- a/test/test_type.jl +++ b/test/test_type.jl @@ -248,7 +248,7 @@ isdir(outdir) && rm(outdir, recursive = true) @test eltype(@inferred Trixi.max_abs_speeds(u, equations)) == RealT @test eltype(@inferred cons2prim(u, equations)) == RealT - @test eltype(@inferred cons2thermo(u, equations)) == RealT + @test eltype(@inferred Trixi.cons2thermo(u, equations)) == RealT @test eltype(@inferred prim2cons(u, equations)) == RealT @test eltype(@inferred cons2entropy(u, equations)) == RealT # TODO: if entropy2cons is implemented, add a test From f6731cd7e6b6b559b773f2653df837259039040b Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Thu, 5 Feb 2026 12:59:13 -0600 Subject: [PATCH 209/360] remove period for consistency --- .../tree_1d_dgsem/elixir_euler_nonideal_transcritical_wave.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/tree_1d_dgsem/elixir_euler_nonideal_transcritical_wave.jl b/examples/tree_1d_dgsem/elixir_euler_nonideal_transcritical_wave.jl index 2ed8b38dd88..3973479a017 100644 --- a/examples/tree_1d_dgsem/elixir_euler_nonideal_transcritical_wave.jl +++ b/examples/tree_1d_dgsem/elixir_euler_nonideal_transcritical_wave.jl @@ -39,7 +39,7 @@ function initial_condition_transcritical_wave(x, t, iter += 1 end if iter == 100 - @warn "Solver for temperature(V, p) did not converge." + @warn "Solver for temperature(V, p) did not converge" end return prim2cons(SVector(V, v1, T), equations) From 26c2a92f70d073b280b4fd3d5bd484d5783c2882 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Thu, 5 Feb 2026 13:01:39 -0600 Subject: [PATCH 210/360] update e -> e_{\text{internal}} in docs --- src/equations/equation_of_state_ideal_gas.jl | 4 ++-- src/equations/equation_of_state_peng_robinson.jl | 4 ++-- src/equations/equation_of_state_vdw.jl | 4 ++-- src/equations/equations_of_state.jl | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/equations/equation_of_state_ideal_gas.jl b/src/equations/equation_of_state_ideal_gas.jl index bc5e74de035..4669fa3292d 100644 --- a/src/equations/equation_of_state_ideal_gas.jl +++ b/src/equations/equation_of_state_ideal_gas.jl @@ -11,7 +11,7 @@ This defines the polytropic ideal gas equation of state given by the pressure and internal energy relations ```math -p = \rho R T, \quad e = c_v T +p = \rho R T, \quad e_{\text{internal}} = c_v T ``` with ``c_v = \frac{R}{\gamma - 1}``. """ @@ -50,7 +50,7 @@ end energy_internal(V, T, eos::IdealGas) Computes internal energy for an ideal gas from specific volume `V` and temperature `T` as -``e = c_v T``. +``e_{\text{internal}} = c_v T``. """ function energy_internal(V, T, eos::IdealGas) (; cv) = eos diff --git a/src/equations/equation_of_state_peng_robinson.jl b/src/equations/equation_of_state_peng_robinson.jl index 421b466ae35..f7a87d613bd 100644 --- a/src/equations/equation_of_state_peng_robinson.jl +++ b/src/equations/equation_of_state_peng_robinson.jl @@ -11,7 +11,7 @@ This defines the Peng-Robinson equation of state given by the pressure and internal energy relations ```math -p = \frac{R T}{V - b} - \frac{a(T)}{V^2 + 2bV - b^2}, \quad e = c_{v,0} T + K(a(T) - Ta'(T)) +p = \frac{R T}{V - b} - \frac{a(T)}{V^2 + 2bV - b^2}, \quad e_{\text{internal}} = c_{v,0} T + K(a(T) - Ta'(T)) ``` where ``V = inv(rho)`` and auxiliary expressions for ``a(T)`` and ``K`` are given by ```math @@ -107,7 +107,7 @@ end energy_internal(V, T, eos::PengRobinson) Computes internal energy for a Peng-Robinson gas from specific volume `V` and temperature `T` as -``e = c_{v,0} T + K_1 (a(T) - T a'(T))``. +``e_{\text{internal}} = c_{v,0} T + K_1 (a(T) - T a'(T))``. """ function energy_internal(V, T, eos::PengRobinson) (; cv0) = eos diff --git a/src/equations/equation_of_state_vdw.jl b/src/equations/equation_of_state_vdw.jl index ba577bbaf94..75c50ddbfce 100644 --- a/src/equations/equation_of_state_vdw.jl +++ b/src/equations/equation_of_state_vdw.jl @@ -11,7 +11,7 @@ This defines the van der Waals equation of state given by the pressure and internal energy relations ```math -p = \frac{\rho R T}{1 - \rho b} - a \rho^2, \quad e = c_v T - a \rho +p = \frac{\rho R T}{1 - \rho b} - a \rho^2, \quad e_{\text{internal}} = c_v T - a \rho ``` with ``c_v = \frac{R}{\gamma - 1}``. This corresponds to the "simple van der Waals" fluid with constant `c_v`, which can be found on p28 of @@ -61,7 +61,7 @@ end energy_internal(V, T, eos::VanDerWaals) Computes internal energy for a van der Waals gas from specific volume `V` and temperature `T` as -``e = c_v T - a \rho``. +``e_{\text{internal}} = c_v T - a \rho``. """ function energy_internal(V, T, eos::VanDerWaals) (; cv, a) = eos diff --git a/src/equations/equations_of_state.jl b/src/equations/equations_of_state.jl index 51bbcfcebad..b2921c8a59d 100644 --- a/src/equations/equations_of_state.jl +++ b/src/equations/equations_of_state.jl @@ -61,8 +61,8 @@ eos_initial_temperature(V, e, eos::AbstractEquationOfState) = 1 eos_newton_maxiter(eos) = 20 """ - temperature(V, e, eos::AbstractEquationOfState; initial_T = 1.0, tol = eos_newton_tol(eos), - maxiter = 100) + temperature(V, e_internal, eos::AbstractEquationOfState; initial_T = 1.0, + tol = eos_newton_tol(eos), maxiter = 100) Calculates the temperature as a function of specific volume `V` and internal energy `e` by using Newton's method to determine `T` such that `energy_internal(V, T, eos) = e`. From 202a90fb20e223fae81502ea0131eb3e051f7141 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Thu, 5 Feb 2026 13:02:09 -0600 Subject: [PATCH 211/360] update e -> e_internal for variable names --- src/equations/equation_of_state_ideal_gas.jl | 8 ++++---- src/equations/equation_of_state_peng_robinson.jl | 4 ++-- src/equations/equation_of_state_vdw.jl | 12 ++++++------ src/equations/equations_of_state.jl | 10 +++++----- src/equations/nonideal_compressible_euler_1d.jl | 8 ++++---- 5 files changed, 21 insertions(+), 21 deletions(-) diff --git a/src/equations/equation_of_state_ideal_gas.jl b/src/equations/equation_of_state_ideal_gas.jl index 4669fa3292d..5b0eab2b6be 100644 --- a/src/equations/equation_of_state_ideal_gas.jl +++ b/src/equations/equation_of_state_ideal_gas.jl @@ -54,8 +54,8 @@ Computes internal energy for an ideal gas from specific volume `V` and temperatu """ function energy_internal(V, T, eos::IdealGas) (; cv) = eos - e = cv * T - return e + e_internal = cv * T + return e_internal end function entropy_specific(V, T, eos::IdealGas) @@ -74,9 +74,9 @@ end # This is not a required interface function, but specializing it # if an explicit function is available can improve performance. # For general EOS, this is calculated via a Newton solve. -function temperature(V, e, eos::IdealGas) +function temperature(V, e_internal, eos::IdealGas) (; cv) = eos - T = e / cv + T = e_internal / cv return T end end # @muladd diff --git a/src/equations/equation_of_state_peng_robinson.jl b/src/equations/equation_of_state_peng_robinson.jl index f7a87d613bd..c2a7b69b331 100644 --- a/src/equations/equation_of_state_peng_robinson.jl +++ b/src/equations/equation_of_state_peng_robinson.jl @@ -112,8 +112,8 @@ Computes internal energy for a Peng-Robinson gas from specific volume `V` and te function energy_internal(V, T, eos::PengRobinson) (; cv0) = eos K1 = calc_K1(V, eos) - e = cv0 * T + K1 * (peng_robinson_a(T, eos) - T * peng_robinson_da(T, eos)) - return e + e_internal = cv0 * T + K1 * (peng_robinson_a(T, eos) - T * peng_robinson_da(T, eos)) + return e_internal end @inline function heat_capacity_constant_volume(V, T, eos::PengRobinson) diff --git a/src/equations/equation_of_state_vdw.jl b/src/equations/equation_of_state_vdw.jl index 75c50ddbfce..04f93293ebe 100644 --- a/src/equations/equation_of_state_vdw.jl +++ b/src/equations/equation_of_state_vdw.jl @@ -66,8 +66,8 @@ Computes internal energy for a van der Waals gas from specific volume `V` and te function energy_internal(V, T, eos::VanDerWaals) (; cv, a) = eos rho = inv(V) - e = cv * T - a * rho - return e + e_internal = cv * T - a * rho + return e_internal end function entropy_specific(V, T, eos::VanDerWaals) @@ -86,18 +86,18 @@ end function speed_of_sound(V, T, eos::VanDerWaals) (; a, b, gamma) = eos rho = inv(V) - e = energy_internal(V, T, eos) - c2 = gamma * (gamma - 1) * (e + rho * a) / (1 - rho * b)^2 - 2 * a * rho + e_internal = energy_internal(V, T, eos) + c2 = gamma * (gamma - 1) * (e_internal + rho * a) / (1 - rho * b)^2 - 2 * a * rho return sqrt(c2) end # This is not a required interface function, but specializing it # if an explicit function is available can improve performance. # For general EOS, this is calculated via a Newton solve. -function temperature(V, e, eos::VanDerWaals) +function temperature(V, e_internal, eos::VanDerWaals) (; cv, a) = eos rho = inv(V) - T = (e + a * rho) / cv + T = (e_internal + a * rho) / cv return T end diff --git a/src/equations/equations_of_state.jl b/src/equations/equations_of_state.jl index b2921c8a59d..c890701374c 100644 --- a/src/equations/equations_of_state.jl +++ b/src/equations/equations_of_state.jl @@ -57,7 +57,7 @@ end # relative tolerance, initial guess, and maximum number of iterations # for the Newton solver for temperature. eos_newton_tol(eos::AbstractEquationOfState) = 10 * eps() -eos_initial_temperature(V, e, eos::AbstractEquationOfState) = 1 +eos_initial_temperature(V, e_internal, eos::AbstractEquationOfState) = 1 eos_newton_maxiter(eos) = 20 """ @@ -68,14 +68,14 @@ Calculates the temperature as a function of specific volume `V` and internal ene by using Newton's method to determine `T` such that `energy_internal(V, T, eos) = e`. Note that the tolerance may need to be adjusted based on the specific equation of state. """ -function temperature(V, e, eos::AbstractEquationOfState; - initial_T = eos_initial_temperature(V, e, eos), +function temperature(V, e_internal, eos::AbstractEquationOfState; + initial_T = eos_initial_temperature(V, e_internal, eos), tol = eos_newton_tol(eos), maxiter = eos_newton_maxiter(eos)) T = initial_T - de = energy_internal(V, T, eos) - e + de = energy_internal(V, T, eos) - e_internal iter = 1 while abs(de) > tol * abs(e) && iter < maxiter - de = energy_internal(V, T, eos) - e + de = energy_internal(V, T, eos) - e_internal # for thermodynamically admissible states, c_v = de_dT_V > 0, which should # guarantee convergence of this iteration. diff --git a/src/equations/nonideal_compressible_euler_1d.jl b/src/equations/nonideal_compressible_euler_1d.jl index d9ebf3c8d56..86f8fae4fd4 100644 --- a/src/equations/nonideal_compressible_euler_1d.jl +++ b/src/equations/nonideal_compressible_euler_1d.jl @@ -81,8 +81,8 @@ equation of state routines are assumed to be evaluated in terms of `V` and `T`. V = inv(rho) v1 = rho_v1 * V - e = (rho_e_total - 0.5f0 * rho_v1 * v1) * V - T = temperature(V, e, eos) + e_internal = (rho_e_total - 0.5f0 * rho_v1 * v1) * V + T = temperature(V, e_internal, eos) return SVector(V, v1, T) end @@ -381,7 +381,7 @@ end @inline function internal_energy_density(u, equations::NonIdealCompressibleEulerEquations1D) rho, rho_v1, rho_e_total = u - rho_e = rho_e_total - 0.5f0 * rho_v1^2 / rho - return rho_e + rho_e_internal = rho_e_total - 0.5f0 * rho_v1^2 / rho + return rho_e_internal end end # @muladd From 341a3c55b92388f8fa599272470ba864ed2cbd61 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Thu, 5 Feb 2026 13:02:23 -0600 Subject: [PATCH 212/360] move dimension-agnostic functions into equations.jl --- src/equations/equations.jl | 72 +++++++++++++++++++ .../nonideal_compressible_euler_1d.jl | 69 ------------------ 2 files changed, 72 insertions(+), 69 deletions(-) diff --git a/src/equations/equations.jl b/src/equations/equations.jl index e6ce9992caa..253a03c04d4 100644 --- a/src/equations/equations.jl +++ b/src/equations/equations.jl @@ -650,6 +650,78 @@ include("compressible_euler_quasi_1d.jl") # Non-ideal compressibleEulerEquations abstract type AbstractNonIdealCompressibleEulerEquations{NDIMS, NVARS} <: AbstractCompressibleEulerEquations{NDIMS, NVARS} end + +# The following functions are dimension-independent + +function get_name(equations::AbstractNonIdealCompressibleEulerEquations) + return (equations |> typeof |> nameof |> string) * "{" * + (equations.equation_of_state |> typeof |> nameof |> string) * "}" +end + +@doc raw""" + entropy_math(u, equations::AbstractNonIdealCompressibleEulerEquations) + +Calculate mathematical entropy for a conservative state `cons` as +```math +S = -\rho s +``` +where `s` is the specific entropy determined by the equation of state. +""" +@inline function entropy_math(u, equations::AbstractNonIdealCompressibleEulerEquations) + eos = equations.equation_of_state + u_thermo = cons2thermo(u, equations) + V = first(u_thermo) + T = last(u_thermo) + rho = u[1] + S = -rho * entropy_specific(V, T, eos) + return S +end + +""" + entropy(cons, equations::AbstractNonIdealCompressibleEulerEquations) + +Default entropy is the mathematical entropy +[`entropy_math(u, equations::AbstractNonIdealCompressibleEulerEquations)`](@ref). +""" +@inline function entropy(cons, equations::AbstractNonIdealCompressibleEulerEquations) + return entropy_math(cons, equations) +end + +@inline function density(u, equations::AbstractNonIdealCompressibleEulerEquations) + rho = u[1] + return rho +end + +@inline function pressure(u, equations::AbstractNonIdealCompressibleEulerEquations) + eos = equations.equation_of_state + u_thermo = cons2thermo(u, equations) + V = first(u_thermo) + T = last(u_thermo) + p = pressure(V, T, eos) + return p +end + +@inline function density_pressure(u, + equations::AbstractNonIdealCompressibleEulerEquations) + eos = equations.equation_of_state + rho = u[1] + u_thermo = cons2thermo(u, equations) + V = first(u_thermo) + T = last(u_thermo) + p = pressure(V, T, eos) + return rho * p +end + +@inline function energy_internal(u, + equations::AbstractNonIdealCompressibleEulerEquations) + eos = equations.equation_of_state + u_thermo = cons2thermo(u, equations) + V = first(u_thermo) + T = last(u_thermo) + e_internal = energy_internal(V, T, eos) + return e_internal +end + include("equations_of_state.jl") include("nonideal_compressible_euler_1d.jl") diff --git a/src/equations/nonideal_compressible_euler_1d.jl b/src/equations/nonideal_compressible_euler_1d.jl index 86f8fae4fd4..a21dd49220f 100644 --- a/src/equations/nonideal_compressible_euler_1d.jl +++ b/src/equations/nonideal_compressible_euler_1d.jl @@ -46,11 +46,6 @@ struct NonIdealCompressibleEulerEquations1D{EoS <: AbstractEquationOfState} <: equation_of_state::EoS end -function get_name(equations::AbstractNonIdealCompressibleEulerEquations) - return (equations |> typeof |> nameof |> string) * "{" * - (equations.equation_of_state |> typeof |> nameof |> string) * "}" -end - function varnames(::typeof(cons2cons), ::NonIdealCompressibleEulerEquations1D) return ("rho", "rho_v1", "rho_e_total") end @@ -303,40 +298,6 @@ end return SVector(rho, rho_v1, rho_e_total) end -@doc raw""" - entropy_math(u, equations::AbstractNonIdealCompressibleEulerEquations) - -Calculate mathematical entropy for a conservative state `cons` as -```math -S = -\rho s -``` -where `s` is the specific entropy determined by the equation of state. -""" -@inline function entropy_math(u, equations::AbstractNonIdealCompressibleEulerEquations) - eos = equations.equation_of_state - u_thermo = cons2thermo(u, equations) - V = first(u_thermo) - T = last(u_thermo) - rho = u[1] - S = -rho * entropy_specific(V, T, eos) - return S -end - -""" - entropy(cons, equations::AbstractNonIdealCompressibleEulerEquations) - -Default entropy is the mathematical entropy -[`entropy_math(u, equations::AbstractNonIdealCompressibleEulerEquations)`](@ref). -""" -@inline function entropy(cons, equations::AbstractNonIdealCompressibleEulerEquations) - return entropy_math(cons, equations) -end - -@inline function density(u, equations::AbstractNonIdealCompressibleEulerEquations) - rho = u[1] - return rho -end - @inline function velocity(u, orientation_or_normal, equations::NonIdealCompressibleEulerEquations1D) return velocity(u, equations) @@ -348,36 +309,6 @@ end return v1 end -@inline function pressure(u, equations::AbstractNonIdealCompressibleEulerEquations) - eos = equations.equation_of_state - u_thermo = cons2thermo(u, equations) - V = first(u_thermo) - T = last(u_thermo) - p = pressure(V, T, eos) - return p -end - -@inline function density_pressure(u, - equations::AbstractNonIdealCompressibleEulerEquations) - eos = equations.equation_of_state - rho = u[1] - u_thermo = cons2thermo(u, equations) - V = first(u_thermo) - T = last(u_thermo) - p = pressure(V, T, eos) - return rho * p -end - -@inline function energy_internal(u, - equations::AbstractNonIdealCompressibleEulerEquations) - eos = equations.equation_of_state - u_thermo = cons2thermo(u, equations) - V = first(u_thermo) - T = last(u_thermo) - e_internal = energy_internal(V, T, eos) - return e_internal -end - @inline function internal_energy_density(u, equations::NonIdealCompressibleEulerEquations1D) rho, rho_v1, rho_e_total = u From 6d1cb850ab87493feadf91d073bfa659bd81207a Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Thu, 5 Feb 2026 13:02:29 -0600 Subject: [PATCH 213/360] add note in NEWS.md --- NEWS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/NEWS.md b/NEWS.md index 948cc53531c..9e9d7c15ab8 100644 --- a/NEWS.md +++ b/NEWS.md @@ -16,6 +16,7 @@ for human readability. - Extended 3D support for subcell limiting with `P4estMesh` was added ([#2733]). In the new version, local (minimum or maximum) limiting for nonlinear variables (using the keyword `local_onesided_variables_nonlinear` in `SubcellLimiterIDP()`) is supported. +- Added `PengRobinson` equation of state ([#2769]). #### Changed From 646a31501dd60508b2aa6453bfb6d4f1206df629 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Thu, 5 Feb 2026 14:35:31 -0600 Subject: [PATCH 214/360] fix e -> e_internal --- src/equations/equations_of_state.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/equations/equations_of_state.jl b/src/equations/equations_of_state.jl index c890701374c..7ea7d3a0594 100644 --- a/src/equations/equations_of_state.jl +++ b/src/equations/equations_of_state.jl @@ -74,7 +74,7 @@ function temperature(V, e_internal, eos::AbstractEquationOfState; T = initial_T de = energy_internal(V, T, eos) - e_internal iter = 1 - while abs(de) > tol * abs(e) && iter < maxiter + while abs(de) > tol * abs(e_internal) && iter < maxiter de = energy_internal(V, T, eos) - e_internal # for thermodynamically admissible states, c_v = de_dT_V > 0, which should From 7ba0b435bd2f1e341975adec9af7b6328bf88ab8 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Thu, 5 Feb 2026 14:50:10 -0600 Subject: [PATCH 215/360] move shared routines into nonideal_compressible_euler.jl --- src/equations/equations.jl | 73 +------------------- src/equations/nonideal_compressible_euler.jl | 70 +++++++++++++++++++ 2 files changed, 71 insertions(+), 72 deletions(-) create mode 100644 src/equations/nonideal_compressible_euler.jl diff --git a/src/equations/equations.jl b/src/equations/equations.jl index 253a03c04d4..d63704a8dc4 100644 --- a/src/equations/equations.jl +++ b/src/equations/equations.jl @@ -650,79 +650,8 @@ include("compressible_euler_quasi_1d.jl") # Non-ideal compressibleEulerEquations abstract type AbstractNonIdealCompressibleEulerEquations{NDIMS, NVARS} <: AbstractCompressibleEulerEquations{NDIMS, NVARS} end - -# The following functions are dimension-independent - -function get_name(equations::AbstractNonIdealCompressibleEulerEquations) - return (equations |> typeof |> nameof |> string) * "{" * - (equations.equation_of_state |> typeof |> nameof |> string) * "}" -end - -@doc raw""" - entropy_math(u, equations::AbstractNonIdealCompressibleEulerEquations) - -Calculate mathematical entropy for a conservative state `cons` as -```math -S = -\rho s -``` -where `s` is the specific entropy determined by the equation of state. -""" -@inline function entropy_math(u, equations::AbstractNonIdealCompressibleEulerEquations) - eos = equations.equation_of_state - u_thermo = cons2thermo(u, equations) - V = first(u_thermo) - T = last(u_thermo) - rho = u[1] - S = -rho * entropy_specific(V, T, eos) - return S -end - -""" - entropy(cons, equations::AbstractNonIdealCompressibleEulerEquations) - -Default entropy is the mathematical entropy -[`entropy_math(u, equations::AbstractNonIdealCompressibleEulerEquations)`](@ref). -""" -@inline function entropy(cons, equations::AbstractNonIdealCompressibleEulerEquations) - return entropy_math(cons, equations) -end - -@inline function density(u, equations::AbstractNonIdealCompressibleEulerEquations) - rho = u[1] - return rho -end - -@inline function pressure(u, equations::AbstractNonIdealCompressibleEulerEquations) - eos = equations.equation_of_state - u_thermo = cons2thermo(u, equations) - V = first(u_thermo) - T = last(u_thermo) - p = pressure(V, T, eos) - return p -end - -@inline function density_pressure(u, - equations::AbstractNonIdealCompressibleEulerEquations) - eos = equations.equation_of_state - rho = u[1] - u_thermo = cons2thermo(u, equations) - V = first(u_thermo) - T = last(u_thermo) - p = pressure(V, T, eos) - return rho * p -end - -@inline function energy_internal(u, - equations::AbstractNonIdealCompressibleEulerEquations) - eos = equations.equation_of_state - u_thermo = cons2thermo(u, equations) - V = first(u_thermo) - T = last(u_thermo) - e_internal = energy_internal(V, T, eos) - return e_internal -end - include("equations_of_state.jl") +include("nonideal_compressible_euler.jl") include("nonideal_compressible_euler_1d.jl") # CompressibleEulerMulticomponentEquations diff --git a/src/equations/nonideal_compressible_euler.jl b/src/equations/nonideal_compressible_euler.jl new file mode 100644 index 00000000000..2be4c458c98 --- /dev/null +++ b/src/equations/nonideal_compressible_euler.jl @@ -0,0 +1,70 @@ +# The following functions are dimension-independent + +function get_name(equations::AbstractNonIdealCompressibleEulerEquations) + return (equations |> typeof |> nameof |> string) * "{" * + (equations.equation_of_state |> typeof |> nameof |> string) * "}" +end + +@doc raw""" + entropy_math(u, equations::AbstractNonIdealCompressibleEulerEquations) + +Calculate mathematical entropy for a conservative state `cons` as +```math +S = -\rho s +``` +where `s` is the specific entropy determined by the equation of state. +""" +@inline function entropy_math(u, equations::AbstractNonIdealCompressibleEulerEquations) + eos = equations.equation_of_state + u_thermo = cons2thermo(u, equations) + V = first(u_thermo) + T = last(u_thermo) + rho = u[1] + S = -rho * entropy_specific(V, T, eos) + return S +end + +""" + entropy(cons, equations::AbstractNonIdealCompressibleEulerEquations) + +Default entropy is the mathematical entropy +[`entropy_math(u, equations::AbstractNonIdealCompressibleEulerEquations)`](@ref). +""" +@inline function entropy(cons, equations::AbstractNonIdealCompressibleEulerEquations) + return entropy_math(cons, equations) +end + +@inline function density(u, equations::AbstractNonIdealCompressibleEulerEquations) + rho = u[1] + return rho +end + +@inline function pressure(u, equations::AbstractNonIdealCompressibleEulerEquations) + eos = equations.equation_of_state + u_thermo = cons2thermo(u, equations) + V = first(u_thermo) + T = last(u_thermo) + p = pressure(V, T, eos) + return p +end + +@inline function density_pressure(u, + equations::AbstractNonIdealCompressibleEulerEquations) + eos = equations.equation_of_state + rho = u[1] + u_thermo = cons2thermo(u, equations) + V = first(u_thermo) + T = last(u_thermo) + p = pressure(V, T, eos) + return rho * p +end + +@inline function energy_internal(u, + equations::AbstractNonIdealCompressibleEulerEquations) + eos = equations.equation_of_state + u_thermo = cons2thermo(u, equations) + V = first(u_thermo) + T = last(u_thermo) + e_internal = energy_internal(V, T, eos) + return e_internal +end From a81529428a9f4c4f1dca91b3adee712dafd37097 Mon Sep 17 00:00:00 2001 From: Daniel Doehring Date: Fri, 6 Feb 2026 08:03:26 +0100 Subject: [PATCH 216/360] Apply suggestions from code review --- src/equations/compressible_euler_1d.jl | 2 +- src/equations/compressible_euler_2d.jl | 12 ------------ 2 files changed, 1 insertion(+), 13 deletions(-) diff --git a/src/equations/compressible_euler_1d.jl b/src/equations/compressible_euler_1d.jl index eae327d0920..a226bd004e7 100644 --- a/src/equations/compressible_euler_1d.jl +++ b/src/equations/compressible_euler_1d.jl @@ -1149,7 +1149,7 @@ of total energy and kinetic energy. end @doc raw""" - entropy_potential(u, orientation::Int, equations::CompressibleEulerEquations1D) + entropy_potential(u, orientation::Int, equations::AbstractCompressibleEulerEquations) Calculate the entropy potential, which for the compressible Euler equations is simply the momentum for the choice of mathematical entropy ``S(u) = \frac{\rho s}{\gamma - 1}`` diff --git a/src/equations/compressible_euler_2d.jl b/src/equations/compressible_euler_2d.jl index 3407e4a39a5..cc667c9d918 100644 --- a/src/equations/compressible_euler_2d.jl +++ b/src/equations/compressible_euler_2d.jl @@ -2230,18 +2230,6 @@ end return energy_total(cons, equations) - energy_kinetic(cons, equations) end -@doc raw""" - entropy_potential(u, orientation::Int, equations::CompressibleEulerEquations2D) - -Calculate the entropy potential, which for the compressible Euler equations is simply -the momentum for the choice of mathematical entropy ``S(u) = \frac{\rho s}{\gamma - 1}`` -with thermodynamic entropy ``s = \ln(p) - \gamma \ln(\rho)``. - -## References -- Eitan Tadmor (2003) - Entropy stability theory for difference approximations of nonlinear conservation laws and related time-dependent problems - [DOI: 10.1017/S0962492902000156](https://doi.org/10.1017/S0962492902000156) -""" @inline function entropy_potential(u, orientation::Int, equations::CompressibleEulerEquations2D) if orientation == 1 From 5d8fd5bfa454c51e33dab06c0bf89b91e11abd1b Mon Sep 17 00:00:00 2001 From: Jesse Chan <1156048+jlchan@users.noreply.github.com> Date: Fri, 6 Feb 2026 10:27:50 -0600 Subject: [PATCH 217/360] Apply suggestions from code review Co-authored-by: Joshua Lampert <51029046+JoshuaLampert@users.noreply.github.com> --- .../elixir_euler_nonideal_transcritical_wave.jl | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/examples/tree_1d_dgsem/elixir_euler_nonideal_transcritical_wave.jl b/examples/tree_1d_dgsem/elixir_euler_nonideal_transcritical_wave.jl index 3973479a017..550a4e3968d 100644 --- a/examples/tree_1d_dgsem/elixir_euler_nonideal_transcritical_wave.jl +++ b/examples/tree_1d_dgsem/elixir_euler_nonideal_transcritical_wave.jl @@ -54,9 +54,11 @@ coordinates_min = -1.0 coordinates_max = 1.0 mesh = TreeMesh(coordinates_min, coordinates_max, initial_refinement_level = 3, - n_cells_max = 30_000) + n_cells_max = 30_000, + periodicity = true) -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) +semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver; + boundary_conditions = boundary_condition_periodic) ############################################################################### # ODE solvers, callbacks etc. From 98921a8247994a03ad05494a1028064de9281e1d Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Fri, 6 Feb 2026 10:32:35 -0600 Subject: [PATCH 218/360] format --- .../tree_1d_dgsem/elixir_euler_nonideal_transcritical_wave.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/tree_1d_dgsem/elixir_euler_nonideal_transcritical_wave.jl b/examples/tree_1d_dgsem/elixir_euler_nonideal_transcritical_wave.jl index 550a4e3968d..0230131605b 100644 --- a/examples/tree_1d_dgsem/elixir_euler_nonideal_transcritical_wave.jl +++ b/examples/tree_1d_dgsem/elixir_euler_nonideal_transcritical_wave.jl @@ -58,7 +58,7 @@ mesh = TreeMesh(coordinates_min, coordinates_max, periodicity = true) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver; - boundary_conditions = boundary_condition_periodic) + boundary_conditions = boundary_condition_periodic) ############################################################################### # ODE solvers, callbacks etc. From a5e0ad797ebe433e0bbc42f0463b0967f059aeba Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Fri, 6 Feb 2026 13:47:36 -0600 Subject: [PATCH 219/360] switch to specifying explicit IndicatorEntropyCorrection --- ...r_euler_modified_sod_entropy_correction.jl | 3 +- src/solvers/dg.jl | 18 +++++------ test/test_tree_1d_euler.jl | 7 ++-- test/test_tree_2d_euler.jl | 32 ++++++++++--------- 4 files changed, 30 insertions(+), 30 deletions(-) diff --git a/examples/tree_1d_dgsem/elixir_euler_modified_sod_entropy_correction.jl b/examples/tree_1d_dgsem/elixir_euler_modified_sod_entropy_correction.jl index b43c1783bb0..819bf85e3c7 100644 --- a/examples/tree_1d_dgsem/elixir_euler_modified_sod_entropy_correction.jl +++ b/examples/tree_1d_dgsem/elixir_euler_modified_sod_entropy_correction.jl @@ -19,7 +19,8 @@ initial_condition = initial_condition_modified_sod volume_flux = flux_central surface_flux = flux_lax_friedrichs basis = LobattoLegendreBasis(3) -volume_integral = VolumeIntegralEntropyCorrection(equations, basis; +indicator = IndicatorEntropyCorrection(equations, basis) +volume_integral = VolumeIntegralEntropyCorrection(indicator; volume_flux_dg = volume_flux, volume_flux_fv = surface_flux) solver = DGSEM(basis, surface_flux, volume_integral) diff --git a/src/solvers/dg.jl b/src/solvers/dg.jl index cbf9871e029..7d90f052721 100644 --- a/src/solvers/dg.jl +++ b/src/solvers/dg.jl @@ -281,16 +281,16 @@ function Base.show(io::IO, mime::MIME"text/plain", end """ - VolumeIntegralEntropyCorrection(equations, basis; + VolumeIntegralEntropyCorrection(indicator; volume_flux_dg=flux_central, - volume_flux_fv=flux_lax_friedrichs, - scaling = true) + volume_flux_fv=flux_lax_friedrichs) Entropy correction volume integral type for DG methods using a convex blending of the **first-order** finite volume method with numerical flux `volume_flux_fv` and the -[`VolumeIntegralFluxDifferencing`](@ref) with volume flux `volume_flux_dg`. -The amount of blending is determined by the violation of a cell entropy equality by -the volume integral. +[`VolumeIntegralFluxDifferencing`](@ref) with volume flux `volume_flux_dg`. This is +intended to be used with [`IndicatorEntropyCorrection`](@ref), which determines the +amount of blending based on the violation of a cell entropy equality by the volume +integral. `scaling ≥ 1` scales the DG-FV blending parameter ``\\alpha``(see the [tutorial on shock-capturing](https://trixi-framework.github.io/TrixiDocumentation/stable/tutorials/shock_capturing/#Shock-capturing-with-flux-differencing)) @@ -310,11 +310,9 @@ struct VolumeIntegralEntropyCorrection{VolumeFluxDG, VolumeFluxFV, Indicator} <: indicator::Indicator end -function VolumeIntegralEntropyCorrection(equations, basis; +function VolumeIntegralEntropyCorrection(indicator; volume_flux_dg = flux_central, - volume_flux_fv = flux_lax_friedrichs, - scaling = true) - indicator = IndicatorEntropyCorrection(equations, basis; scaling) + volume_flux_fv = flux_lax_friedrichs) return VolumeIntegralEntropyCorrection{typeof(volume_flux_dg), typeof(volume_flux_fv), typeof(indicator)}(volume_flux_dg, diff --git a/test/test_tree_1d_euler.jl b/test/test_tree_1d_euler.jl index a056336ce08..46d54dfbb96 100644 --- a/test/test_tree_1d_euler.jl +++ b/test/test_tree_1d_euler.jl @@ -674,10 +674,9 @@ end @trixi_testset "elixir_euler_nonideal_density_wave.jl with entropy correction" begin @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_nonideal_density_wave.jl"), - solver=DGSEM(LobattoLegendreBasis(3), - flux_lax_friedrichs, - VolumeIntegralEntropyCorrection(CompressibleEulerEquations1D(1.4), - LobattoLegendreBasis(3); + solver=DGSEM(LobattoLegendreBasis(3), flux_lax_friedrichs, + VolumeIntegralEntropyCorrection(IndicatorEntropyCorrection(CompressibleEulerEquations1D(1.4), + LobattoLegendreBasis(3)), volume_flux_dg = flux_central, volume_flux_fv = flux_lax_friedrichs)), tspan=(0.0, 0.1), diff --git a/test/test_tree_2d_euler.jl b/test/test_tree_2d_euler.jl index edd815ed221..dd0fd28b738 100644 --- a/test/test_tree_2d_euler.jl +++ b/test/test_tree_2d_euler.jl @@ -114,8 +114,8 @@ end @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_density_wave.jl"), solver=DGSEM(LobattoLegendreBasis(3), flux_lax_friedrichs, - VolumeIntegralEntropyCorrection(CompressibleEulerEquations2D(1.4), - LobattoLegendreBasis(3); + VolumeIntegralEntropyCorrection(IndicatorEntropyCorrection(CompressibleEulerEquations2D(1.4), + LobattoLegendreBasis(3)), volume_flux_dg = flux_central, volume_flux_fv = flux_lax_friedrichs)), tspan=(0.0, 0.1), @@ -587,22 +587,24 @@ end # adding `scaling = 2` increases the amount of subcell FV blended in by # a factor of 2. If this is not added, the KHI simulation crashes with a # positivity violation at some time t < 3. - volume_integral=VolumeIntegralEntropyCorrection(equations, - basis; - volume_flux_dg = volume_flux, - volume_flux_fv = surface_flux, - scaling = 2.0), + solver=DGSEM(LobattoLegendreBasis(3), + flux_lax_friedrichs, + VolumeIntegralEntropyCorrection(IndicatorEntropyCorrection(CompressibleEulerEquations2D(1.4), + LobattoLegendreBasis(3); + scaling = 2), + volume_flux_dg = flux_central, + volume_flux_fv = flux_lax_friedrichs)), l2=[ - 0.5598004443337804, - 0.19783160019699073, - 0.22886409540262306, - 0.17616905595853216 + 0.5478424814984363, + 0.2040045541508178, + 0.22224623172603333, + 0.1688290550316014 ], linf=[ - 2.2756723403007273, - 0.7899848710261611, - 0.7613239119300793, - 0.6332611254490705 + 2.0077477196844606, + 0.7856525305298842, + 0.6907389422800942, + 0.6256780117443306 ]) end From 7ca4359d400d343244fca79834b6df25518a159b Mon Sep 17 00:00:00 2001 From: Jesse Chan <1156048+jlchan@users.noreply.github.com> Date: Fri, 6 Feb 2026 13:50:42 -0600 Subject: [PATCH 220/360] Update examples/tree_1d_dgsem/elixir_euler_nonideal_transcritical_shock.jl Co-authored-by: Hendrik Ranocha --- .../elixir_euler_nonideal_transcritical_shock.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/tree_1d_dgsem/elixir_euler_nonideal_transcritical_shock.jl b/examples/tree_1d_dgsem/elixir_euler_nonideal_transcritical_shock.jl index 4e53f2d00a3..a9d04f90a7b 100644 --- a/examples/tree_1d_dgsem/elixir_euler_nonideal_transcritical_shock.jl +++ b/examples/tree_1d_dgsem/elixir_euler_nonideal_transcritical_shock.jl @@ -17,9 +17,9 @@ function initial_condition_transcritical_shock(x, t, eos = equations.equation_of_state if x[1] < 0 - rho, v1, p = SVector(800, 0, 60e6) + rho, v1, p = SVector(800, 0, convert(RealT, 60.0e6)) else - rho, v1, p = SVector(80, 0, 6e6) + rho, v1, p = SVector(80, 0, convert(RealT, 6.0e6)) end V = inv(rho) From 5dae0b10af5341672f9ff23e4b36067bd5f036bf Mon Sep 17 00:00:00 2001 From: Jesse Chan <1156048+jlchan@users.noreply.github.com> Date: Fri, 6 Feb 2026 13:53:18 -0600 Subject: [PATCH 221/360] Update src/equations/equation_of_state_peng_robinson.jl Co-authored-by: Hendrik Ranocha --- src/equations/equation_of_state_peng_robinson.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/equations/equation_of_state_peng_robinson.jl b/src/equations/equation_of_state_peng_robinson.jl index c2a7b69b331..dd44d9e3382 100644 --- a/src/equations/equation_of_state_peng_robinson.jl +++ b/src/equations/equation_of_state_peng_robinson.jl @@ -104,12 +104,12 @@ function pressure(V, T, eos::PengRobinson) end @doc raw""" - energy_internal(V, T, eos::PengRobinson) + energy_internal_specific(V, T, eos::PengRobinson) -Computes internal energy for a Peng-Robinson gas from specific volume `V` and temperature `T` as +Computes specific internal energy for a Peng-Robinson gas from specific volume `V` and temperature `T` as ``e_{\text{internal}} = c_{v,0} T + K_1 (a(T) - T a'(T))``. """ -function energy_internal(V, T, eos::PengRobinson) +function energy_internal_specific(V, T, eos::PengRobinson) (; cv0) = eos K1 = calc_K1(V, eos) e_internal = cv0 * T + K1 * (peng_robinson_a(T, eos) - T * peng_robinson_da(T, eos)) From 98bb0b33afbdc4f9ef4d89f42fa6d30e0cbf3a4e Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Fri, 6 Feb 2026 13:58:12 -0600 Subject: [PATCH 222/360] add change to v0.15 section --- NEWS.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index edf0425e666..83c5a9df94f 100644 --- a/NEWS.md +++ b/NEWS.md @@ -13,6 +13,9 @@ for human readability. - `convergence_test` now returns the complete convergence orders and the full errors matrix. To obtain the mean convergence rates, use `Trixi.calc_mean_convergence` on the convergence orders ([#2753]). - The serial and parallel mesh types have been renamed from `SerialTreeMesh`, `ParallelTreeMesh`, `SerialP4estMesh`, `ParallelP4estMesh`, `SerialT8codeMesh`, and `ParallelT8codeMesh` to `TreeMeshSerial`, `TreeMeshParallel`, `P4estMeshSerial`, `P4estMeshParallel`, `T8codeMeshSerial`, and `T8codeMeshParallel`, respectively ([#2787]). +#### Added +- Added `PengRobinson` equation of state ([#2769]). + ## Changes in the v0.14 lifecycle #### Added @@ -24,7 +27,6 @@ for human readability. - Extended 3D support for subcell limiting with `P4estMesh` was added ([#2733]). In the new version, local (minimum or maximum) limiting for nonlinear variables (using the keyword `local_onesided_variables_nonlinear` in `SubcellLimiterIDP()`) is supported. -- Added `PengRobinson` equation of state ([#2769]). #### Changed From 15f0f0e64d421da88d796cfb9057136e155d20a3 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Fri, 6 Feb 2026 13:59:12 -0600 Subject: [PATCH 223/360] formatting --- test/test_unit.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/test_unit.jl b/test/test_unit.jl index 071c347a8aa..773c2c00274 100644 --- a/test/test_unit.jl +++ b/test/test_unit.jl @@ -807,7 +807,8 @@ end V, v1, T = Trixi.cons2thermo(u, equations) e_internal = energy_internal_specific(V, T, eos) @test temperature(V, e_internal, eos) ≈ - invoke(temperature, Tuple{Any, Any, Trixi.AbstractEquationOfState}, V, e_internal, eos) + invoke(temperature, Tuple{Any, Any, Trixi.AbstractEquationOfState}, V, + e_internal, eos) @test cons2prim(u, equations) ≈ SVector(u[1], v1, pressure(u, equations)) From 45fa79a6fefd08e5e2c82b54754551d04b981ba3 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Fri, 6 Feb 2026 14:03:10 -0600 Subject: [PATCH 224/360] replace energy_internal -> energy_internal_specific --- src/equations/nonideal_compressible_euler.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/equations/nonideal_compressible_euler.jl b/src/equations/nonideal_compressible_euler.jl index 2be4c458c98..e25238c0735 100644 --- a/src/equations/nonideal_compressible_euler.jl +++ b/src/equations/nonideal_compressible_euler.jl @@ -59,12 +59,12 @@ end return rho * p end -@inline function energy_internal(u, - equations::AbstractNonIdealCompressibleEulerEquations) +@inline function energy_internal_specific(u, + equations::AbstractNonIdealCompressibleEulerEquations) eos = equations.equation_of_state u_thermo = cons2thermo(u, equations) V = first(u_thermo) T = last(u_thermo) - e_internal = energy_internal(V, T, eos) + e_internal = energy_internal_specific(V, T, eos) return e_internal end From 65fdc1d50afc2b8ff73559f1f2829b7cbccc51ca Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Fri, 6 Feb 2026 14:08:56 -0600 Subject: [PATCH 225/360] update NEWS.md with more changes --- NEWS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/NEWS.md b/NEWS.md index 83c5a9df94f..6281e903d61 100644 --- a/NEWS.md +++ b/NEWS.md @@ -9,6 +9,7 @@ for human readability. #### Changed - Renamed `energy_internal` for `NonIdealCompressibleEulerEquations1D` to `energy_internal_specific` ([#2762]). This makes `energy_internal` for `NonIdealCompressibleEulerEquations1D` consistent with the rest of Trixi.jl, where `energy_internal` refers to the specific internal energy scaled by density. +- Changed `cons2prim(u, ::NonIdealCompressibleEulerEquations1D)` so that it returns `rho, v1, p`. Previously, `cons2prim` returned `V, v1, T`; this functionalityis now provided by the non-exported function `cons2thermo`. - The variable name (printed to the screen and files) of the total energy in the compressible Euler equations has been changed from `rho_e` to `rho_e_total` to avoid confusion with the internal energy `rho_e_internal` ([#2778]). - `convergence_test` now returns the complete convergence orders and the full errors matrix. To obtain the mean convergence rates, use `Trixi.calc_mean_convergence` on the convergence orders ([#2753]). - The serial and parallel mesh types have been renamed from `SerialTreeMesh`, `ParallelTreeMesh`, `SerialP4estMesh`, `ParallelP4estMesh`, `SerialT8codeMesh`, and `ParallelT8codeMesh` to `TreeMeshSerial`, `TreeMeshParallel`, `P4estMeshSerial`, `P4estMeshParallel`, `T8codeMeshSerial`, and `T8codeMeshParallel`, respectively ([#2787]). From 1a19d69156b70d50a3f35a7385fad45ba3155816 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Fri, 6 Feb 2026 14:15:08 -0600 Subject: [PATCH 226/360] use energy_internal and energy_internal_specific --- .../nonideal_compressible_euler_2d.jl | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/equations/nonideal_compressible_euler_2d.jl b/src/equations/nonideal_compressible_euler_2d.jl index fa0235631e4..e8eaf03d2cf 100644 --- a/src/equations/nonideal_compressible_euler_2d.jl +++ b/src/equations/nonideal_compressible_euler_2d.jl @@ -39,7 +39,7 @@ and temperature ``T`` by some user-specified equation of state (EOS) (see [`pres p = p(V, T) ``` -Similarly, the internal energy is specified by `e = energy_internal(V, T, eos)`, see +Similarly, the internal energy is specified by `e_{\text{internal}} = energy_internal(V, T, eos)`, see [`energy_internal(V, T, eos::IdealGas)`](@ref), [`energy_internal(V, T, eos::VanDerWaals)`](@ref). Because of this, the primitive variables are also defined to be `V, v1, v2, T` (instead of @@ -232,8 +232,8 @@ function flux_terashima_etal(u_ll, u_rr, orientation::Int, rho_ll = u_ll[1] rho_rr = u_rr[1] - rho_e_ll = internal_energy_density(u_ll, equations) - rho_e_rr = internal_energy_density(u_rr, equations) + rho_e_ll = energy_internal(u_ll, equations) + rho_e_rr = energy_internal(u_rr, equations) p_ll = pressure(V_ll, T_ll, eos) p_rr = pressure(V_rr, T_rr, eos) @@ -276,8 +276,8 @@ function flux_terashima_etal(u_ll, u_rr, normal_direction::AbstractVector, rho_ll = u_ll[1] rho_rr = u_rr[1] - rho_e_ll = internal_energy_density(u_ll, equations) - rho_e_rr = internal_energy_density(u_rr, equations) + rho_e_ll = energy_internal(u_ll, equations) + rho_e_rr = energy_internal(u_rr, equations) p_ll = pressure(V_ll, T_ll, eos) p_rr = pressure(V_rr, T_rr, eos) @@ -326,8 +326,8 @@ function flux_central_terashima_etal(u_ll, u_rr, orientation::Int, rho_ll, rho_v1_ll, rho_v2_ll, rho_e_total_ll = u_ll rho_rr, rho_v1_rr, rho_v2_rr, rho_e_total_rr = u_rr - rho_e_ll = internal_energy_density(u_ll, equations) - rho_e_rr = internal_energy_density(u_rr, equations) + rho_e_ll = energy_internal(u_ll, equations) + rho_e_rr = energy_internal(u_rr, equations) p_ll = pressure(V_ll, T_ll, eos) p_rr = pressure(V_rr, T_rr, eos) @@ -375,8 +375,8 @@ function flux_central_terashima_etal(u_ll, u_rr, normal_direction::AbstractVecto rho_ll, rho_v1_ll, rho_v2_ll, rho_e_total_ll = u_ll rho_rr, rho_v1_rr, rho_v2_rr, rho_e_total_rr = u_rr - rho_e_ll = internal_energy_density(u_ll, equations) - rho_e_rr = internal_energy_density(u_rr, equations) + rho_e_ll = energy_internal(u_ll, equations) + rho_e_rr = energy_internal(u_rr, equations) p_ll = pressure(V_ll, T_ll, eos) p_rr = pressure(V_rr, T_rr, eos) @@ -622,8 +622,8 @@ equation of state routines are assumed to be evaluated in terms of `V` and `T`. V = inv(rho) v1 = rho_v1 * V v2 = rho_v2 * V - e = internal_energy_density(u, equations) * V - T = temperature(V, e, eos) + e_internal = energy_internal(u, equations) * V + T = temperature(V, e_internal, eos) return SVector(V, v1, v2, T) end @@ -643,8 +643,8 @@ end rho = inv(V) rho_v1 = rho * v1 rho_v2 = rho * v2 - e = energy_internal(V, T, eos) - rho_e_total = rho * e + 0.5f0 * (rho_v1 * v1 + rho_v2 * v2) + e_internal = energy_internal_specific(V, T, eos) + rho_e_total = rho * e_internal + 0.5f0 * (rho_v1 * v1 + rho_v2 * v2) return SVector(rho, rho_v1, rho_v2, rho_e_total) end @@ -666,8 +666,8 @@ end return SVector(v1, v2) end -@inline function internal_energy_density(u, - equations::NonIdealCompressibleEulerEquations2D) +@inline function energy_internal(u, + equations::NonIdealCompressibleEulerEquations2D) rho, rho_v1, rho_v2, rho_e_total = u rho_e = rho_e_total - 0.5f0 * (rho_v1^2 + rho_v2^2) / rho return rho_e From 76bf781f64e478e0adbabe06f9bf77ef0e6497dd Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Fri, 6 Feb 2026 14:15:17 -0600 Subject: [PATCH 227/360] reference terashima APEC properly --- .../nonideal_compressible_euler_2d.jl | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/equations/nonideal_compressible_euler_2d.jl b/src/equations/nonideal_compressible_euler_2d.jl index e8eaf03d2cf..a82f6bd9731 100644 --- a/src/equations/nonideal_compressible_euler_2d.jl +++ b/src/equations/nonideal_compressible_euler_2d.jl @@ -218,10 +218,12 @@ end flux_terashima_etal(u_ll, u_rr, orientation::Int, equations::NonIdealCompressibleEulerEquations1D) -Approximately pressure equilibrium conserving (APEC) flux from -"Approximately pressure-equilibrium-preserving scheme for fully conservative -simulations of compressible multi-species and real-fluid interfacial flows" -by Terashima, Ly, Ihme (2025). https://doi.org/10.1016/j.jcp.2024.11370 1 +Approximately pressure equilibrium conserving (APEC) flux. + +- H. Terashima, N. Ly, M. Ihme (2025) + Approximately pressure-equilibrium-preserving scheme for fully conservative simulations of + compressible multi-species and real-fluid interfacial flows + [DOI: 10.1016/j.jcp.2024.113701](https://doi.org/10.1016/j.jcp.2024.113701) """ function flux_terashima_etal(u_ll, u_rr, orientation::Int, @@ -313,10 +315,12 @@ end equations::NonIdealCompressibleEulerEquations1D) A version of the central flux which uses the approximately pressure equilibrium conserving -(APEC) internal energy correction of -"Approximately pressure-equilibrium-preserving scheme for fully conservative -simulations of compressible multi-species and real-fluid interfacial flows" -by Terashima, Ly, Ihme (2025). https://doi.org/10.1016/j.jcp.2024.11370 +(APEC) internal energy correction. + +- H. Terashima, N. Ly, M. Ihme (2025) + Approximately pressure-equilibrium-preserving scheme for fully conservative simulations of + compressible multi-species and real-fluid interfacial flows + [DOI: 10.1016/j.jcp.2024.113701](https://doi.org/10.1016/j.jcp.2024.113701) """ function flux_central_terashima_etal(u_ll, u_rr, orientation::Int, equations::NonIdealCompressibleEulerEquations2D) From 901b5710230e6dec03e4c4fa36b9725e9f52cdf9 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Fri, 6 Feb 2026 14:15:30 -0600 Subject: [PATCH 228/360] reference Terashima APEC paper properly --- src/equations/nonideal_compressible_euler_1d.jl | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/equations/nonideal_compressible_euler_1d.jl b/src/equations/nonideal_compressible_euler_1d.jl index a4b6c8a6ce3..e1bc30fcd75 100644 --- a/src/equations/nonideal_compressible_euler_1d.jl +++ b/src/equations/nonideal_compressible_euler_1d.jl @@ -103,9 +103,11 @@ end equations::NonIdealCompressibleEulerEquations1D) Approximately pressure equilibrium preserving with conservation (APEC) flux from -"Approximately pressure-equilibrium-preserving scheme for fully conservative -simulations of compressible multi-species and real-fluid interfacial flows" -by Terashima, Ly, Ihme (2025). + +- H. Terashima, N. Ly, M. Ihme (2025) + Approximately pressure-equilibrium-preserving scheme for fully conservative simulations of + compressible multi-species and real-fluid interfacial flows + [DOI: 10.1016/j.jcp.2024.113701](https://doi.org/10.1016/j.jcp.2024.113701) """ @inline function flux_terashima_etal(u_ll, u_rr, orientation::Int, From aa71f779d5bfdd34c0e79355f0ab48e3123310df Mon Sep 17 00:00:00 2001 From: Daniel Doehring Date: Sun, 8 Feb 2026 16:15:46 +0100 Subject: [PATCH 229/360] Apply suggestion from @DanielDoehring --- src/callbacks_step/analysis_dg2d.jl | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/callbacks_step/analysis_dg2d.jl b/src/callbacks_step/analysis_dg2d.jl index 2befbecc6dd..fd3b2aed864 100644 --- a/src/callbacks_step/analysis_dg2d.jl +++ b/src/callbacks_step/analysis_dg2d.jl @@ -239,19 +239,19 @@ function surface_integral(func::Func, u, element, mesh::TreeMesh{2}, equations, dg::DGSEM, cache, args...) where {Func} surface_integral = zero(real(dg)) - for ii in eachnode(dg) + for i in eachnode(dg) # integrate along x direction, normal in y (2) direction - u_bottom = get_node_vars(u, equations, dg, ii, 1, element) - u_top = get_node_vars(u, equations, dg, ii, nnodes(dg), element) + u_bottom = get_node_vars(u, equations, dg, i, 1, element) + u_top = get_node_vars(u, equations, dg, i, nnodes(dg), element) - surface_integral += dg.basis.weights[ii] * + surface_integral += dg.basis.weights[i] * (func(u_top, 2, equations) - func(u_bottom, 2, equations)) # integrate along y direction, normal in x (1) direction - u_left = get_node_vars(u, equations, dg, 1, ii, element) - u_right = get_node_vars(u, equations, dg, nnodes(dg), ii, element) + u_left = get_node_vars(u, equations, dg, 1, i, element) + u_right = get_node_vars(u, equations, dg, nnodes(dg), i, element) - surface_integral += dg.basis.weights[ii] * + surface_integral += dg.basis.weights[i] * (func(u_right, 1, equations) - func(u_left, 1, equations)) end From 9a97a63c4651cf08c5eea3424435bbe32ba25daa Mon Sep 17 00:00:00 2001 From: Daniel_Doehring Date: Mon, 9 Feb 2026 11:37:43 +0100 Subject: [PATCH 230/360] use nonideal eqs --- test/test_tree_1d_euler.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_tree_1d_euler.jl b/test/test_tree_1d_euler.jl index 471d5f49c7f..4286a378d8c 100644 --- a/test/test_tree_1d_euler.jl +++ b/test/test_tree_1d_euler.jl @@ -688,7 +688,7 @@ end @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_nonideal_density_wave.jl"), solver=DGSEM(LobattoLegendreBasis(3), flux_lax_friedrichs, - VolumeIntegralEntropyCorrection(IndicatorEntropyCorrection(CompressibleEulerEquations1D(1.4), + VolumeIntegralEntropyCorrection(IndicatorEntropyCorrection(equations, LobattoLegendreBasis(3)), volume_flux_dg = flux_central, volume_flux_fv = flux_lax_friedrichs)), From ff4661b22fd166e9d67ac1a45d55df7f0061492c Mon Sep 17 00:00:00 2001 From: Daniel_Doehring Date: Mon, 9 Feb 2026 11:50:50 +0100 Subject: [PATCH 231/360] reuse eqs --- test/test_tree_2d_euler.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_tree_2d_euler.jl b/test/test_tree_2d_euler.jl index dd0fd28b738..f4acab83ee9 100644 --- a/test/test_tree_2d_euler.jl +++ b/test/test_tree_2d_euler.jl @@ -114,7 +114,7 @@ end @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_density_wave.jl"), solver=DGSEM(LobattoLegendreBasis(3), flux_lax_friedrichs, - VolumeIntegralEntropyCorrection(IndicatorEntropyCorrection(CompressibleEulerEquations2D(1.4), + VolumeIntegralEntropyCorrection(IndicatorEntropyCorrection(equations, LobattoLegendreBasis(3)), volume_flux_dg = flux_central, volume_flux_fv = flux_lax_friedrichs)), @@ -589,7 +589,7 @@ end # positivity violation at some time t < 3. solver=DGSEM(LobattoLegendreBasis(3), flux_lax_friedrichs, - VolumeIntegralEntropyCorrection(IndicatorEntropyCorrection(CompressibleEulerEquations2D(1.4), + VolumeIntegralEntropyCorrection(IndicatorEntropyCorrection(equations, LobattoLegendreBasis(3); scaling = 2), volume_flux_dg = flux_central, From ac0c420cde5a9cb191367783a9492dcb53bf411e Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Mon, 9 Feb 2026 09:35:09 -0600 Subject: [PATCH 232/360] fix energy_internal in docstrings --- src/equations/nonideal_compressible_euler_2d.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/equations/nonideal_compressible_euler_2d.jl b/src/equations/nonideal_compressible_euler_2d.jl index a82f6bd9731..623c9c80943 100644 --- a/src/equations/nonideal_compressible_euler_2d.jl +++ b/src/equations/nonideal_compressible_euler_2d.jl @@ -39,8 +39,8 @@ and temperature ``T`` by some user-specified equation of state (EOS) (see [`pres p = p(V, T) ``` -Similarly, the internal energy is specified by `e_{\text{internal}} = energy_internal(V, T, eos)`, see -[`energy_internal(V, T, eos::IdealGas)`](@ref), [`energy_internal(V, T, eos::VanDerWaals)`](@ref). +Similarly, the specific internal energy is specified by `e_{\text{internal}} = energy_internal_specific(V, T, eos)`, see +[`energy_internal_specific(V, T, eos::IdealGas)`](@ref), [`energy_internal_specific(V, T, eos::VanDerWaals)`](@ref). Because of this, the primitive variables are also defined to be `V, v1, v2, T` (instead of `rho, v1, v2, p` for `CompressibleEulerEquations2D`). The implementation also assumes From fc0c0ac153f63c9bdb4b52810cb8699773b0d5d4 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Mon, 9 Feb 2026 09:35:29 -0600 Subject: [PATCH 233/360] fix tests --- test/test_unit.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_unit.jl b/test/test_unit.jl index 71d9d06c49b..973ede27666 100644 --- a/test/test_unit.jl +++ b/test/test_unit.jl @@ -838,7 +838,7 @@ end @test density(u, equations) ≈ 0.5 @test velocity(u, equations) ≈ SVector(0.1, 0.2) @test density_pressure(u, equations) ≈ u[1] * pressure(V, T, eos) - @test energy_internal(u, equations) ≈ energy_internal(V, T, eos) + @test energy_internal_specific(u, equations) ≈ energy_internal_specific(V, T, eos) @test ForwardDiff.gradient(u -> entropy(u, equations), u) ≈ cons2entropy(u, equations) @@ -875,7 +875,7 @@ end # check that the fallback temperature and specialized temperature # return the same value V, v1, v2, T = cons2prim(u, equations) - e = energy_internal(V, T, eos) + e = energy_internal_specific(V, T, eos) @test temperature(V, e, eos) ≈ invoke(temperature, Tuple{Any, Any, Trixi.AbstractEquationOfState}, V, e, eos) From d6399955d0a304c5606cfdce5ff1202e8aaa5e61 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Mon, 9 Feb 2026 14:06:58 -0600 Subject: [PATCH 234/360] explicitly provide periodic BCs --- .../tree_2d_dgsem/elixir_euler_nonideal_density_wave.jl | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/examples/tree_2d_dgsem/elixir_euler_nonideal_density_wave.jl b/examples/tree_2d_dgsem/elixir_euler_nonideal_density_wave.jl index 8f7454c60c0..4d92d3841ae 100644 --- a/examples/tree_2d_dgsem/elixir_euler_nonideal_density_wave.jl +++ b/examples/tree_2d_dgsem/elixir_euler_nonideal_density_wave.jl @@ -19,9 +19,11 @@ coordinates_min = (-1.0, -1.0) coordinates_max = (1.0, 1.0) mesh = TreeMesh(coordinates_min, coordinates_max, initial_refinement_level = 3, - n_cells_max = 30_000) + n_cells_max = 30_000, + periodicity = true) -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) +semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver; + boundary_conditions = boundary_condition_periodic) ############################################################################### # ODE solvers, callbacks etc. From 92598a261f2a818acb26865f8ebb7448b87fede3 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Mon, 9 Feb 2026 14:07:06 -0600 Subject: [PATCH 235/360] fix test name --- test/test_unit.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_unit.jl b/test/test_unit.jl index 973ede27666..5ba2a95e49b 100644 --- a/test/test_unit.jl +++ b/test/test_unit.jl @@ -828,7 +828,7 @@ end Tuple{Any, Any, Trixi.AbstractEquationOfState}, V, T, eos)[2] end -@timed_testset "Test consistency (fluxes, entropy/cons2entropy) for NonIdealCompressibleEulerEquations1D" begin +@timed_testset "Test consistency (fluxes, entropy/cons2entropy) for NonIdealCompressibleEulerEquations2D" begin eos = VanDerWaals(; a = 10, b = 0.01, R = 287, gamma = 1.4) equations = NonIdealCompressibleEulerEquations2D(eos) q = SVector(2.0, 0.1, 0.2, 10.0) From 9e603646c1c8a8df72482c3d3859d016897bbf24 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Mon, 9 Feb 2026 14:07:10 -0600 Subject: [PATCH 236/360] add more tests --- test/test_tree_2d_euler.jl | 40 ++++++++++++++++++++++++++++++++++++++ test/test_unit.jl | 10 ++++++---- 2 files changed, 46 insertions(+), 4 deletions(-) diff --git a/test/test_tree_2d_euler.jl b/test/test_tree_2d_euler.jl index ecf078272c4..cc480e29922 100644 --- a/test/test_tree_2d_euler.jl +++ b/test/test_tree_2d_euler.jl @@ -110,6 +110,46 @@ end @test_allocations(Trixi.rhs!, semi, sol, 1000) end +@trixi_testset "elixir_euler_nonideal_density_wave.jl" begin + @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_nonideal_density_wave.jl"), + tspan=(0.0, 0.5), + l2=[ + 0.005128264024223658, + 0.0005102755455974174, + 0.0010227429683760888, + 0.2647398010816978 + ], + linf=[ + 0.022550400235924695, + 0.00214674544358473, + 0.004371621105621781, + 1.0212525633021983 + ]) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + @test_allocations(Trixi.rhs!, semi, sol, 1000) +end + +@trixi_testset "elixir_euler_nonideal_density_wave.jl (min_max_speed_naive)" begin + @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_nonideal_density_wave.jl"), + tspan=(0.0, 0.5), surface_flux=FluxHLL(min_max_speed_naive), + l2=[ + 0.005120569410818968, + 0.0005107175634328961, + 0.0010214028052822862, + 0.26424662406680544 + ], + linf=[ + 0.02253539764111734, + 0.0021502487629356526, + 0.004367440276381418, + 1.0203844424142972 + ]) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + @test_allocations(Trixi.rhs!, semi, sol, 1000) +end + @trixi_testset "elixir_euler_source_terms_nonperiodic.jl" begin @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_source_terms_nonperiodic.jl"), diff --git a/test/test_unit.jl b/test/test_unit.jl index 5ba2a95e49b..c1d0b7b2a64 100644 --- a/test/test_unit.jl +++ b/test/test_unit.jl @@ -867,10 +867,12 @@ end flux_central_terashima_etal(u_ll, u_rr, 1, equations) * normal_direction[1] + flux_central_terashima_etal(u_ll, u_rr, 2, equations) * normal_direction[2] - @test flux_lax_friedrichs(u_ll, u_rr, 1, equations) ≈ - flux_lax_friedrichs(u_ll, u_rr, SVector(1, 0), equations) - @test flux_lax_friedrichs(u_ll, u_rr, 2, equations) ≈ - flux_lax_friedrichs(u_ll, u_rr, SVector(0, 1), equations) + for _flux_function in (flux_lax_friedrichs, min_max_speed_naive, min_max_speed_davis) + @test all(_flux_function(u_ll, u_rr, 1, equations) .≈ + _flux_function(u_ll, u_rr, SVector(1, 0), equations)) + @test all(_flux_function(u_ll, u_rr, 2, equations) .≈ + _flux_function(u_ll, u_rr, SVector(0, 1), equations)) + end # check that the fallback temperature and specialized temperature # return the same value From eb68cc6a0d2d5c2147a9ff0f4eeca4a052c42323 Mon Sep 17 00:00:00 2001 From: Jesse Chan <1156048+jlchan@users.noreply.github.com> Date: Mon, 9 Feb 2026 14:53:42 -0600 Subject: [PATCH 237/360] Update src/auxiliary/math.jl Co-authored-by: Hendrik Ranocha --- src/auxiliary/math.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/auxiliary/math.jl b/src/auxiliary/math.jl index 6e615ba8728..9bff0a557ed 100644 --- a/src/auxiliary/math.jl +++ b/src/auxiliary/math.jl @@ -448,5 +448,5 @@ end # regularized approximation to the ratio a / b which is numerically stable # for b close to zero. -@inline regularized_ratio(a, b) = a * b / (eps(b) + b^2) +@inline regularized_ratio(a, b) = a * b / (eps(typeof(b)) + b^2) end # @muladd From 7885f92fb2d1e41cb2ad54fafe9369322c52f109 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Mon, 9 Feb 2026 15:05:52 -0600 Subject: [PATCH 238/360] formatting --- test/test_unit.jl | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/test/test_unit.jl b/test/test_unit.jl index c1d0b7b2a64..708455c64ca 100644 --- a/test/test_unit.jl +++ b/test/test_unit.jl @@ -867,11 +867,12 @@ end flux_central_terashima_etal(u_ll, u_rr, 1, equations) * normal_direction[1] + flux_central_terashima_etal(u_ll, u_rr, 2, equations) * normal_direction[2] - for _flux_function in (flux_lax_friedrichs, min_max_speed_naive, min_max_speed_davis) + for _flux_function in (flux_lax_friedrichs, min_max_speed_naive, + min_max_speed_davis) @test all(_flux_function(u_ll, u_rr, 1, equations) .≈ - _flux_function(u_ll, u_rr, SVector(1, 0), equations)) + _flux_function(u_ll, u_rr, SVector(1, 0), equations)) @test all(_flux_function(u_ll, u_rr, 2, equations) .≈ - _flux_function(u_ll, u_rr, SVector(0, 1), equations)) + _flux_function(u_ll, u_rr, SVector(0, 1), equations)) end # check that the fallback temperature and specialized temperature From a57ca5d04cbfbfa8587973b80acf93a7ce1953c2 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Mon, 9 Feb 2026 17:01:19 -0600 Subject: [PATCH 239/360] more tests --- test/test_unit.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/test_unit.jl b/test/test_unit.jl index 708455c64ca..a56ee64c44f 100644 --- a/test/test_unit.jl +++ b/test/test_unit.jl @@ -837,6 +837,7 @@ end @test density(u, equations) ≈ 0.5 @test velocity(u, equations) ≈ SVector(0.1, 0.2) + @test velocity(u, 1, equations) ≈ 0.1 @test density_pressure(u, equations) ≈ u[1] * pressure(V, T, eos) @test energy_internal_specific(u, equations) ≈ energy_internal_specific(V, T, eos) @@ -868,7 +869,7 @@ end flux_central_terashima_etal(u_ll, u_rr, 2, equations) * normal_direction[2] for _flux_function in (flux_lax_friedrichs, min_max_speed_naive, - min_max_speed_davis) + min_max_speed_davis, max_abs_speed_naive) @test all(_flux_function(u_ll, u_rr, 1, equations) .≈ _flux_function(u_ll, u_rr, SVector(1, 0), equations)) @test all(_flux_function(u_ll, u_rr, 2, equations) .≈ From b302900f0e02e45d3e47c1729040759c65b472cb Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Mon, 9 Feb 2026 17:23:22 -0600 Subject: [PATCH 240/360] fix velocity function d --- src/equations/nonideal_compressible_euler_2d.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/src/equations/nonideal_compressible_euler_2d.jl b/src/equations/nonideal_compressible_euler_2d.jl index 623c9c80943..4dc125f236b 100644 --- a/src/equations/nonideal_compressible_euler_2d.jl +++ b/src/equations/nonideal_compressible_euler_2d.jl @@ -654,6 +654,7 @@ end @inline function velocity(u, orientation::Int, equations::NonIdealCompressibleEulerEquations2D) + rho = u[1] if orientation == 1 v1 = u[2] / rho return v1 From a32c9dc9aa4d2f54b6d5a37efd01aae202b76efd Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Mon, 9 Feb 2026 23:23:09 -0600 Subject: [PATCH 241/360] allow for scalar function visualization using ScalarPlotData2D --- src/visualization/types.jl | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/src/visualization/types.jl b/src/visualization/types.jl index 3c9f0086f9f..f7550ef154c 100644 --- a/src/visualization/types.jl +++ b/src/visualization/types.jl @@ -444,10 +444,44 @@ end """ ScalarPlotData2D(u, semi::AbstractSemidiscretization; kwargs...) + ScalarPlotData2D(u, function_to_visualize, semi::AbstractSemidiscretization; kwargs...) Returns an `PlotData2DTriangulated` object which is used to visualize a single scalar field. `u` should be an array whose entries correspond to values of the scalar field at nodal points. + +The optional argument `function_to_visualize(u, equations)` should be a function which takes +in the conservative variables as input and outputs a scalar variable to be visualized. """ +function ScalarPlotData2D(u, function_to_visualize::Func, + semi::AbstractSemidiscretization; + kwargs...) where {Func} + return ScalarPlotData2D(evaluate_scalar_function_at_nodes(Trixi.wrap_array(u, semi), + function_to_visualize, + mesh_equations_solver_cache(semi)...), + mesh_equations_solver_cache(semi)...; kwargs...) +end + +function evaluate_scalar_function_at_nodes(u, function_to_visualize, mesh, equations, + dg::DGMulti, cache) + # for DGMulti solvers, eltype(u) should be SVector{nvariables(equations)}, so + # broadcasting `func_to_visualize` over the solution array will work. + return function_to_visualize.(u, equations) +end + +function evaluate_scalar_function_at_nodes(u, function_to_visualize, mesh, equations, + dg::DGSEM, cache) + + # `u` should be an array of size (nvariables, nnodes, nnodes, nelements) + f = zeros(eltype(u), nnodes(dg), nnodes(dg), nelements(dg, cache)) + for element in eachelement(dg, cache) + for j in eachnode(dg), i in eachnode(dg) + u_node = get_node_vars(u, equations, dg, i, j, element) + f[i, j, element] = function_to_visualize(u_node, equations) + end + end + return f +end + function ScalarPlotData2D(u, semi::AbstractSemidiscretization; kwargs...) return ScalarPlotData2D(u, mesh_equations_solver_cache(semi)...; kwargs...) end From 9a0f14834df292b94c8bdefcc97b6864ed8ccd1d Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Mon, 9 Feb 2026 23:23:12 -0600 Subject: [PATCH 242/360] add tests --- test/test_visualization.jl | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/test_visualization.jl b/test/test_visualization.jl index 7090c1dec32..16bb713da1c 100644 --- a/test/test_visualization.jl +++ b/test/test_visualization.jl @@ -104,10 +104,14 @@ test_examples_2d = Dict("TreeMesh" => ("tree_2d_dgsem", end scalar_data = StructArrays.component(u, 1) @trixi_test_nowarn Plots.plot(ScalarPlotData2D(scalar_data, semi)) + @trixi_test_nowarn Plots.plot(ScalarPlotData2D(u, (u, equations) -> u[1], + semi)) else cache = semi.cache x = view(cache.elements.node_coordinates, 1, :, :, :) @trixi_test_nowarn Plots.plot(ScalarPlotData2D(x, semi)) + @trixi_test_nowarn Plots.plot(ScalarPlotData2D(u, (u, equations) -> u[1], + semi)) end end From b006dc2553aa45f3c9dcaca87cd45230c952f1b0 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Mon, 9 Feb 2026 23:26:41 -0600 Subject: [PATCH 243/360] add NEWS.md entry --- NEWS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/NEWS.md b/NEWS.md index a22dd7facf6..6397620721d 100644 --- a/NEWS.md +++ b/NEWS.md @@ -23,6 +23,7 @@ for human readability. #### Added +- Added functionality to `ScalarPlotData2D` allowing visualization a field provided by a user-defined scalar function ([#2796]). - Added `PengRobinson` equation of state ([#2769]). ## Changes in the v0.14 lifecycle From aee486bbb0f6c9d0eaf6806df99c4b901b3a346d Mon Sep 17 00:00:00 2001 From: Jesse Chan <1156048+jlchan@users.noreply.github.com> Date: Tue, 10 Feb 2026 07:55:18 -0600 Subject: [PATCH 244/360] Update src/visualization/types.jl Co-authored-by: Daniel Doehring --- src/visualization/types.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/visualization/types.jl b/src/visualization/types.jl index f7550ef154c..38597ef366f 100644 --- a/src/visualization/types.jl +++ b/src/visualization/types.jl @@ -450,7 +450,7 @@ Returns an `PlotData2DTriangulated` object which is used to visualize a single s `u` should be an array whose entries correspond to values of the scalar field at nodal points. The optional argument `function_to_visualize(u, equations)` should be a function which takes -in the conservative variables as input and outputs a scalar variable to be visualized. +in the conservative variables and equations as input and outputs a scalar variable to be visualized. """ function ScalarPlotData2D(u, function_to_visualize::Func, semi::AbstractSemidiscretization; From 088309e90e6994c60ac7474e887a71e5be6aeafd Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Tue, 10 Feb 2026 08:01:23 -0600 Subject: [PATCH 245/360] fix test --- test/test_visualization.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/test_visualization.jl b/test/test_visualization.jl index 16bb713da1c..d1fc20a2392 100644 --- a/test/test_visualization.jl +++ b/test/test_visualization.jl @@ -110,7 +110,8 @@ test_examples_2d = Dict("TreeMesh" => ("tree_2d_dgsem", cache = semi.cache x = view(cache.elements.node_coordinates, 1, :, :, :) @trixi_test_nowarn Plots.plot(ScalarPlotData2D(x, semi)) - @trixi_test_nowarn Plots.plot(ScalarPlotData2D(u, (u, equations) -> u[1], + @trixi_test_nowarn Plots.plot(ScalarPlotData2D(sol.u[end], + (u, equations) -> u[1], semi)) end end From 5ecff5b9fa0cbe38454e4950628de725c0952d6f Mon Sep 17 00:00:00 2001 From: Jesse Chan <1156048+jlchan@users.noreply.github.com> Date: Tue, 10 Feb 2026 11:06:24 -0600 Subject: [PATCH 246/360] Update src/visualization/types.jl Co-authored-by: Hendrik Ranocha --- src/visualization/types.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/visualization/types.jl b/src/visualization/types.jl index 38597ef366f..f9f0cc638a8 100644 --- a/src/visualization/types.jl +++ b/src/visualization/types.jl @@ -450,7 +450,8 @@ Returns an `PlotData2DTriangulated` object which is used to visualize a single s `u` should be an array whose entries correspond to values of the scalar field at nodal points. The optional argument `function_to_visualize(u, equations)` should be a function which takes -in the conservative variables and equations as input and outputs a scalar variable to be visualized. +in the conservative variables and equations as input and outputs a scalar variable to be visualized, +e.g., [`pressure`](@ref) or [`density`](@ref) for the compressible Euler equations. """ function ScalarPlotData2D(u, function_to_visualize::Func, semi::AbstractSemidiscretization; From 77d53cefa864a582e52c0627bd886d0c6f0aa3f3 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Tue, 10 Feb 2026 11:33:48 -0600 Subject: [PATCH 247/360] reorder function args --- src/visualization/types.jl | 10 +++++----- test/test_visualization.jl | 14 +++++++------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/visualization/types.jl b/src/visualization/types.jl index f9f0cc638a8..d03c9b8c1e7 100644 --- a/src/visualization/types.jl +++ b/src/visualization/types.jl @@ -453,23 +453,23 @@ The optional argument `function_to_visualize(u, equations)` should be a function in the conservative variables and equations as input and outputs a scalar variable to be visualized, e.g., [`pressure`](@ref) or [`density`](@ref) for the compressible Euler equations. """ -function ScalarPlotData2D(u, function_to_visualize::Func, +function ScalarPlotData2D(function_to_visualize::Func, u, semi::AbstractSemidiscretization; kwargs...) where {Func} - return ScalarPlotData2D(evaluate_scalar_function_at_nodes(Trixi.wrap_array(u, semi), - function_to_visualize, + return ScalarPlotData2D(evaluate_scalar_function_at_nodes(function_to_visualize, + Trixi.wrap_array(u, semi), mesh_equations_solver_cache(semi)...), mesh_equations_solver_cache(semi)...; kwargs...) end -function evaluate_scalar_function_at_nodes(u, function_to_visualize, mesh, equations, +function evaluate_scalar_function_at_nodes(function_to_visualize, u, mesh, equations, dg::DGMulti, cache) # for DGMulti solvers, eltype(u) should be SVector{nvariables(equations)}, so # broadcasting `func_to_visualize` over the solution array will work. return function_to_visualize.(u, equations) end -function evaluate_scalar_function_at_nodes(u, function_to_visualize, mesh, equations, +function evaluate_scalar_function_at_nodes(function_to_visualize, u, dg::DGSEM, cache) # `u` should be an array of size (nvariables, nnodes, nnodes, nelements) diff --git a/test/test_visualization.jl b/test/test_visualization.jl index d1fc20a2392..f2b86431935 100644 --- a/test/test_visualization.jl +++ b/test/test_visualization.jl @@ -103,17 +103,17 @@ test_examples_2d = Dict("TreeMesh" => ("tree_2d_dgsem", u = sol.u[end] end scalar_data = StructArrays.component(u, 1) - @trixi_test_nowarn Plots.plot(ScalarPlotData2D(scalar_data, semi)) - @trixi_test_nowarn Plots.plot(ScalarPlotData2D(u, (u, equations) -> u[1], - semi)) else cache = semi.cache x = view(cache.elements.node_coordinates, 1, :, :, :) - @trixi_test_nowarn Plots.plot(ScalarPlotData2D(x, semi)) - @trixi_test_nowarn Plots.plot(ScalarPlotData2D(sol.u[end], - (u, equations) -> u[1], - semi)) + scalar_data = x end + @trixi_test_nowarn Plots.plot(ScalarPlotData2D(scalar_data, semi)) + @trixi_test_nowarn Plots.plot(ScalarPlotData2D((u, equations) -> u[1], + sol.u[end], semi)) + @test typeof(ScalarPlotData2D(scalar_data, semi)) == + typeof(ScalarPlotData2D((u, equations) -> u[1], + sol.u[end], semi)) end @testset "1D plot from 2D solution" begin From 77993b8255de54406a8d6e75fc4347acf777c430 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Tue, 10 Feb 2026 11:34:04 -0600 Subject: [PATCH 248/360] extend visualization to FDSBP --- src/visualization/types.jl | 6 ++++-- src/visualization/utilities.jl | 34 ++++++++++++++++++++++++++++------ 2 files changed, 32 insertions(+), 8 deletions(-) diff --git a/src/visualization/types.jl b/src/visualization/types.jl index d03c9b8c1e7..de5347f6e68 100644 --- a/src/visualization/types.jl +++ b/src/visualization/types.jl @@ -470,7 +470,8 @@ function evaluate_scalar_function_at_nodes(function_to_visualize, u, mesh, equat end function evaluate_scalar_function_at_nodes(function_to_visualize, u, - dg::DGSEM, cache) + mesh::AbstractMesh{2}, equations, + dg::Union{<:DGSEM, <:FDSBP}, cache) # `u` should be an array of size (nvariables, nnodes, nnodes, nelements) f = zeros(eltype(u), nnodes(dg), nnodes(dg), nelements(dg, cache)) @@ -514,7 +515,8 @@ function ScalarPlotData2D(u, mesh, equations, dg::DGMulti, cache; x_face, y_face, face_data, variable_name) end -function ScalarPlotData2D(u, mesh, equations, dg::DGSEM, cache; variable_name = nothing, +function ScalarPlotData2D(u, mesh, equations, dg::Union{<:DGSEM, <:FDSBP}, cache; + variable_name = nothing, nvisnodes = 2 * nnodes(dg)) n_nodes_2d = nnodes(dg)^ndims(mesh) n_elements = nelements(dg, cache) diff --git a/src/visualization/utilities.jl b/src/visualization/utilities.jl index b65cce1756d..329388a63c4 100644 --- a/src/visualization/utilities.jl +++ b/src/visualization/utilities.jl @@ -89,11 +89,13 @@ function global_plotting_triangulation_triplot(xyz_plot, u_plot, t) return vec.(xyz_plot)..., zp, tp end -function get_face_node_indices(r, s, dg::DGSEM, tol = 100 * eps()) - face_1 = findall(@. abs(s + 1) < tol) - face_2 = findall(@. abs(r - 1) < tol) - face_3 = findall(@. abs(s - 1) < tol) - face_4 = findall(@. abs(r + 1) < tol) +function get_face_node_indices(r, s, dg::Union{<:DGSEM, <:FDSBP}, tol = 100 * eps()) + r_max, r_min = extrema(r) + s_max, s_min = extrema(s) + face_1 = findall(@. abs(s - s_min) < tol) + face_2 = findall(@. abs(r - r_max) < tol) + face_3 = findall(@. abs(s - s_max) < tol) + face_4 = findall(@. abs(r - r_min) < tol) Fmask = hcat(face_1, face_2, face_3, face_4) return Fmask end @@ -180,7 +182,8 @@ function mesh_plotting_wireframe(u::StructArray, mesh, equations, dg::DGSEM, cac return xfp, yfp, ufp end -function mesh_plotting_wireframe(u::ScalarData, mesh, equations, dg::DGSEM, cache; +function mesh_plotting_wireframe(u::ScalarData, mesh, equations, + dg::Union{<:DGSEM, <:FDSBP}, cache; nvisnodes = 2 * nnodes(dg)) # build nodes on reference element (seems to be the right ordering) @@ -1674,6 +1677,25 @@ function reference_node_coordinates_2d(dg::DGSEM) return r, s end +function reference_node_coordinates_2d(dg::FDSBP) + nodes = dg.basis.grid + r = vec([nodes[i] for i in eachnode(dg), j in eachnode(dg)]) + s = vec([nodes[j] for i in eachnode(dg), j in eachnode(dg)]) + return r, s +end + +function plotting_interpolation_matrix(dg::FDSBP; kwargs...) + # Typically, DGSEM interpolates LGL nodes to a finer set of uniformly spaced points. + # However, since FDSBP already has equally spaced nodes, we skip this step + return I +end + +function face_plotting_interpolation_matrix(dg::FDSBP; kwargs...) + # Typically, DGSEM interpolates LGL nodes to a finer set of uniformly spaced points. + # However, since FDSBP already has equally spaced nodes, we skip this step + return I +end + # Find element and triangle ids containing coordinates given as a matrix [ndims, npoints] function get_ids_by_coordinates!(ids, coordinates, pd) if length(ids) != 2 * size(coordinates, 2) From cf55222810fad9ee2582711a71d97efa4a0b3a4d Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Tue, 10 Feb 2026 11:34:09 -0600 Subject: [PATCH 249/360] add FDSBP vis test --- test/test_visualization.jl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/test_visualization.jl b/test/test_visualization.jl index f2b86431935..a4c7ee3bdd2 100644 --- a/test/test_visualization.jl +++ b/test/test_visualization.jl @@ -24,6 +24,8 @@ isdir(outdir) && rm(outdir, recursive = true) # Run 2D tests with elixirs for all mesh types test_examples_2d = Dict("TreeMesh" => ("tree_2d_dgsem", "elixir_euler_blast_wave_amr.jl"), + "TreeMesh (FDSBP)" => ("tree_2d_fdsbp", + "elixir_advection_extended.jl"), "StructuredMesh" => ("structured_2d_dgsem", "elixir_euler_source_terms_waving_flag.jl"), "UnstructuredMesh" => ("unstructured_2d_dgsem", From 2645da000c84064a573d2b61e9f9ae4c516b2774 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Tue, 10 Feb 2026 11:41:50 -0600 Subject: [PATCH 250/360] expand consistency test --- test/test_visualization.jl | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/test/test_visualization.jl b/test/test_visualization.jl index a4c7ee3bdd2..e9f1bc665ff 100644 --- a/test/test_visualization.jl +++ b/test/test_visualization.jl @@ -107,15 +107,28 @@ test_examples_2d = Dict("TreeMesh" => ("tree_2d_dgsem", scalar_data = StructArrays.component(u, 1) else cache = semi.cache - x = view(cache.elements.node_coordinates, 1, :, :, :) - scalar_data = x + scalar_data = u[1, ..] end @trixi_test_nowarn Plots.plot(ScalarPlotData2D(scalar_data, semi)) @trixi_test_nowarn Plots.plot(ScalarPlotData2D((u, equations) -> u[1], sol.u[end], semi)) - @test typeof(ScalarPlotData2D(scalar_data, semi)) == - typeof(ScalarPlotData2D((u, equations) -> u[1], - sol.u[end], semi)) + + # test for consistency between the two ScalarPlotData2D constructions + spd_no_function = ScalarPlotData2D(scalar_data, semi) + spd_function = ScalarPlotData2D((u, equations) -> u[1], + sol.u[end], semi) + @test typeof(spd_no_function) == typeof(spd_function) + for property in propertynames(spd_function) + if property == :data + @test spd_no_function.data.data ≈ spd_function.data.data + elseif property == :variable_names + @test getproperty(spd_no_function, property) == + getproperty(spd_function, property) + else + @test getproperty(spd_no_function, property) ≈ + getproperty(spd_function, property) + end + end end @testset "1D plot from 2D solution" begin From b0fe1bbb9e6f02399f187c2f6f6c44f8338c6295 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Tue, 10 Feb 2026 12:02:54 -0600 Subject: [PATCH 251/360] fix FDSBP vis test --- test/test_visualization.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_visualization.jl b/test/test_visualization.jl index e9f1bc665ff..23a937d496d 100644 --- a/test/test_visualization.jl +++ b/test/test_visualization.jl @@ -25,7 +25,7 @@ isdir(outdir) && rm(outdir, recursive = true) test_examples_2d = Dict("TreeMesh" => ("tree_2d_dgsem", "elixir_euler_blast_wave_amr.jl"), "TreeMesh (FDSBP)" => ("tree_2d_fdsbp", - "elixir_advection_extended.jl"), + "elixir_euler_convergence.jl"), "StructuredMesh" => ("structured_2d_dgsem", "elixir_euler_source_terms_waving_flag.jl"), "UnstructuredMesh" => ("unstructured_2d_dgsem", @@ -41,7 +41,7 @@ test_examples_2d = Dict("TreeMesh" => ("tree_2d_dgsem", tspan=(0, 0.1)) # Constructor tests - if mesh == "TreeMesh" + if mesh isa TreeMesh @test PlotData2D(sol) isa Trixi.PlotData2DCartesian @test PlotData2D(sol; nvisnodes = 0, grid_lines = false, solution_variables = cons2cons) isa Trixi.PlotData2DCartesian From 51a7a80475d78cf2d3a74dc0f633825a56ed524d Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Tue, 10 Feb 2026 13:04:16 -0600 Subject: [PATCH 252/360] add transcritical mixing test of wall BCs --- ...uler_peng_robinson_transcritical_mixing.jl | 3 +-- test/test_tree_2d_euler.jl | 22 +++++++++++++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/examples/tree_2d_dgsem/elixir_euler_peng_robinson_transcritical_mixing.jl b/examples/tree_2d_dgsem/elixir_euler_peng_robinson_transcritical_mixing.jl index 1c938c8208a..8aa9d14475b 100644 --- a/examples/tree_2d_dgsem/elixir_euler_peng_robinson_transcritical_mixing.jl +++ b/examples/tree_2d_dgsem/elixir_euler_peng_robinson_transcritical_mixing.jl @@ -26,8 +26,7 @@ function initial_condition_transcritical_mixing(x, t, # from Bernades et al epsilon, delta, A = 1.0, 1 / 20, 3 / 8 u0 = 25 # m/s - Tc = eos.T0 # this value is 126.2 for N2 - T = Tc * (3 * A - A * tanh(y / delta)) + T = eos.Tc * (3 * A - A * tanh(y / delta)) # Tc is 126.2 for N2 tol = Trixi.eos_newton_tol(eos) diff --git a/test/test_tree_2d_euler.jl b/test/test_tree_2d_euler.jl index cc480e29922..a0c9f7e4154 100644 --- a/test/test_tree_2d_euler.jl +++ b/test/test_tree_2d_euler.jl @@ -150,6 +150,28 @@ end @test_allocations(Trixi.rhs!, semi, sol, 1000) end +@trixi_testset "elixir_euler_peng_robinson_transcritical_mixing" begin + @test_trixi_include(joinpath(EXAMPLES_DIR, + "elixir_euler_peng_robinson_transcritical_mixing.jl"), + tspan=(0.0, 0.0003), + # note that errors are large because the solution values are O(1e5)-O(1e7) + l2=[ + 0.8908754595982343, + 274.62878855174165, + 129.95856100796416, + 94433.86273840266 + ], + linf=[ + 6.623271612803023, + 732.0924019701561, + 403.7976140940856, + 584547.9740598286 + ]) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + @test_allocations(Trixi.rhs!, semi, sol, 1000) +end + @trixi_testset "elixir_euler_source_terms_nonperiodic.jl" begin @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_source_terms_nonperiodic.jl"), From d415a3af524b81e43b47c35f74dec9c1f6adaae7 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Tue, 10 Feb 2026 13:12:53 -0600 Subject: [PATCH 253/360] fix tests --- test/test_visualization.jl | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/test_visualization.jl b/test/test_visualization.jl index 23a937d496d..d431014b899 100644 --- a/test/test_visualization.jl +++ b/test/test_visualization.jl @@ -45,7 +45,9 @@ test_examples_2d = Dict("TreeMesh" => ("tree_2d_dgsem", @test PlotData2D(sol) isa Trixi.PlotData2DCartesian @test PlotData2D(sol; nvisnodes = 0, grid_lines = false, solution_variables = cons2cons) isa Trixi.PlotData2DCartesian - @test Trixi.PlotData2DTriangulated(sol) isa Trixi.PlotData2DTriangulated + if semi.solver isa DGSEM + @test Trixi.PlotData2DTriangulated(sol) isa Trixi.PlotData2DTriangulated + end else @test PlotData2D(sol) isa Trixi.PlotData2DTriangulated @test PlotData2D(sol; nvisnodes = 0, solution_variables = cons2cons) isa @@ -107,6 +109,7 @@ test_examples_2d = Dict("TreeMesh" => ("tree_2d_dgsem", scalar_data = StructArrays.component(u, 1) else cache = semi.cache + u = Trixi.wrap_array(sol.u[end], semi) scalar_data = u[1, ..] end @trixi_test_nowarn Plots.plot(ScalarPlotData2D(scalar_data, semi)) From c092089fb216ea6c81ac1da6d13f40786c5e26a4 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Tue, 10 Feb 2026 13:28:03 -0600 Subject: [PATCH 254/360] update modified sod with entropy correction test --- test/test_tree_1d_euler.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_tree_1d_euler.jl b/test/test_tree_1d_euler.jl index 4286a378d8c..e5994870541 100644 --- a/test/test_tree_1d_euler.jl +++ b/test/test_tree_1d_euler.jl @@ -663,8 +663,8 @@ end @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_modified_sod_entropy_correction.jl"), tspan=(0.0, 0.1), - l2=[0.17918606870048043, 0.3021079622668064, 0.591922971435801], - linf=[0.6787210684791858, 0.8094457932998395, 1.9399759521796582]) + l2=[0.17918607435161274, 0.3021079725486445, 0.5919229917120694], + linf=[0.6787210671799078, 0.8094457929285299, 1.9399759495474393]) # Ensure that we do not have excessive memory allocations # (e.g., from type instabilities) From 72186751878828ce7fd6ffb476f7ac52e02d0a7e Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Tue, 10 Feb 2026 13:36:27 -0600 Subject: [PATCH 255/360] fix u[1, ..] -> u[1, :, :, :] --- test/test_visualization.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_visualization.jl b/test/test_visualization.jl index d431014b899..7143c4d3255 100644 --- a/test/test_visualization.jl +++ b/test/test_visualization.jl @@ -110,7 +110,7 @@ test_examples_2d = Dict("TreeMesh" => ("tree_2d_dgsem", else cache = semi.cache u = Trixi.wrap_array(sol.u[end], semi) - scalar_data = u[1, ..] + scalar_data = u[1, :, :, :] end @trixi_test_nowarn Plots.plot(ScalarPlotData2D(scalar_data, semi)) @trixi_test_nowarn Plots.plot(ScalarPlotData2D((u, equations) -> u[1], From ce6c407e83bac8e56907c9144bd139cfc5eddaa0 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Tue, 10 Feb 2026 13:52:57 -0600 Subject: [PATCH 256/360] Revert "fix u[1, ..] -> u[1, :, :, :]" This reverts commit 72186751878828ce7fd6ffb476f7ac52e02d0a7e. --- test/test_visualization.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_visualization.jl b/test/test_visualization.jl index 7143c4d3255..d431014b899 100644 --- a/test/test_visualization.jl +++ b/test/test_visualization.jl @@ -110,7 +110,7 @@ test_examples_2d = Dict("TreeMesh" => ("tree_2d_dgsem", else cache = semi.cache u = Trixi.wrap_array(sol.u[end], semi) - scalar_data = u[1, :, :, :] + scalar_data = u[1, ..] end @trixi_test_nowarn Plots.plot(ScalarPlotData2D(scalar_data, semi)) @trixi_test_nowarn Plots.plot(ScalarPlotData2D((u, equations) -> u[1], From 13576ce0688fd8aabcf55b5dc8f8a5002a5286bc Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Tue, 10 Feb 2026 13:53:58 -0600 Subject: [PATCH 257/360] fix u[1, ..] -> u[1, :, :, :] --- test/test_visualization.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_visualization.jl b/test/test_visualization.jl index d431014b899..7143c4d3255 100644 --- a/test/test_visualization.jl +++ b/test/test_visualization.jl @@ -110,7 +110,7 @@ test_examples_2d = Dict("TreeMesh" => ("tree_2d_dgsem", else cache = semi.cache u = Trixi.wrap_array(sol.u[end], semi) - scalar_data = u[1, ..] + scalar_data = u[1, :, :, :] end @trixi_test_nowarn Plots.plot(ScalarPlotData2D(scalar_data, semi)) @trixi_test_nowarn Plots.plot(ScalarPlotData2D((u, equations) -> u[1], From bf0ba9cb829a8ca236b46896dba26849c719a6f9 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Tue, 10 Feb 2026 13:56:31 -0600 Subject: [PATCH 258/360] print out directory and elixir to diagnose CI failures --- test/test_visualization.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/test/test_visualization.jl b/test/test_visualization.jl index 7143c4d3255..a6f67be6b82 100644 --- a/test/test_visualization.jl +++ b/test/test_visualization.jl @@ -49,6 +49,7 @@ test_examples_2d = Dict("TreeMesh" => ("tree_2d_dgsem", @test Trixi.PlotData2DTriangulated(sol) isa Trixi.PlotData2DTriangulated end else + @show directory, elixir @test PlotData2D(sol) isa Trixi.PlotData2DTriangulated @test PlotData2D(sol; nvisnodes = 0, solution_variables = cons2cons) isa Trixi.PlotData2DTriangulated From 2f1730d2e699c7f672e4f1db3a38bd32564d42ed Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Tue, 10 Feb 2026 14:31:24 -0600 Subject: [PATCH 259/360] fix cons2prim -> cons2thermo --- src/equations/nonideal_compressible_euler_1d.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/equations/nonideal_compressible_euler_1d.jl b/src/equations/nonideal_compressible_euler_1d.jl index 161c77ab62b..316507ecec0 100644 --- a/src/equations/nonideal_compressible_euler_1d.jl +++ b/src/equations/nonideal_compressible_euler_1d.jl @@ -327,7 +327,7 @@ EOS is ``p v_i / T`` for the choice of entropy ``S(u) = \rho s``. @inline function entropy_potential(u, orientation::Int, equations::NonIdealCompressibleEulerEquations1D) eos = equations.equation_of_state - V, v1, T = cons2prim(u, equations) + V, v1, T = cons2thermo(u, equations) p = pressure(V, T, eos) return p * v1 / T end From a19b444b0a7572661e6a566e63844ab5a36b382f Mon Sep 17 00:00:00 2001 From: Jesse Chan <1156048+jlchan@users.noreply.github.com> Date: Tue, 10 Feb 2026 14:54:48 -0600 Subject: [PATCH 260/360] Apply suggestions from code review Co-authored-by: Hendrik Ranocha --- src/visualization/types.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/visualization/types.jl b/src/visualization/types.jl index de5347f6e68..2170dbd5b00 100644 --- a/src/visualization/types.jl +++ b/src/visualization/types.jl @@ -444,9 +444,9 @@ end """ ScalarPlotData2D(u, semi::AbstractSemidiscretization; kwargs...) - ScalarPlotData2D(u, function_to_visualize, semi::AbstractSemidiscretization; kwargs...) + ScalarPlotData2D(function_to_visualize, u, semi::AbstractSemidiscretization; kwargs...) -Returns an `PlotData2DTriangulated` object which is used to visualize a single scalar field. +Returns a `PlotData2DTriangulated` object which is used to visualize a single scalar field. `u` should be an array whose entries correspond to values of the scalar field at nodal points. The optional argument `function_to_visualize(u, equations)` should be a function which takes @@ -475,7 +475,7 @@ function evaluate_scalar_function_at_nodes(function_to_visualize, u, # `u` should be an array of size (nvariables, nnodes, nnodes, nelements) f = zeros(eltype(u), nnodes(dg), nnodes(dg), nelements(dg, cache)) - for element in eachelement(dg, cache) + @threaded for element in eachelement(dg, cache) for j in eachnode(dg), i in eachnode(dg) u_node = get_node_vars(u, equations, dg, i, j, element) f[i, j, element] = function_to_visualize(u_node, equations) From 934130f92e457cb536ba1b72c8c3c22e765b68b8 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Tue, 10 Feb 2026 14:57:18 -0600 Subject: [PATCH 261/360] update NEWS.md --- NEWS.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index 6397620721d..98057ba1de3 100644 --- a/NEWS.md +++ b/NEWS.md @@ -5,6 +5,10 @@ Trixi.jl follows the interpretation of used in the Julia ecosystem. Notable changes will be documented in this file for human readability. +## Changes in the v0.15 lifecycle + +- Added functionality to `ScalarPlotData2D` allowing visualization a field provided by a user-defined scalar function ([#2796]). + ## Changes when updating to v0.15 from v0.14.x #### Changed @@ -23,7 +27,6 @@ for human readability. #### Added -- Added functionality to `ScalarPlotData2D` allowing visualization a field provided by a user-defined scalar function ([#2796]). - Added `PengRobinson` equation of state ([#2769]). ## Changes in the v0.14 lifecycle From 2110f31f2d8d516be33d2a8579ab978d35ac8993 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Tue, 10 Feb 2026 18:53:05 -0600 Subject: [PATCH 262/360] more test fixes --- src/visualization/utilities.jl | 7 +++++++ test/test_visualization.jl | 7 ++++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/visualization/utilities.jl b/src/visualization/utilities.jl index 329388a63c4..2adffe2591b 100644 --- a/src/visualization/utilities.jl +++ b/src/visualization/utilities.jl @@ -1684,6 +1684,13 @@ function reference_node_coordinates_2d(dg::FDSBP) return r, s end +function reference_node_coordinates_2d(dg::DG{<:SummationByPartsOperators.UpwindOperators}) + nodes = dg.basis.central.grid + r = vec([nodes[i] for i in eachnode(dg), j in eachnode(dg)]) + s = vec([nodes[j] for i in eachnode(dg), j in eachnode(dg)]) + return r, s +end + function plotting_interpolation_matrix(dg::FDSBP; kwargs...) # Typically, DGSEM interpolates LGL nodes to a finer set of uniformly spaced points. # However, since FDSBP already has equally spaced nodes, we skip this step diff --git a/test/test_visualization.jl b/test/test_visualization.jl index a6f67be6b82..329e4b33b12 100644 --- a/test/test_visualization.jl +++ b/test/test_visualization.jl @@ -49,7 +49,7 @@ test_examples_2d = Dict("TreeMesh" => ("tree_2d_dgsem", @test Trixi.PlotData2DTriangulated(sol) isa Trixi.PlotData2DTriangulated end else - @show directory, elixir + @show typeof(mesh), directory, elixir @test PlotData2D(sol) isa Trixi.PlotData2DTriangulated @test PlotData2D(sol; nvisnodes = 0, solution_variables = cons2cons) isa Trixi.PlotData2DTriangulated @@ -109,7 +109,6 @@ test_examples_2d = Dict("TreeMesh" => ("tree_2d_dgsem", end scalar_data = StructArrays.component(u, 1) else - cache = semi.cache u = Trixi.wrap_array(sol.u[end], semi) scalar_data = u[1, :, :, :] end @@ -124,7 +123,9 @@ test_examples_2d = Dict("TreeMesh" => ("tree_2d_dgsem", @test typeof(spd_no_function) == typeof(spd_function) for property in propertynames(spd_function) if property == :data - @test spd_no_function.data.data ≈ spd_function.data.data + # test that scalar plotting data is the same up to ordering + @test sort(vec(spd_no_function.data.data)) ≈ + sort(vec(spd_function.data.data)) elseif property == :variable_names @test getproperty(spd_no_function, property) == getproperty(spd_function, property) From 993193daef6447af3e36cfa7b689aadc7e4d5dda Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Tue, 10 Feb 2026 19:22:09 -0600 Subject: [PATCH 263/360] try to fix tests again --- test/test_visualization.jl | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/test/test_visualization.jl b/test/test_visualization.jl index 329e4b33b12..88478333e42 100644 --- a/test/test_visualization.jl +++ b/test/test_visualization.jl @@ -41,7 +41,7 @@ test_examples_2d = Dict("TreeMesh" => ("tree_2d_dgsem", tspan=(0, 0.1)) # Constructor tests - if mesh isa TreeMesh + if mesh == "TreeMesh" || mesh == "TreeMesh (FDSBP)" @test PlotData2D(sol) isa Trixi.PlotData2DCartesian @test PlotData2D(sol; nvisnodes = 0, grid_lines = false, solution_variables = cons2cons) isa Trixi.PlotData2DCartesian @@ -49,7 +49,6 @@ test_examples_2d = Dict("TreeMesh" => ("tree_2d_dgsem", @test Trixi.PlotData2DTriangulated(sol) isa Trixi.PlotData2DTriangulated end else - @show typeof(mesh), directory, elixir @test PlotData2D(sol) isa Trixi.PlotData2DTriangulated @test PlotData2D(sol; nvisnodes = 0, solution_variables = cons2cons) isa Trixi.PlotData2DTriangulated @@ -117,21 +116,23 @@ test_examples_2d = Dict("TreeMesh" => ("tree_2d_dgsem", sol.u[end], semi)) # test for consistency between the two ScalarPlotData2D constructions - spd_no_function = ScalarPlotData2D(scalar_data, semi) - spd_function = ScalarPlotData2D((u, equations) -> u[1], - sol.u[end], semi) - @test typeof(spd_no_function) == typeof(spd_function) - for property in propertynames(spd_function) - if property == :data - # test that scalar plotting data is the same up to ordering - @test sort(vec(spd_no_function.data.data)) ≈ - sort(vec(spd_function.data.data)) - elseif property == :variable_names - @test getproperty(spd_no_function, property) == - getproperty(spd_function, property) - else - @test getproperty(spd_no_function, property) ≈ - getproperty(spd_function, property) + if mesh == "TreeMesh" || mesh == "TreeMesh (FDSBP)" || mesh == "DGMulti" + spd_no_function = ScalarPlotData2D(scalar_data, semi) + spd_function = ScalarPlotData2D((u, equations) -> u[1], + sol.u[end], semi) + @test typeof(spd_no_function) == typeof(spd_function) + for property in propertynames(spd_function) + if property == :data + # test that scalar plotting data is the same up to ordering + @test sort(vec(spd_no_function.data.data)) ≈ + sort(vec(spd_function.data.data)) + elseif property == :variable_names + @test getproperty(spd_no_function, property) == + getproperty(spd_function, property) + else + @test getproperty(spd_no_function, property) ≈ + getproperty(spd_function, property) + end end end end From 116315b4710b8fc922da24c21e4a1509fc46e5d7 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Tue, 10 Feb 2026 20:13:41 -0600 Subject: [PATCH 264/360] use clearer function argument names --- src/visualization/types.jl | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/visualization/types.jl b/src/visualization/types.jl index 2170dbd5b00..5c09d011b8f 100644 --- a/src/visualization/types.jl +++ b/src/visualization/types.jl @@ -456,12 +456,18 @@ e.g., [`pressure`](@ref) or [`density`](@ref) for the compressible Euler equatio function ScalarPlotData2D(function_to_visualize::Func, u, semi::AbstractSemidiscretization; kwargs...) where {Func} - return ScalarPlotData2D(evaluate_scalar_function_at_nodes(function_to_visualize, - Trixi.wrap_array(u, semi), - mesh_equations_solver_cache(semi)...), + scalar_data = evaluate_scalar_function_at_nodes(function_to_visualize, + wrap_array(u, semi), + mesh_equations_solver_cache(semi)...) + return ScalarPlotData2D(scalar_data, mesh_equations_solver_cache(semi)...; kwargs...) end +function ScalarPlotData2D(scalar_data, semi::AbstractSemidiscretization; kwargs...) + return ScalarPlotData2D(scalar_data, mesh_equations_solver_cache(semi)...; + kwargs...) +end + function evaluate_scalar_function_at_nodes(function_to_visualize, u, mesh, equations, dg::DGMulti, cache) # for DGMulti solvers, eltype(u) should be SVector{nvariables(equations)}, so @@ -484,10 +490,6 @@ function evaluate_scalar_function_at_nodes(function_to_visualize, u, return f end -function ScalarPlotData2D(u, semi::AbstractSemidiscretization; kwargs...) - return ScalarPlotData2D(u, mesh_equations_solver_cache(semi)...; kwargs...) -end - # Returns an `PlotData2DTriangulated` which is used to visualize a single scalar field function ScalarPlotData2D(u, mesh, equations, dg::DGMulti, cache; variable_name = nothing, nvisnodes = 2 * nnodes(dg)) From ff5924d20dad79d9d4ab7dce3f1e8e174e21031b Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Tue, 10 Feb 2026 20:14:09 -0600 Subject: [PATCH 265/360] fix tests again --- test/test_visualization.jl | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/test/test_visualization.jl b/test/test_visualization.jl index 88478333e42..4dfe76f3a80 100644 --- a/test/test_visualization.jl +++ b/test/test_visualization.jl @@ -109,7 +109,7 @@ test_examples_2d = Dict("TreeMesh" => ("tree_2d_dgsem", scalar_data = StructArrays.component(u, 1) else u = Trixi.wrap_array(sol.u[end], semi) - scalar_data = u[1, :, :, :] + scalar_data = Array(u[1, :, :, :]) end @trixi_test_nowarn Plots.plot(ScalarPlotData2D(scalar_data, semi)) @trixi_test_nowarn Plots.plot(ScalarPlotData2D((u, equations) -> u[1], @@ -123,9 +123,7 @@ test_examples_2d = Dict("TreeMesh" => ("tree_2d_dgsem", @test typeof(spd_no_function) == typeof(spd_function) for property in propertynames(spd_function) if property == :data - # test that scalar plotting data is the same up to ordering - @test sort(vec(spd_no_function.data.data)) ≈ - sort(vec(spd_function.data.data)) + @test spd_no_function.data.data ≈ spd_function.data.data elseif property == :variable_names @test getproperty(spd_no_function, property) == getproperty(spd_function, property) From 6b0c511f4b3efa9d1b61e8da7df5ce0c92ad792f Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Tue, 10 Feb 2026 21:32:02 -0600 Subject: [PATCH 266/360] add one more test to improve coverage --- test/test_visualization.jl | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/test/test_visualization.jl b/test/test_visualization.jl index 4dfe76f3a80..407a7ac979f 100644 --- a/test/test_visualization.jl +++ b/test/test_visualization.jl @@ -21,7 +21,7 @@ isdir(outdir) && rm(outdir, recursive = true) @testset "Visualization tests" begin #! format: noindent -# Run 2D tests with elixirs for all mesh types +# Run 2D tests with elixirs for different mesh and solver types test_examples_2d = Dict("TreeMesh" => ("tree_2d_dgsem", "elixir_euler_blast_wave_amr.jl"), "TreeMesh (FDSBP)" => ("tree_2d_fdsbp", @@ -133,6 +133,15 @@ test_examples_2d = Dict("TreeMesh" => ("tree_2d_dgsem", end end end + + # test that non-upwinded FDSBP functionality is consistent with upwinded FDSBP solver behavior + if mesh == "TreeMesh (FDSBP)" + (; volume_integral, surface_integral) = semi.solver + solver_fdsbp = FDSBP(semi.solver.basis.central; surface_integral, + volume_integral) + @test all(Trixi.reference_node_coordinates_2d(semi.solver) .≈ + Trixi.reference_node_coordinates_2d(solver_fdsbp)) + end end @testset "1D plot from 2D solution" begin From 515b1c53c0f1e5a17a2d41942a519f2957065bd2 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Tue, 10 Feb 2026 21:36:39 -0600 Subject: [PATCH 267/360] move initial condition into elixir --- .../elixir_euler_nonideal_density_wave.jl | 33 ++++++++++++++++++ .../nonideal_compressible_euler_2d.jl | 34 ------------------- 2 files changed, 33 insertions(+), 34 deletions(-) diff --git a/examples/tree_2d_dgsem/elixir_euler_nonideal_density_wave.jl b/examples/tree_2d_dgsem/elixir_euler_nonideal_density_wave.jl index 4d92d3841ae..f625e16155c 100644 --- a/examples/tree_2d_dgsem/elixir_euler_nonideal_density_wave.jl +++ b/examples/tree_2d_dgsem/elixir_euler_nonideal_density_wave.jl @@ -8,6 +8,39 @@ using Trixi: ForwardDiff eos = VanDerWaals(; a = 10, b = 1e-2, gamma = 1.4, R = 287) equations = NonIdealCompressibleEulerEquations2D(eos) +# the default amplitude and frequency k are chosen to be consistent with +# initial_condition_density_wave for CompressibleEulerEquations1D +function Trixi.initial_condition_density_wave(x, t, + equations::NonIdealCompressibleEulerEquations2D; + amplitude = 0.98, k = 2) + RealT = eltype(x) + + eos = equations.equation_of_state + + v1 = convert(RealT, 0.1) + v2 = convert(RealT, 0.2) + rho = 1 + convert(RealT, amplitude) * sinpi(k * (x[1] + x[2] - t * (v1 + v2))) + p = 20 + + V = inv(rho) + + # invert for temperature given p, V + T = 1 + tol = 100 * eps(RealT) + dp = pressure(V, T, eos) - p + iter = 1 + while abs(dp) / abs(p) > tol && iter < 100 + dp = pressure(V, T, eos) - p + dpdT_V = ForwardDiff.derivative(T -> pressure(V, T, eos), T) + T = max(tol, T - dp / dpdT_V) + iter += 1 + end + if iter == 100 + println("Warning: solver for temperature(V, p) did not converge") + end + + return prim2cons(SVector(V, v1, v2, T), equations) +end initial_condition = initial_condition_density_wave volume_flux = flux_terashima_etal diff --git a/src/equations/nonideal_compressible_euler_2d.jl b/src/equations/nonideal_compressible_euler_2d.jl index 4dc125f236b..a41982163c6 100644 --- a/src/equations/nonideal_compressible_euler_2d.jl +++ b/src/equations/nonideal_compressible_euler_2d.jl @@ -110,40 +110,6 @@ end return SVector(f1, f2, f3, f4) end -# the default amplitude and frequency k are chosen to be consistent with -# initial_condition_density_wave for CompressibleEulerEquations1D -function initial_condition_density_wave(x, t, - equations::NonIdealCompressibleEulerEquations2D; - amplitude = 0.98, k = 2) - RealT = eltype(x) - - eos = equations.equation_of_state - - v1 = convert(RealT, 0.1) - v2 = convert(RealT, 0.2) - rho = 1 + convert(RealT, amplitude) * sinpi(k * (x[1] + x[2] - t * (v1 + v2))) - p = 20 - - V = inv(rho) - - # invert for temperature given p, V - T = 1 - tol = 100 * eps(RealT) - dp = pressure(V, T, eos) - p - iter = 1 - while abs(dp) / abs(p) > tol && iter < 100 - dp = pressure(V, T, eos) - p - dpdT_V = ForwardDiff.derivative(T -> pressure(V, T, eos), T) - T = max(tol, T - dp / dpdT_V) - iter += 1 - end - if iter == 100 - println("Warning: solver for temperature(V, p) did not converge") - end - - return prim2cons(SVector(V, v1, v2, T), equations) -end - """ boundary_condition_slip_wall(u_inner, orientation, direction, x, t, surface_flux_function, equations::NonIdealCompressibleEulerEquations2D) From a8ae12ed712d1cf8d6d18648022f0fad5378ec8e Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Tue, 10 Feb 2026 22:52:15 -0600 Subject: [PATCH 268/360] test slip wall BCs --- test/test_unit.jl | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/test/test_unit.jl b/test/test_unit.jl index a56ee64c44f..573af9c8831 100644 --- a/test/test_unit.jl +++ b/test/test_unit.jl @@ -876,6 +876,17 @@ end _flux_function(u_ll, u_rr, SVector(0, 1), equations)) end + # check consistency of slip wall boundary conditions + for orientation in [1, 2] + x, t = 0, 0 + direction = 1 # this variable is not used in `boundary_condition_slip_wall` + normal_direction = orientation == 1 ? SVector(1.0, 0.0) : SVector(0.0, 1.0) + @test boundary_condition_slip_wall(u, orientation, direction, x, t, + flux_lax_friedrichs, equations) ≈ + boundary_condition_slip_wall(u, normal_direction, x, t, + flux_lax_friedrichs, equations) + end + # check that the fallback temperature and specialized temperature # return the same value V, v1, v2, T = cons2prim(u, equations) From 4864c7cd5c4f323289a5cdb5fbc3c27bbc76af8d Mon Sep 17 00:00:00 2001 From: Jesse Chan <1156048+jlchan@users.noreply.github.com> Date: Wed, 11 Feb 2026 08:36:48 -0600 Subject: [PATCH 269/360] Apply suggestions from code review Co-authored-by: Hendrik Ranocha --- src/visualization/types.jl | 5 +++-- test/test_visualization.jl | 7 +++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/visualization/types.jl b/src/visualization/types.jl index 5c09d011b8f..7263e2be36b 100644 --- a/src/visualization/types.jl +++ b/src/visualization/types.jl @@ -453,11 +453,12 @@ The optional argument `function_to_visualize(u, equations)` should be a function in the conservative variables and equations as input and outputs a scalar variable to be visualized, e.g., [`pressure`](@ref) or [`density`](@ref) for the compressible Euler equations. """ -function ScalarPlotData2D(function_to_visualize::Func, u, +function ScalarPlotData2D(function_to_visualize::Func, u_ode, semi::AbstractSemidiscretization; kwargs...) where {Func} + u = wrap_array(u_ode, semi) scalar_data = evaluate_scalar_function_at_nodes(function_to_visualize, - wrap_array(u, semi), + u, mesh_equations_solver_cache(semi)...) return ScalarPlotData2D(scalar_data, mesh_equations_solver_cache(semi)...; kwargs...) diff --git a/test/test_visualization.jl b/test/test_visualization.jl index 407a7ac979f..a5ac5b09f0d 100644 --- a/test/test_visualization.jl +++ b/test/test_visualization.jl @@ -108,8 +108,11 @@ test_examples_2d = Dict("TreeMesh" => ("tree_2d_dgsem", end scalar_data = StructArrays.component(u, 1) else - u = Trixi.wrap_array(sol.u[end], semi) - scalar_data = Array(u[1, :, :, :]) + # There are some issues with `PtrArray`s returned by default + # by `Trixi.wrap_array`, see + # https://github.com/trixi-framework/Trixi.jl/issues/2797 + u = Trixi.wrap_array_native(sol.u[end], semi) + scalar_data = u[1, :, :, :] end @trixi_test_nowarn Plots.plot(ScalarPlotData2D(scalar_data, semi)) @trixi_test_nowarn Plots.plot(ScalarPlotData2D((u, equations) -> u[1], From 2aa2bfc0101b5e8ac4da8754729d129eab2d9da8 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Wed, 11 Feb 2026 08:40:37 -0600 Subject: [PATCH 270/360] unify reference_node_coordinates_2d --- src/visualization/utilities.jl | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/src/visualization/utilities.jl b/src/visualization/utilities.jl index 2adffe2591b..f3f227807ac 100644 --- a/src/visualization/utilities.jl +++ b/src/visualization/utilities.jl @@ -1670,22 +1670,8 @@ function plotting_interpolation_matrix(dg::DGSEM; return kron(Vp1D, Vp1D) end -function reference_node_coordinates_2d(dg::DGSEM) - @unpack nodes = dg.basis - r = vec([nodes[i] for i in eachnode(dg), j in eachnode(dg)]) - s = vec([nodes[j] for i in eachnode(dg), j in eachnode(dg)]) - return r, s -end - -function reference_node_coordinates_2d(dg::FDSBP) - nodes = dg.basis.grid - r = vec([nodes[i] for i in eachnode(dg), j in eachnode(dg)]) - s = vec([nodes[j] for i in eachnode(dg), j in eachnode(dg)]) - return r, s -end - -function reference_node_coordinates_2d(dg::DG{<:SummationByPartsOperators.UpwindOperators}) - nodes = dg.basis.central.grid +function reference_node_coordinates_2d(dg::Union{DGSEM, FDSBP}) + nodes = get_nodes(dg.basis) r = vec([nodes[i] for i in eachnode(dg), j in eachnode(dg)]) s = vec([nodes[j] for i in eachnode(dg), j in eachnode(dg)]) return r, s From 874a6d1caacb00d276248875b5ff277816489ebe Mon Sep 17 00:00:00 2001 From: Jesse Chan <1156048+jlchan@users.noreply.github.com> Date: Wed, 11 Feb 2026 08:52:03 -0600 Subject: [PATCH 271/360] Apply suggestions from code review Co-authored-by: Joshua Lampert <51029046+JoshuaLampert@users.noreply.github.com> Co-authored-by: Hendrik Ranocha --- .../elixir_euler_nonideal_density_wave.jl | 2 +- .../nonideal_compressible_euler_2d.jl | 41 +++++++++---------- test/test_unit.jl | 4 +- 3 files changed, 23 insertions(+), 24 deletions(-) diff --git a/examples/tree_2d_dgsem/elixir_euler_nonideal_density_wave.jl b/examples/tree_2d_dgsem/elixir_euler_nonideal_density_wave.jl index f625e16155c..bc6fc05236b 100644 --- a/examples/tree_2d_dgsem/elixir_euler_nonideal_density_wave.jl +++ b/examples/tree_2d_dgsem/elixir_euler_nonideal_density_wave.jl @@ -36,7 +36,7 @@ function Trixi.initial_condition_density_wave(x, t, iter += 1 end if iter == 100 - println("Warning: solver for temperature(V, p) did not converge") + @warn "Solver for temperature(V, p) did not converge" end return prim2cons(SVector(V, v1, v2, T), equations) diff --git a/src/equations/nonideal_compressible_euler_2d.jl b/src/equations/nonideal_compressible_euler_2d.jl index a41982163c6..c210255c331 100644 --- a/src/equations/nonideal_compressible_euler_2d.jl +++ b/src/equations/nonideal_compressible_euler_2d.jl @@ -12,26 +12,26 @@ The compressible Euler equations ```math \frac{\partial}{\partial t} \begin{pmatrix} - \rho \\ \rho v_1 \\ \rho v_2 \\ \rho e_{total} + \rho \\ \rho v_1 \\ \rho v_2 \\ \rho e_{\text{total}} \end{pmatrix} + \frac{\partial}{\partial x} \begin{pmatrix} - \rho v_1 \\ \rho v_1^2 + p \\ \rho v_1 v_2 \\ (\rho e_{total} + p) v_1 + \rho v_1 \\ \rho v_1^2 + p \\ \rho v_1 v_2 \\ (\rho e_{\text{total}} + p) v_1 \end{pmatrix} + \frac{\partial}{\partial y} \begin{pmatrix} -\rho v_2 \\ \rho v_1 v_2 \\ \rho v_2^2 + p \\ (\rho e_{total} + p) v_2 +\rho v_2 \\ \rho v_1 v_2 \\ \rho v_2^2 + p \\ (\rho e_{\text{total}} + p) v_2 \end{pmatrix} = \begin{pmatrix} 0 \\ 0 \\ 0 \end{pmatrix} ``` -for a gas with pressure ``p`` specified by some equation of state in one space dimension. +for a gas with pressure ``p`` specified by some equation of state in two space dimensions. -Here, ``\rho`` is the density, ``v_1`` the x-velocity, ``v_2`` is the y-velocity, ``e_{total}`` +Here, ``\rho`` is the density, ``v_1`` the x-velocity, ``v_2`` is the y-velocity, ``e_{\text{total}}`` the specific total energy, and the pressure ``p`` is given in terms of specific volume ``V = 1/\rho`` and temperature ``T`` by some user-specified equation of state (EOS) (see [`pressure(V, T, eos::IdealGas)`](@ref), [`pressure(V, T, eos::VanDerWaals)`](@ref)) as @@ -55,7 +55,7 @@ function varnames(::typeof(cons2cons), ::NonIdealCompressibleEulerEquations2D) return ("rho", "rho_v1", "rho_v2", "rho_e_total") end -# for plotting with PlotData1D(sol, solution_variables=density_velocity_pressure) +# for plotting with PlotData2D(sol, solution_variables=cons2prim) @inline function cons2prim(u, equations::NonIdealCompressibleEulerEquations2D) eos = equations.equation_of_state rho = u[1] @@ -139,7 +139,7 @@ end Determine the boundary numerical surface flux for a slip wall condition. Imposes a zero normal velocity at the wall. -Density is taken from the internal solution state, +Density is taken from the internal solution state. Should be used together with [`UnstructuredMesh2D`](@ref), [`P4estMesh`](@ref), or [`T8codeMesh`](@ref). """ @@ -181,7 +181,7 @@ Should be used together with [`StructuredMesh`](@ref). end """ - flux_terashima_etal(u_ll, u_rr, orientation::Int, + flux_terashima_etal(u_ll, u_rr, orientation_or_normal_direction, equations::NonIdealCompressibleEulerEquations1D) Approximately pressure equilibrium conserving (APEC) flux. @@ -190,7 +190,6 @@ Approximately pressure equilibrium conserving (APEC) flux. Approximately pressure-equilibrium-preserving scheme for fully conservative simulations of compressible multi-species and real-fluid interfacial flows [DOI: 10.1016/j.jcp.2024.113701](https://doi.org/10.1016/j.jcp.2024.113701) - """ function flux_terashima_etal(u_ll, u_rr, orientation::Int, equations::NonIdealCompressibleEulerEquations2D) @@ -200,8 +199,8 @@ function flux_terashima_etal(u_ll, u_rr, orientation::Int, rho_ll = u_ll[1] rho_rr = u_rr[1] - rho_e_ll = energy_internal(u_ll, equations) - rho_e_rr = energy_internal(u_rr, equations) + rho_e_internal_ll = energy_internal(u_ll, equations) + rho_e_internal_rr = energy_internal(u_rr, equations) p_ll = pressure(V_ll, T_ll, eos) p_rr = pressure(V_rr, T_rr, eos) @@ -209,28 +208,28 @@ function flux_terashima_etal(u_ll, u_rr, orientation::Int, v1_avg = 0.5f0 * (v1_ll + v1_rr) v2_avg = 0.5f0 * (v2_ll + v2_rr) p_avg = 0.5f0 * (p_ll + p_rr) - rho_e_avg = 0.5f0 * (rho_e_ll + rho_e_rr) + rho_e_internal_avg = 0.5f0 * (rho_e_internal_ll + rho_e_internal_rr) p_v1_avg = 0.5f0 * (p_ll * v1_rr + p_rr * v1_ll) p_v2_avg = 0.5f0 * (p_ll * v2_rr + p_rr * v2_ll) # chain rule from Terashima drho_e_drho_p_ll = drho_e_drho_at_const_p(V_ll, T_ll, eos) drho_e_drho_p_rr = drho_e_drho_at_const_p(V_rr, T_rr, eos) - rho_e_avg_corrected = (rho_e_avg - + rho_e_internal_avg_corrected = (rho_e_internal_avg - 0.25f0 * (drho_e_drho_p_rr - drho_e_drho_p_ll) * (rho_rr - rho_ll)) - ke_avg = 0.5f0 * ((v1_ll * v1_rr) + (v2_ll * v2_rr)) + e_kinetic_avg = 0.5f0 * ((v1_ll * v1_rr) + (v2_ll * v2_rr)) if orientation == 1 f_rho = rho_avg * v1_avg f_rho_v1 = f_rho * v1_avg + p_avg f_rho_v2 = f_rho * v2_avg - f_rho_E = (rho_e_avg_corrected + rho_avg * ke_avg) * v1_avg + p_v1_avg + f_rho_e_total = (rho_e_internal_avg_corrected + rho_avg * e_kinetic_avg) * v1_avg + p_v1_avg else # if orientation == 2 f_rho = rho_avg * v2_avg f_rho_v1 = f_rho * v1_avg f_rho_v2 = f_rho * v2_avg + p_avg - f_rho_E = (rho_e_avg_corrected + rho_avg * ke_avg) * v2_avg + p_v2_avg + f_rho_e_total = (rho_e_internal_avg_corrected + rho_avg * e_kinetic_avg) * v2_avg + p_v2_avg end return SVector(f_rho, f_rho_v1, f_rho_v2, f_rho_E) @@ -277,8 +276,8 @@ function flux_terashima_etal(u_ll, u_rr, normal_direction::AbstractVector, end """ - flux_central_terashima_etal(u_ll, u_rr, orientation::Int, - equations::NonIdealCompressibleEulerEquations1D) + flux_central_terashima_etal(u_ll, u_rr, orientation_or_normal_direction, + equations::NonIdealCompressibleEulerEquations2D) A version of the central flux which uses the approximately pressure equilibrium conserving (APEC) internal energy correction. @@ -579,7 +578,7 @@ end end """ - function cons2thermo(u, equations::NonIdealCompressibleEulerEquations2D) + cons2thermo(u, equations::NonIdealCompressibleEulerEquations2D) Convert conservative variables to specific volume, velocity, and temperature variables `V, v1, v2, T`. These are referred to as "thermodynamic" variables since @@ -640,7 +639,7 @@ end @inline function energy_internal(u, equations::NonIdealCompressibleEulerEquations2D) rho, rho_v1, rho_v2, rho_e_total = u - rho_e = rho_e_total - 0.5f0 * (rho_v1^2 + rho_v2^2) / rho - return rho_e + rho_e_internal = rho_e_total - 0.5f0 * (rho_v1^2 + rho_v2^2) / rho + return rho_e_internal end end # @muladd diff --git a/test/test_unit.jl b/test/test_unit.jl index 573af9c8831..659cc4da374 100644 --- a/test/test_unit.jl +++ b/test/test_unit.jl @@ -840,7 +840,7 @@ end @test velocity(u, 1, equations) ≈ 0.1 @test density_pressure(u, equations) ≈ u[1] * pressure(V, T, eos) @test energy_internal_specific(u, equations) ≈ energy_internal_specific(V, T, eos) - + @test energy_internal_specific(u, equations) ≈ energy_internal(u, equations) * V @test ForwardDiff.gradient(u -> entropy(u, equations), u) ≈ cons2entropy(u, equations) for orientation in (1, 2) @@ -877,7 +877,7 @@ end end # check consistency of slip wall boundary conditions - for orientation in [1, 2] + for orientation in (1, 2) x, t = 0, 0 direction = 1 # this variable is not used in `boundary_condition_slip_wall` normal_direction = orientation == 1 ? SVector(1.0, 0.0) : SVector(0.0, 1.0) From 4f288630f8547d0e7abb5ee57884cc65c65103f8 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Wed, 11 Feb 2026 08:52:37 -0600 Subject: [PATCH 272/360] fix docstring mention of cons2prim behavior --- src/equations/nonideal_compressible_euler_1d.jl | 5 ++--- src/equations/nonideal_compressible_euler_2d.jl | 15 ++++++++------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/equations/nonideal_compressible_euler_1d.jl b/src/equations/nonideal_compressible_euler_1d.jl index fe43baa3385..01203391097 100644 --- a/src/equations/nonideal_compressible_euler_1d.jl +++ b/src/equations/nonideal_compressible_euler_1d.jl @@ -37,9 +37,8 @@ p = p(V, T) Similarly, the internal energy is specified by `e_internal = energy_internal_specific(V, T, eos)`, see [`energy_internal_specific(V, T, eos::IdealGas)`](@ref), [`energy_internal_specific(V, T, eos::VanDerWaals)`](@ref). -Because of this, the primitive variables are also defined to be `V, v1, T` (instead of -`rho, v1, p` for `CompressibleEulerEquations1D`). The implementation also assumes -mass basis unless otherwise specified. +Note that this implementation also assumes a mass basis, so molar weight is not taken into account when calculating +specific volume. """ struct NonIdealCompressibleEulerEquations1D{EoS <: AbstractEquationOfState} <: AbstractNonIdealCompressibleEulerEquations{1, 3} diff --git a/src/equations/nonideal_compressible_euler_2d.jl b/src/equations/nonideal_compressible_euler_2d.jl index c210255c331..18864e991e0 100644 --- a/src/equations/nonideal_compressible_euler_2d.jl +++ b/src/equations/nonideal_compressible_euler_2d.jl @@ -42,9 +42,8 @@ p = p(V, T) Similarly, the specific internal energy is specified by `e_{\text{internal}} = energy_internal_specific(V, T, eos)`, see [`energy_internal_specific(V, T, eos::IdealGas)`](@ref), [`energy_internal_specific(V, T, eos::VanDerWaals)`](@ref). -Because of this, the primitive variables are also defined to be `V, v1, v2, T` (instead of -`rho, v1, v2, p` for `CompressibleEulerEquations2D`). The implementation also assumes -mass basis unless otherwise specified. +Note that this implementation also assumes a mass basis, so molar weight is not taken into account when calculating +specific volume. """ struct NonIdealCompressibleEulerEquations2D{EoS <: AbstractEquationOfState} <: AbstractNonIdealCompressibleEulerEquations{2, 4} @@ -216,20 +215,22 @@ function flux_terashima_etal(u_ll, u_rr, orientation::Int, drho_e_drho_p_ll = drho_e_drho_at_const_p(V_ll, T_ll, eos) drho_e_drho_p_rr = drho_e_drho_at_const_p(V_rr, T_rr, eos) rho_e_internal_avg_corrected = (rho_e_internal_avg - - 0.25f0 * (drho_e_drho_p_rr - drho_e_drho_p_ll) * - (rho_rr - rho_ll)) + 0.25f0 * (drho_e_drho_p_rr - drho_e_drho_p_ll) * + (rho_rr - rho_ll)) e_kinetic_avg = 0.5f0 * ((v1_ll * v1_rr) + (v2_ll * v2_rr)) if orientation == 1 f_rho = rho_avg * v1_avg f_rho_v1 = f_rho * v1_avg + p_avg f_rho_v2 = f_rho * v2_avg - f_rho_e_total = (rho_e_internal_avg_corrected + rho_avg * e_kinetic_avg) * v1_avg + p_v1_avg + f_rho_e_total = (rho_e_internal_avg_corrected + rho_avg * e_kinetic_avg) * + v1_avg + p_v1_avg else # if orientation == 2 f_rho = rho_avg * v2_avg f_rho_v1 = f_rho * v1_avg f_rho_v2 = f_rho * v2_avg + p_avg - f_rho_e_total = (rho_e_internal_avg_corrected + rho_avg * e_kinetic_avg) * v2_avg + p_v2_avg + f_rho_e_total = (rho_e_internal_avg_corrected + rho_avg * e_kinetic_avg) * + v2_avg + p_v2_avg end return SVector(f_rho, f_rho_v1, f_rho_v2, f_rho_E) From 017ca765c8b524357b1fcc55a6dc7caac73fa2c8 Mon Sep 17 00:00:00 2001 From: Jesse Chan <1156048+jlchan@users.noreply.github.com> Date: Wed, 11 Feb 2026 08:54:46 -0600 Subject: [PATCH 273/360] Update examples/tree_2d_dgsem/elixir_euler_peng_robinson_transcritical_mixing.jl Co-authored-by: Joshua Lampert <51029046+JoshuaLampert@users.noreply.github.com> --- .../elixir_euler_peng_robinson_transcritical_mixing.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/tree_2d_dgsem/elixir_euler_peng_robinson_transcritical_mixing.jl b/examples/tree_2d_dgsem/elixir_euler_peng_robinson_transcritical_mixing.jl index 8aa9d14475b..29cbebe324a 100644 --- a/examples/tree_2d_dgsem/elixir_euler_peng_robinson_transcritical_mixing.jl +++ b/examples/tree_2d_dgsem/elixir_euler_peng_robinson_transcritical_mixing.jl @@ -42,7 +42,7 @@ function initial_condition_transcritical_mixing(x, t, iter += 1 end if iter == 100 - println("Warning: solver for temperature(V, p) did not converge") + @warn "Solver for temperature(V, p) did not converge" end k = 6 From b6a252ecc3d46c94f5ca6f4743d49c1adcae1752 Mon Sep 17 00:00:00 2001 From: Jesse Chan <1156048+jlchan@users.noreply.github.com> Date: Wed, 11 Feb 2026 08:54:57 -0600 Subject: [PATCH 274/360] Update examples/tree_2d_dgsem/elixir_euler_peng_robinson_transcritical_mixing.jl Co-authored-by: Joshua Lampert <51029046+JoshuaLampert@users.noreply.github.com> --- .../elixir_euler_peng_robinson_transcritical_mixing.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/tree_2d_dgsem/elixir_euler_peng_robinson_transcritical_mixing.jl b/examples/tree_2d_dgsem/elixir_euler_peng_robinson_transcritical_mixing.jl index 29cbebe324a..ff33f703d57 100644 --- a/examples/tree_2d_dgsem/elixir_euler_peng_robinson_transcritical_mixing.jl +++ b/examples/tree_2d_dgsem/elixir_euler_peng_robinson_transcritical_mixing.jl @@ -24,7 +24,7 @@ function initial_condition_transcritical_mixing(x, t, p = 2 * pc # from Bernades et al - epsilon, delta, A = 1.0, 1 / 20, 3 / 8 + epsilon, delta, A = 1, convert(RealT, 1 / 20), convert(RealT, 3 / 8) u0 = 25 # m/s T = eos.Tc * (3 * A - A * tanh(y / delta)) # Tc is 126.2 for N2 From 530bb9d6dbd183346f194926de6ceeedc5e0e960 Mon Sep 17 00:00:00 2001 From: Jesse Chan <1156048+jlchan@users.noreply.github.com> Date: Wed, 11 Feb 2026 08:55:16 -0600 Subject: [PATCH 275/360] Update examples/tree_2d_dgsem/elixir_euler_peng_robinson_transcritical_mixing.jl Co-authored-by: Joshua Lampert <51029046+JoshuaLampert@users.noreply.github.com> --- .../elixir_euler_peng_robinson_transcritical_mixing.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/tree_2d_dgsem/elixir_euler_peng_robinson_transcritical_mixing.jl b/examples/tree_2d_dgsem/elixir_euler_peng_robinson_transcritical_mixing.jl index ff33f703d57..3b8e8ffd3d0 100644 --- a/examples/tree_2d_dgsem/elixir_euler_peng_robinson_transcritical_mixing.jl +++ b/examples/tree_2d_dgsem/elixir_euler_peng_robinson_transcritical_mixing.jl @@ -46,7 +46,7 @@ function initial_condition_transcritical_mixing(x, t, end k = 6 - dv = epsilon * sin(k * pi * x) * (tanh(100 * (y + 0.1)) - tanh(100 * (y - 0.1))) / 2 + dv = epsilon * sinpi(k * x) * (tanh(100 * (y + 0.1)) - tanh(100 * (y - 0.1))) / 2 v1 = u0 * (1 + 0.2 * tanh(y / delta)) + dv v2 = dv From f8207bf7c5608ac96b61adfdec0b4ab4625301c5 Mon Sep 17 00:00:00 2001 From: Jesse Chan <1156048+jlchan@users.noreply.github.com> Date: Wed, 11 Feb 2026 08:55:26 -0600 Subject: [PATCH 276/360] Update examples/tree_2d_dgsem/elixir_euler_peng_robinson_transcritical_mixing.jl Co-authored-by: Joshua Lampert <51029046+JoshuaLampert@users.noreply.github.com> --- .../elixir_euler_peng_robinson_transcritical_mixing.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/tree_2d_dgsem/elixir_euler_peng_robinson_transcritical_mixing.jl b/examples/tree_2d_dgsem/elixir_euler_peng_robinson_transcritical_mixing.jl index 3b8e8ffd3d0..4562f4833d9 100644 --- a/examples/tree_2d_dgsem/elixir_euler_peng_robinson_transcritical_mixing.jl +++ b/examples/tree_2d_dgsem/elixir_euler_peng_robinson_transcritical_mixing.jl @@ -47,7 +47,7 @@ function initial_condition_transcritical_mixing(x, t, k = 6 dv = epsilon * sinpi(k * x) * (tanh(100 * (y + 0.1)) - tanh(100 * (y - 0.1))) / 2 - v1 = u0 * (1 + 0.2 * tanh(y / delta)) + dv + v1 = u0 * (1 + convert(RealT, 0.2) * tanh(y / delta)) + dv v2 = dv return prim2cons(SVector(V, v1, v2, T), equations) From 81b8396e163d378c89aff27d261d6587025a3e2a Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Wed, 11 Feb 2026 09:03:23 -0600 Subject: [PATCH 277/360] remove max_abs_speed and min_max_speed_naive --- .../nonideal_compressible_euler_2d.jl | 86 ------------------- test/test_tree_2d_euler.jl | 20 ++--- 2 files changed, 10 insertions(+), 96 deletions(-) diff --git a/src/equations/nonideal_compressible_euler_2d.jl b/src/equations/nonideal_compressible_euler_2d.jl index 18864e991e0..9fdfcaf31fe 100644 --- a/src/equations/nonideal_compressible_euler_2d.jl +++ b/src/equations/nonideal_compressible_euler_2d.jl @@ -386,47 +386,6 @@ function flux_central_terashima_etal(u_ll, u_rr, normal_direction::AbstractVecto return SVector(f_rho, f_rho_v1, f_rho_v2, f_rho_E) end -# Calculate estimates for minimum and maximum wave speeds for HLL-type fluxes -@inline function min_max_speed_naive(u_ll, u_rr, orientation::Integer, - equations::NonIdealCompressibleEulerEquations2D) - V_ll, v1_ll, v2_ll, T_ll = cons2thermo(u_ll, equations) - V_rr, v1_rr, v2_rr, T_rr = cons2thermo(u_rr, equations) - - eos = equations.equation_of_state - c_ll = speed_of_sound(V_ll, T_ll, eos) - c_rr = speed_of_sound(V_rr, T_rr, eos) - - if orientation == 1 # x-direction - λ_min = v1_ll - c_ll - λ_max = v1_rr + c_rr - else # y-direction - λ_min = v2_ll - c_ll - λ_max = v2_rr + c_rr - end - - return λ_min, λ_max -end - -@inline function min_max_speed_naive(u_ll, u_rr, normal_direction::AbstractVector, - equations::NonIdealCompressibleEulerEquations2D) - V_ll, v1_ll, v2_ll, T_ll = cons2thermo(u_ll, equations) - V_rr, v1_rr, v2_rr, T_rr = cons2thermo(u_rr, equations) - - v_normal_ll = v1_ll * normal_direction[1] + v2_ll * normal_direction[2] - v_normal_rr = v1_rr * normal_direction[1] + v2_rr * normal_direction[2] - - eos = equations.equation_of_state - c_ll = speed_of_sound(V_ll, T_ll, eos) - c_rr = speed_of_sound(V_rr, T_rr, eos) - - norm_ = norm(normal_direction) - # The v_normals are already scaled by the norm - λ_min = v_normal_ll - c_ll * norm_ - λ_max = v_normal_rr + c_rr * norm_ - - return λ_min, λ_max -end - # Less "cautious", i.e., less overestimating `λ_max` compared to `max_abs_speed_naive` @inline function max_abs_speed(u_ll, u_rr, orientation::Integer, equations::NonIdealCompressibleEulerEquations2D) @@ -478,51 +437,6 @@ end abs(v_rr) + c_rr * norm_) end -@inline function max_abs_speed_naive(u_ll, u_rr, orientation::Integer, - equations::NonIdealCompressibleEulerEquations2D) - V_ll, v1_ll, v2_ll, T_ll = cons2thermo(u_ll, equations) - V_rr, v1_rr, v2_rr, T_rr = cons2thermo(u_rr, equations) - - # Get the velocity value in the appropriate direction - if orientation == 1 - v_ll = v1_ll - v_rr = v1_rr - else # orientation == 2 - v_ll = v2_ll - v_rr = v2_rr - end - - v_mag_ll = abs(v_ll) - v_mag_rr = abs(v_rr) - - # Calculate primitive variables and speed of sound - eos = equations.equation_of_state - c_ll = speed_of_sound(V_ll, T_ll, eos) - c_rr = speed_of_sound(V_rr, T_rr, eos) - - return max(v_mag_ll, v_mag_rr) + max(c_ll, c_rr) -end - -@inline function max_abs_speed_naive(u_ll, u_rr, normal_direction::AbstractVector, - equations::NonIdealCompressibleEulerEquations2D) - V_ll, v1_ll, v2_ll, T_ll = cons2thermo(u_ll, equations) - V_rr, v1_rr, v2_rr, T_rr = cons2thermo(u_rr, equations) - - # Get the velocity value in the appropriate direction - v_dot_n_ll = v1_ll * normal_direction[1] + v2_ll * normal_direction[2] - v_dot_n_rr = v1_rr * normal_direction[1] + v2_rr * normal_direction[2] - - v_mag_ll = abs(v_dot_n_ll) - v_mag_rr = abs(v_dot_n_rr) - - # Calculate primitive variables and speed of sound - eos = equations.equation_of_state - c_ll = speed_of_sound(V_ll, T_ll, eos) - c_rr = speed_of_sound(V_rr, T_rr, eos) - - return max(v_mag_ll, v_mag_rr) + max(c_ll, c_rr) -end - # More refined estimates for minimum and maximum wave speeds for HLL-type fluxes @inline function min_max_speed_davis(u_ll, u_rr, orientation::Integer, equations::NonIdealCompressibleEulerEquations2D) diff --git a/test/test_tree_2d_euler.jl b/test/test_tree_2d_euler.jl index a0c9f7e4154..825e4b89dfc 100644 --- a/test/test_tree_2d_euler.jl +++ b/test/test_tree_2d_euler.jl @@ -130,20 +130,20 @@ end @test_allocations(Trixi.rhs!, semi, sol, 1000) end -@trixi_testset "elixir_euler_nonideal_density_wave.jl (min_max_speed_naive)" begin +@trixi_testset "elixir_euler_nonideal_density_wave.jl (FluxHLL))" begin @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_nonideal_density_wave.jl"), - tspan=(0.0, 0.5), surface_flux=FluxHLL(min_max_speed_naive), + tspan=(0.0, 0.5), surface_flux=flux_hll, l2=[ - 0.005120569410818968, - 0.0005107175634328961, - 0.0010214028052822862, - 0.26424662406680544 + 0.005120682930479358, + 0.0005107154473420592, + 0.0010214220340067936, + 0.2642543660997129 ], linf=[ - 0.02253539764111734, - 0.0021502487629356526, - 0.004367440276381418, - 1.0203844424142972 + 0.022535393384123026, + 0.0021501709863882, + 0.004367450646593718, + 1.020387671247505 ]) # Ensure that we do not have excessive memory allocations # (e.g., from type instabilities) From c3a273df23a79130c574349ea024716a74a5d15f Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Wed, 11 Feb 2026 09:05:52 -0600 Subject: [PATCH 278/360] rename f_rho_E -> f_rho_e_total --- src/equations/nonideal_compressible_euler_1d.jl | 12 ++++++------ src/equations/nonideal_compressible_euler_2d.jl | 7 ++++--- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/equations/nonideal_compressible_euler_1d.jl b/src/equations/nonideal_compressible_euler_1d.jl index 01203391097..62832a5ef33 100644 --- a/src/equations/nonideal_compressible_euler_1d.jl +++ b/src/equations/nonideal_compressible_euler_1d.jl @@ -145,9 +145,9 @@ Approximately pressure equilibrium preserving with conservation (APEC) flux from f_rho = rho_avg * v1_avg f_rho_v1 = rho_avg * v1_avg * v1_avg + p_avg # Note that the additional "average" is a product and not v1_avg - f_rho_E = rho_e_v1_avg + rho_avg * 0.5f0 * (v1_ll * v1_rr) * v1_avg + p_v1_avg + f_rho_e_total = rho_e_v1_avg + rho_avg * 0.5f0 * (v1_ll * v1_rr) * v1_avg + p_v1_avg - return SVector(f_rho, f_rho_v1, f_rho_E) + return SVector(f_rho, f_rho_v1, f_rho_e_total) end """ @@ -198,11 +198,11 @@ by Terashima, Ly, Ihme (2025). # contributions separately in the energy equation ke_ll = 0.5f0 * v1_ll^2 ke_rr = 0.5f0 * v1_rr^2 - f_rho_E = rho_e_v1_avg + - 0.5f0 * (rho_v1_ll * ke_ll + rho_v1_rr * ke_rr) + - 0.5f0 * (p_ll * v1_ll + p_rr * v1_rr) + f_rho_e_total = rho_e_v1_avg + + 0.5f0 * (rho_v1_ll * ke_ll + rho_v1_rr * ke_rr) + + 0.5f0 * (p_ll * v1_ll + p_rr * v1_rr) - return SVector(f_rho, f_rho_v1, f_rho_E) + return SVector(f_rho, f_rho_v1, f_rho_e_total) end # Calculate estimates for minimum and maximum wave speeds for HLL-type fluxes diff --git a/src/equations/nonideal_compressible_euler_2d.jl b/src/equations/nonideal_compressible_euler_2d.jl index 9fdfcaf31fe..f28e23beb75 100644 --- a/src/equations/nonideal_compressible_euler_2d.jl +++ b/src/equations/nonideal_compressible_euler_2d.jl @@ -233,7 +233,7 @@ function flux_terashima_etal(u_ll, u_rr, orientation::Int, v2_avg + p_v2_avg end - return SVector(f_rho, f_rho_v1, f_rho_v2, f_rho_E) + return SVector(f_rho, f_rho_v1, f_rho_v2, f_rho_e_total) end function flux_terashima_etal(u_ll, u_rr, normal_direction::AbstractVector, @@ -272,8 +272,9 @@ function flux_terashima_etal(u_ll, u_rr, normal_direction::AbstractVector, f_rho = rho_avg * v_dot_n_avg f_rho_v1 = f_rho * v1_avg + p_avg * normal_direction[1] f_rho_v2 = f_rho * v2_avg + p_avg * normal_direction[2] - f_rho_E = (rho_e_avg_corrected + rho_avg * ke_avg) * v_dot_n_avg + p_v_dot_n_avg - return SVector(f_rho, f_rho_v1, f_rho_v2, f_rho_E) + f_rho_e_total = (rho_e_avg_corrected + rho_avg * ke_avg) * v_dot_n_avg + + p_v_dot_n_avg + return SVector(f_rho, f_rho_v1, f_rho_v2, f_rho_e_total) end """ From a1ec5e062af2f2df0ebb53d79ae5038f4f2b6200 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Wed, 11 Feb 2026 09:14:45 -0600 Subject: [PATCH 279/360] rename ke -> e_kinetic --- src/equations/nonideal_compressible_euler_1d.jl | 8 ++++---- src/equations/nonideal_compressible_euler_2d.jl | 10 +++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/equations/nonideal_compressible_euler_1d.jl b/src/equations/nonideal_compressible_euler_1d.jl index 62832a5ef33..7e85c9ad7e6 100644 --- a/src/equations/nonideal_compressible_euler_1d.jl +++ b/src/equations/nonideal_compressible_euler_1d.jl @@ -196,10 +196,10 @@ by Terashima, Ly, Ihme (2025). # calculate internal energy (with APEC correction) and kinetic energy # contributions separately in the energy equation - ke_ll = 0.5f0 * v1_ll^2 - ke_rr = 0.5f0 * v1_rr^2 - f_rho_e_total = rho_e_v1_avg + - 0.5f0 * (rho_v1_ll * ke_ll + rho_v1_rr * ke_rr) + + e_kinetic_ll = 0.5f0 * v1_ll^2 + e_kinetic_rr = 0.5f0 * v1_rr^2 + f_rho_e_total = rho_e_internal_corrected_v1_avg + + 0.5f0 * (rho_v1_ll * e_kinetic_ll + rho_v1_rr * e_kinetic_rr) + 0.5f0 * (p_ll * v1_ll + p_rr * v1_rr) return SVector(f_rho, f_rho_v1, f_rho_e_total) diff --git a/src/equations/nonideal_compressible_euler_2d.jl b/src/equations/nonideal_compressible_euler_2d.jl index f28e23beb75..a662aaae9f5 100644 --- a/src/equations/nonideal_compressible_euler_2d.jl +++ b/src/equations/nonideal_compressible_euler_2d.jl @@ -267,7 +267,7 @@ function flux_terashima_etal(u_ll, u_rr, normal_direction::AbstractVector, 0.25f0 * (drho_e_drho_p_rr - drho_e_drho_p_ll) * (rho_rr - rho_ll)) - ke_avg = 0.5f0 * ((v1_ll * v1_rr) + (v2_ll * v2_rr)) + e_kinetic_avg = 0.5f0 * ((v1_ll * v1_rr) + (v2_ll * v2_rr)) f_rho = rho_avg * v_dot_n_avg f_rho_v1 = f_rho * v1_avg + p_avg * normal_direction[1] @@ -316,8 +316,8 @@ function flux_central_terashima_etal(u_ll, u_rr, orientation::Int, # calculate internal energy (with APEC correction) and kinetic energy # contributions separately in energy equation - ke_ll = 0.5f0 * (v1_ll^2 + v2_ll^2) - ke_rr = 0.5f0 * (v1_rr^2 + v2_rr^2) + e_kinetic_ll = 0.5f0 * (v1_ll^2 + v2_ll^2) + e_kinetic_rr = 0.5f0 * (v1_rr^2 + v2_rr^2) if orientation == 1 f_rho = 0.5f0 * (rho_v1_ll + rho_v1_rr) @@ -371,8 +371,8 @@ function flux_central_terashima_etal(u_ll, u_rr, normal_direction::AbstractVecto # calculate internal energy (with APEC correction) and kinetic energy # contributions separately in energy equation - ke_ll = 0.5f0 * (v1_ll^2 + v2_ll^2) - ke_rr = 0.5f0 * (v1_rr^2 + v2_rr^2) + e_kinetic_ll = 0.5f0 * (v1_ll^2 + v2_ll^2) + e_kinetic_rr = 0.5f0 * (v1_rr^2 + v2_rr^2) rho_v_dot_n_ll = rho_ll * v_dot_n_ll rho_v_dot_n_rr = rho_rr * v_dot_n_rr From 9ca24da3d022d1d099099d69e426dce696b8ca38 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Wed, 11 Feb 2026 09:14:56 -0600 Subject: [PATCH 280/360] remove unnecessary comment --- src/equations/nonideal_compressible_euler_2d.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/equations/nonideal_compressible_euler_2d.jl b/src/equations/nonideal_compressible_euler_2d.jl index a662aaae9f5..a592f1187fa 100644 --- a/src/equations/nonideal_compressible_euler_2d.jl +++ b/src/equations/nonideal_compressible_euler_2d.jl @@ -54,13 +54,13 @@ function varnames(::typeof(cons2cons), ::NonIdealCompressibleEulerEquations2D) return ("rho", "rho_v1", "rho_v2", "rho_e_total") end -# for plotting with PlotData2D(sol, solution_variables=cons2prim) @inline function cons2prim(u, equations::NonIdealCompressibleEulerEquations2D) eos = equations.equation_of_state rho = u[1] V, v1, v2, T = cons2thermo(u, equations) return SVector(rho, v1, v2, pressure(V, T, eos)) end + varnames(::typeof(cons2prim), ::NonIdealCompressibleEulerEquations2D) = ("rho", "v1", "v2", From fff9838ff9d96d83e880af0f1deece45dba6419a Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Wed, 11 Feb 2026 09:16:31 -0600 Subject: [PATCH 281/360] use e_total and e_internal consistently --- .../nonideal_compressible_euler_1d.jl | 20 +++++---- .../nonideal_compressible_euler_2d.jl | 44 ++++++++++--------- 2 files changed, 34 insertions(+), 30 deletions(-) diff --git a/src/equations/nonideal_compressible_euler_1d.jl b/src/equations/nonideal_compressible_euler_1d.jl index 7e85c9ad7e6..cf6b22bbbde 100644 --- a/src/equations/nonideal_compressible_euler_1d.jl +++ b/src/equations/nonideal_compressible_euler_1d.jl @@ -133,19 +133,20 @@ Approximately pressure equilibrium preserving with conservation (APEC) flux from # internal energy density with respect to the density at # constant pressure is zero for an ideal gas EOS. Thus, # the following mean value reduces to - # rho_e_v1_avg = rho_e_avg * v1_avg + # rho_e_internal_corrected_v1_avg = rho_e_internal_corrected_avg * v1_avg # for an ideal gas EOS. drho_e_drho_p_ll = drho_e_drho_at_const_p(V_ll, T_ll, eos) drho_e_drho_p_rr = drho_e_drho_at_const_p(V_rr, T_rr, eos) - rho_e_v1_avg = (rho_e_avg - - 0.25f0 * (drho_e_drho_p_rr - drho_e_drho_p_ll) * (rho_rr - rho_ll)) * - v1_avg + rho_e_internal_corrected_v1_avg = (rho_e_avg - + 0.25f0 * (drho_e_drho_p_rr - drho_e_drho_p_ll) * + (rho_rr - rho_ll)) * v1_avg # Ignore orientation since it is always "1" in 1D f_rho = rho_avg * v1_avg f_rho_v1 = rho_avg * v1_avg * v1_avg + p_avg # Note that the additional "average" is a product and not v1_avg - f_rho_e_total = rho_e_v1_avg + rho_avg * 0.5f0 * (v1_ll * v1_rr) * v1_avg + p_v1_avg + f_rho_e_total = rho_e_internal_corrected_v1_avg + + rho_avg * 0.5f0 * (v1_ll * v1_rr) * v1_avg + p_v1_avg return SVector(f_rho, f_rho_v1, f_rho_e_total) end @@ -182,13 +183,14 @@ by Terashima, Ly, Ihme (2025). # internal energy density with respect to the density at # constant pressure is zero for an ideal gas EOS. Thus, # the following mean value reduces to - # rho_e_v1_avg = rho_e_avg * v1_avg + # rho_e_internal_corrected_v1_avg = rho_e_avg * v1_avg # for an ideal gas EOS. drho_e_drho_p_ll = drho_e_drho_at_const_p(V_ll, T_ll, eos) drho_e_drho_p_rr = drho_e_drho_at_const_p(V_rr, T_rr, eos) - rho_e_v1_avg = (rho_e_avg - - 0.25f0 * (drho_e_drho_p_rr - drho_e_drho_p_ll) * (rho_rr - rho_ll)) * - v1_avg + rho_e_internal_corrected_v1_avg = (rho_e_avg - + 0.25f0 * (drho_e_drho_p_rr - drho_e_drho_p_ll) * + (rho_rr - rho_ll)) * + v1_avg # Ignore orientation since it is always "1" in 1D f_rho = 0.5f0 * (rho_v1_ll + rho_v1_rr) diff --git a/src/equations/nonideal_compressible_euler_2d.jl b/src/equations/nonideal_compressible_euler_2d.jl index a592f1187fa..34f7240ee80 100644 --- a/src/equations/nonideal_compressible_euler_2d.jl +++ b/src/equations/nonideal_compressible_euler_2d.jl @@ -263,16 +263,17 @@ function flux_terashima_etal(u_ll, u_rr, normal_direction::AbstractVector, # chain rule from Terashima drho_e_drho_p_ll = drho_e_drho_at_const_p(V_ll, T_ll, eos) drho_e_drho_p_rr = drho_e_drho_at_const_p(V_rr, T_rr, eos) - rho_e_avg_corrected = (rho_e_avg - - 0.25f0 * (drho_e_drho_p_rr - drho_e_drho_p_ll) * - (rho_rr - rho_ll)) + rho_e_internal_avg_corrected = (rho_e_avg - + 0.25f0 * (drho_e_drho_p_rr - drho_e_drho_p_ll) * + (rho_rr - rho_ll)) e_kinetic_avg = 0.5f0 * ((v1_ll * v1_rr) + (v2_ll * v2_rr)) f_rho = rho_avg * v_dot_n_avg f_rho_v1 = f_rho * v1_avg + p_avg * normal_direction[1] f_rho_v2 = f_rho * v2_avg + p_avg * normal_direction[2] - f_rho_e_total = (rho_e_avg_corrected + rho_avg * ke_avg) * v_dot_n_avg + + f_rho_e_total = (rho_e_internal_avg_corrected + rho_avg * e_kinetic_avg) * + v_dot_n_avg + p_v_dot_n_avg return SVector(f_rho, f_rho_v1, f_rho_v2, f_rho_e_total) end @@ -310,9 +311,9 @@ function flux_central_terashima_etal(u_ll, u_rr, orientation::Int, # chain rule from Terashima drho_e_drho_p_ll = drho_e_drho_at_const_p(V_ll, T_ll, eos) drho_e_drho_p_rr = drho_e_drho_at_const_p(V_rr, T_rr, eos) - rho_e_avg_corrected = (rho_e_avg - - 0.25f0 * (drho_e_drho_p_rr - drho_e_drho_p_ll) * - (rho_rr - rho_ll)) + rho_e_internal_avg_corrected = (rho_e_avg - + 0.25f0 * (drho_e_drho_p_rr - drho_e_drho_p_ll) * + (rho_rr - rho_ll)) # calculate internal energy (with APEC correction) and kinetic energy # contributions separately in energy equation @@ -323,19 +324,19 @@ function flux_central_terashima_etal(u_ll, u_rr, orientation::Int, f_rho = 0.5f0 * (rho_v1_ll + rho_v1_rr) f_rho_v1 = 0.5f0 * (rho_v1_ll * v1_ll + rho_v1_rr * v1_rr) + p_avg f_rho_v2 = 0.5f0 * (rho_v1_ll * v2_ll + rho_v1_rr * v2_rr) - f_rho_E = rho_e_avg_corrected * v1_avg + - 0.5f0 * (rho_v1_ll * ke_ll + rho_v1_rr * ke_rr) + - 0.5f0 * (p_ll * v1_ll + p_rr * v1_rr) + f_rho_e_total = rho_e_internal_avg_corrected * v1_avg + + 0.5f0 * (rho_v1_ll * e_kinetic_ll + rho_v1_rr * e_kinetic_rr) + + 0.5f0 * (p_ll * v1_ll + p_rr * v1_rr) else # if orientation == 2 f_rho = 0.5f0 * (rho_v2_ll + rho_v2_rr) f_rho_v1 = 0.5f0 * (rho_v1_ll * v2_ll + rho_v1_rr * v2_rr) f_rho_v2 = 0.5f0 * (rho_v2_ll * v2_ll + rho_v2_rr * v2_rr) + p_avg - f_rho_E = rho_e_avg_corrected * v2_avg + - 0.5f0 * (rho_v2_ll * ke_ll + rho_v2_rr * ke_rr) + - 0.5f0 * (p_ll * v2_ll + p_rr * v2_rr) + f_rho_e_total = rho_e_internal_avg_corrected * v2_avg + + 0.5f0 * (rho_v2_ll * e_kinetic_ll + rho_v2_rr * e_kinetic_rr) + + 0.5f0 * (p_ll * v2_ll + p_rr * v2_rr) end - return SVector(f_rho, f_rho_v1, f_rho_v2, f_rho_E) + return SVector(f_rho, f_rho_v1, f_rho_v2, f_rho_e_total) end function flux_central_terashima_etal(u_ll, u_rr, normal_direction::AbstractVector, @@ -365,9 +366,9 @@ function flux_central_terashima_etal(u_ll, u_rr, normal_direction::AbstractVecto # chain rule from Terashima drho_e_drho_p_ll = drho_e_drho_at_const_p(V_ll, T_ll, eos) drho_e_drho_p_rr = drho_e_drho_at_const_p(V_rr, T_rr, eos) - rho_e_avg_corrected = (rho_e_avg - - 0.25f0 * (drho_e_drho_p_rr - drho_e_drho_p_ll) * - (rho_rr - rho_ll)) + rho_e_internal_avg_corrected = (rho_e_avg - + 0.25f0 * (drho_e_drho_p_rr - drho_e_drho_p_ll) * + (rho_rr - rho_ll)) # calculate internal energy (with APEC correction) and kinetic energy # contributions separately in energy equation @@ -381,10 +382,11 @@ function flux_central_terashima_etal(u_ll, u_rr, normal_direction::AbstractVecto p_avg * normal_direction[1] f_rho_v2 = 0.5f0 * (rho_v_dot_n_ll * v2_ll + rho_v_dot_n_rr * v2_rr) + p_avg * normal_direction[2] - f_rho_E = rho_e_avg_corrected * v_dot_n_avg + - 0.5f0 * (rho_v_dot_n_ll * ke_ll + rho_v_dot_n_rr * ke_rr) + - 0.5f0 * (p_ll * v_dot_n_ll + p_rr * v_dot_n_rr) - return SVector(f_rho, f_rho_v1, f_rho_v2, f_rho_E) + f_rho_e_total = rho_e_internal_avg_corrected * v_dot_n_avg + + 0.5f0 * + (rho_v_dot_n_ll * e_kinetic_ll + rho_v_dot_n_rr * e_kinetic_rr) + + 0.5f0 * (p_ll * v_dot_n_ll + p_rr * v_dot_n_rr) + return SVector(f_rho, f_rho_v1, f_rho_v2, f_rho_e_total) end # Less "cautious", i.e., less overestimating `λ_max` compared to `max_abs_speed_naive` From 6d208aa46e8ac04f0216ac60867dd99aac7f07b0 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Wed, 11 Feb 2026 09:21:34 -0600 Subject: [PATCH 282/360] rename d_rho_e_drho_at_const_p (use e_internal) --- src/equations/equations_of_state.jl | 6 ++-- .../nonideal_compressible_euler_1d.jl | 20 +++++++----- .../nonideal_compressible_euler_2d.jl | 32 ++++++++++++------- 3 files changed, 35 insertions(+), 23 deletions(-) diff --git a/src/equations/equations_of_state.jl b/src/equations/equations_of_state.jl index 24ea4ad81b0..eb3ea31ffbd 100644 --- a/src/equations/equations_of_state.jl +++ b/src/equations/equations_of_state.jl @@ -93,16 +93,16 @@ function temperature(V, e_internal, eos::AbstractEquationOfState; end # helper function used in [`flux_terashima_etal`](@ref) and [`flux_terashima_etal_central`](@ref) -@inline function drho_e_drho_at_const_p(V, T, eos::AbstractEquationOfState) +@inline function drho_e_internal_drho_at_const_p(V, T, eos::AbstractEquationOfState) rho = inv(V) e_internal = energy_internal_specific(V, T, eos) dpdT_V, dpdV_T = calc_pressure_derivatives(V, T, eos) dpdrho_T = dpdV_T * (-V / rho) # V = inv(rho), so dVdrho = -1/rho^2 = -V^2. de_dV_T = T * dpdT_V - pressure(V, T, eos) - drho_e_drho_T = e_internal + rho * de_dV_T * (-V / rho) # d(rho_e)/drho_|T = e + rho * de_dV|T * dVdrho + drho_e_internal_drho_T = e_internal + rho * de_dV_T * (-V / rho) # d(rho_e)/drho_|T = e + rho * de_dV|T * dVdrho c_v = heat_capacity_constant_volume(V, T, eos) - return ((-rho * c_v) / (dpdT_V) * dpdrho_T + drho_e_drho_T) + return ((-rho * c_v) / (dpdT_V) * dpdrho_T + drho_e_internal_drho_T) end end # @muladd diff --git a/src/equations/nonideal_compressible_euler_1d.jl b/src/equations/nonideal_compressible_euler_1d.jl index cf6b22bbbde..ccf937af61a 100644 --- a/src/equations/nonideal_compressible_euler_1d.jl +++ b/src/equations/nonideal_compressible_euler_1d.jl @@ -129,16 +129,18 @@ Approximately pressure equilibrium preserving with conservation (APEC) flux from p_v1_avg = 0.5f0 * (p_ll * v1_rr + p_rr * v1_ll) # chain rule from Terashima - # Note that `drho_e_drho_p`, i.e., the derivative of the + # Note that `drho_e_internal_drho_p`, i.e., the derivative of the # internal energy density with respect to the density at # constant pressure is zero for an ideal gas EOS. Thus, # the following mean value reduces to # rho_e_internal_corrected_v1_avg = rho_e_internal_corrected_avg * v1_avg # for an ideal gas EOS. - drho_e_drho_p_ll = drho_e_drho_at_const_p(V_ll, T_ll, eos) - drho_e_drho_p_rr = drho_e_drho_at_const_p(V_rr, T_rr, eos) + drho_e_internal_drho_p_ll = drho_e_internal_drho_at_const_p(V_ll, T_ll, eos) + drho_e_internal_drho_p_rr = drho_e_internal_drho_at_const_p(V_rr, T_rr, eos) rho_e_internal_corrected_v1_avg = (rho_e_avg - - 0.25f0 * (drho_e_drho_p_rr - drho_e_drho_p_ll) * + 0.25f0 * + (drho_e_internal_drho_p_rr - + drho_e_internal_drho_p_ll) * (rho_rr - rho_ll)) * v1_avg # Ignore orientation since it is always "1" in 1D @@ -179,16 +181,18 @@ by Terashima, Ly, Ihme (2025). rho_e_avg = 0.5f0 * (rho_e_ll + rho_e_rr) # chain rule from Terashima - # Note that `drho_e_drho_p`, i.e., the derivative of the + # Note that `drho_e_internal_drho_p`, i.e., the derivative of the # internal energy density with respect to the density at # constant pressure is zero for an ideal gas EOS. Thus, # the following mean value reduces to # rho_e_internal_corrected_v1_avg = rho_e_avg * v1_avg # for an ideal gas EOS. - drho_e_drho_p_ll = drho_e_drho_at_const_p(V_ll, T_ll, eos) - drho_e_drho_p_rr = drho_e_drho_at_const_p(V_rr, T_rr, eos) + drho_e_internal_drho_p_ll = drho_e_internal_drho_at_const_p(V_ll, T_ll, eos) + drho_e_internal_drho_p_rr = drho_e_internal_drho_at_const_p(V_rr, T_rr, eos) rho_e_internal_corrected_v1_avg = (rho_e_avg - - 0.25f0 * (drho_e_drho_p_rr - drho_e_drho_p_ll) * + 0.25f0 * + (drho_e_internal_drho_p_rr - + drho_e_internal_drho_p_ll) * (rho_rr - rho_ll)) * v1_avg diff --git a/src/equations/nonideal_compressible_euler_2d.jl b/src/equations/nonideal_compressible_euler_2d.jl index 34f7240ee80..1238c19243a 100644 --- a/src/equations/nonideal_compressible_euler_2d.jl +++ b/src/equations/nonideal_compressible_euler_2d.jl @@ -212,10 +212,12 @@ function flux_terashima_etal(u_ll, u_rr, orientation::Int, p_v2_avg = 0.5f0 * (p_ll * v2_rr + p_rr * v2_ll) # chain rule from Terashima - drho_e_drho_p_ll = drho_e_drho_at_const_p(V_ll, T_ll, eos) - drho_e_drho_p_rr = drho_e_drho_at_const_p(V_rr, T_rr, eos) + drho_e_internal_drho_p_ll = drho_e_internal_drho_at_const_p(V_ll, T_ll, eos) + drho_e_internal_drho_p_rr = drho_e_internal_drho_at_const_p(V_rr, T_rr, eos) rho_e_internal_avg_corrected = (rho_e_internal_avg - - 0.25f0 * (drho_e_drho_p_rr - drho_e_drho_p_ll) * + 0.25f0 * + (drho_e_internal_drho_p_rr - + drho_e_internal_drho_p_ll) * (rho_rr - rho_ll)) e_kinetic_avg = 0.5f0 * ((v1_ll * v1_rr) + (v2_ll * v2_rr)) @@ -261,10 +263,12 @@ function flux_terashima_etal(u_ll, u_rr, normal_direction::AbstractVector, p_v_dot_n_avg = 0.5f0 * (p_ll * v_dot_n_rr + p_rr * v_dot_n_ll) # chain rule from Terashima - drho_e_drho_p_ll = drho_e_drho_at_const_p(V_ll, T_ll, eos) - drho_e_drho_p_rr = drho_e_drho_at_const_p(V_rr, T_rr, eos) + drho_e_internal_drho_p_ll = drho_e_internal_drho_at_const_p(V_ll, T_ll, eos) + drho_e_internal_drho_p_rr = drho_e_internal_drho_at_const_p(V_rr, T_rr, eos) rho_e_internal_avg_corrected = (rho_e_avg - - 0.25f0 * (drho_e_drho_p_rr - drho_e_drho_p_ll) * + 0.25f0 * + (drho_e_internal_drho_p_rr - + drho_e_internal_drho_p_ll) * (rho_rr - rho_ll)) e_kinetic_avg = 0.5f0 * ((v1_ll * v1_rr) + (v2_ll * v2_rr)) @@ -309,10 +313,12 @@ function flux_central_terashima_etal(u_ll, u_rr, orientation::Int, rho_e_avg = 0.5f0 * (rho_e_ll + rho_e_rr) # chain rule from Terashima - drho_e_drho_p_ll = drho_e_drho_at_const_p(V_ll, T_ll, eos) - drho_e_drho_p_rr = drho_e_drho_at_const_p(V_rr, T_rr, eos) + drho_e_internal_drho_p_ll = drho_e_internal_drho_at_const_p(V_ll, T_ll, eos) + drho_e_internal_drho_p_rr = drho_e_internal_drho_at_const_p(V_rr, T_rr, eos) rho_e_internal_avg_corrected = (rho_e_avg - - 0.25f0 * (drho_e_drho_p_rr - drho_e_drho_p_ll) * + 0.25f0 * + (drho_e_internal_drho_p_rr - + drho_e_internal_drho_p_ll) * (rho_rr - rho_ll)) # calculate internal energy (with APEC correction) and kinetic energy @@ -364,10 +370,12 @@ function flux_central_terashima_etal(u_ll, u_rr, normal_direction::AbstractVecto p_v_dot_n_avg = 0.5f0 * (p_ll * v_dot_n_rr + p_rr * v_dot_n_ll) # chain rule from Terashima - drho_e_drho_p_ll = drho_e_drho_at_const_p(V_ll, T_ll, eos) - drho_e_drho_p_rr = drho_e_drho_at_const_p(V_rr, T_rr, eos) + drho_e_internal_drho_p_ll = drho_e_internal_drho_at_const_p(V_ll, T_ll, eos) + drho_e_internal_drho_p_rr = drho_e_internal_drho_at_const_p(V_rr, T_rr, eos) rho_e_internal_avg_corrected = (rho_e_avg - - 0.25f0 * (drho_e_drho_p_rr - drho_e_drho_p_ll) * + 0.25f0 * + (drho_e_internal_drho_p_rr - + drho_e_internal_drho_p_ll) * (rho_rr - rho_ll)) # calculate internal energy (with APEC correction) and kinetic energy From 9265b4edb9c9abfba9c83076d1837261133ab87f Mon Sep 17 00:00:00 2001 From: Jesse Chan <1156048+jlchan@users.noreply.github.com> Date: Wed, 11 Feb 2026 10:12:40 -0600 Subject: [PATCH 283/360] Update test_unit.jl Co-authored-by: Joshua Lampert <51029046+JoshuaLampert@users.noreply.github.com> --- test/test_unit.jl | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/test_unit.jl b/test/test_unit.jl index 659cc4da374..95026119a16 100644 --- a/test/test_unit.jl +++ b/test/test_unit.jl @@ -868,8 +868,7 @@ end flux_central_terashima_etal(u_ll, u_rr, 1, equations) * normal_direction[1] + flux_central_terashima_etal(u_ll, u_rr, 2, equations) * normal_direction[2] - for _flux_function in (flux_lax_friedrichs, min_max_speed_naive, - min_max_speed_davis, max_abs_speed_naive) + for _flux_function in (flux_lax_friedrichs, min_max_speed_davis) @test all(_flux_function(u_ll, u_rr, 1, equations) .≈ _flux_function(u_ll, u_rr, SVector(1, 0), equations)) @test all(_flux_function(u_ll, u_rr, 2, equations) .≈ From 6b39c67aa085bb0d1b29b8fea571e71005372783 Mon Sep 17 00:00:00 2001 From: Jesse Chan <1156048+jlchan@users.noreply.github.com> Date: Wed, 11 Feb 2026 10:19:29 -0600 Subject: [PATCH 284/360] Apply suggestions from code review Co-authored-by: Joshua Lampert <51029046+JoshuaLampert@users.noreply.github.com> --- .../nonideal_compressible_euler_2d.jl | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/equations/nonideal_compressible_euler_2d.jl b/src/equations/nonideal_compressible_euler_2d.jl index 1238c19243a..747a9bfa715 100644 --- a/src/equations/nonideal_compressible_euler_2d.jl +++ b/src/equations/nonideal_compressible_euler_2d.jl @@ -246,8 +246,8 @@ function flux_terashima_etal(u_ll, u_rr, normal_direction::AbstractVector, rho_ll = u_ll[1] rho_rr = u_rr[1] - rho_e_ll = energy_internal(u_ll, equations) - rho_e_rr = energy_internal(u_rr, equations) + rho_e_internal_ll = energy_internal(u_ll, equations) + rho_e_internal_rr = energy_internal(u_rr, equations) p_ll = pressure(V_ll, T_ll, eos) p_rr = pressure(V_rr, T_rr, eos) @@ -265,7 +265,7 @@ function flux_terashima_etal(u_ll, u_rr, normal_direction::AbstractVector, # chain rule from Terashima drho_e_internal_drho_p_ll = drho_e_internal_drho_at_const_p(V_ll, T_ll, eos) drho_e_internal_drho_p_rr = drho_e_internal_drho_at_const_p(V_rr, T_rr, eos) - rho_e_internal_avg_corrected = (rho_e_avg - + rho_e_internal_avg_corrected = (rho_e_internal_avg - 0.25f0 * (drho_e_internal_drho_p_rr - drho_e_internal_drho_p_ll) * @@ -302,20 +302,20 @@ function flux_central_terashima_etal(u_ll, u_rr, orientation::Int, rho_ll, rho_v1_ll, rho_v2_ll, rho_e_total_ll = u_ll rho_rr, rho_v1_rr, rho_v2_rr, rho_e_total_rr = u_rr - rho_e_ll = energy_internal(u_ll, equations) - rho_e_rr = energy_internal(u_rr, equations) + rho_e_internal_ll = energy_internal(u_ll, equations) + rho_e_internal_rr = energy_internal(u_rr, equations) p_ll = pressure(V_ll, T_ll, eos) p_rr = pressure(V_rr, T_rr, eos) v1_avg = 0.5f0 * (v1_ll + v1_rr) v2_avg = 0.5f0 * (v2_ll + v2_rr) p_avg = 0.5f0 * (p_ll + p_rr) - rho_e_avg = 0.5f0 * (rho_e_ll + rho_e_rr) + rho_e_internal_avg = 0.5f0 * (rho_internal_e_ll + rho_e_internal_rr) # chain rule from Terashima drho_e_internal_drho_p_ll = drho_e_internal_drho_at_const_p(V_ll, T_ll, eos) drho_e_internal_drho_p_rr = drho_e_internal_drho_at_const_p(V_rr, T_rr, eos) - rho_e_internal_avg_corrected = (rho_e_avg - + rho_e_internal_avg_corrected = (rho_e_internal_avg - 0.25f0 * (drho_e_internal_drho_p_rr - drho_e_internal_drho_p_ll) * @@ -353,8 +353,8 @@ function flux_central_terashima_etal(u_ll, u_rr, normal_direction::AbstractVecto rho_ll, rho_v1_ll, rho_v2_ll, rho_e_total_ll = u_ll rho_rr, rho_v1_rr, rho_v2_rr, rho_e_total_rr = u_rr - rho_e_ll = energy_internal(u_ll, equations) - rho_e_rr = energy_internal(u_rr, equations) + rho_e_internal_ll = energy_internal(u_ll, equations) + rho_e_internal_rr = energy_internal(u_rr, equations) p_ll = pressure(V_ll, T_ll, eos) p_rr = pressure(V_rr, T_rr, eos) @@ -366,13 +366,13 @@ function flux_central_terashima_etal(u_ll, u_rr, normal_direction::AbstractVecto v1_avg = 0.5f0 * (v1_ll + v1_rr) v2_avg = 0.5f0 * (v2_ll + v2_rr) p_avg = 0.5f0 * (p_ll + p_rr) - rho_e_avg = 0.5f0 * (rho_e_ll + rho_e_rr) + rho_e_internal_avg = 0.5f0 * (rho_e_internal_ll + rho_e_internal_rr) p_v_dot_n_avg = 0.5f0 * (p_ll * v_dot_n_rr + p_rr * v_dot_n_ll) # chain rule from Terashima drho_e_internal_drho_p_ll = drho_e_internal_drho_at_const_p(V_ll, T_ll, eos) drho_e_internal_drho_p_rr = drho_e_internal_drho_at_const_p(V_rr, T_rr, eos) - rho_e_internal_avg_corrected = (rho_e_avg - + rho_e_internal_avg_corrected = (rho_e_internal_avg - 0.25f0 * (drho_e_internal_drho_p_rr - drho_e_internal_drho_p_ll) * From 74071a3c164c44db496ef752a0483e20f256a7d7 Mon Sep 17 00:00:00 2001 From: Jesse Chan <1156048+jlchan@users.noreply.github.com> Date: Wed, 11 Feb 2026 10:21:57 -0600 Subject: [PATCH 285/360] Update src/equations/nonideal_compressible_euler_2d.jl Co-authored-by: Joshua Lampert <51029046+JoshuaLampert@users.noreply.github.com> --- src/equations/nonideal_compressible_euler_2d.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/equations/nonideal_compressible_euler_2d.jl b/src/equations/nonideal_compressible_euler_2d.jl index 747a9bfa715..91bb30edb3e 100644 --- a/src/equations/nonideal_compressible_euler_2d.jl +++ b/src/equations/nonideal_compressible_euler_2d.jl @@ -259,7 +259,7 @@ function flux_terashima_etal(u_ll, u_rr, normal_direction::AbstractVector, v1_avg = 0.5f0 * (v1_ll + v1_rr) v2_avg = 0.5f0 * (v2_ll + v2_rr) p_avg = 0.5f0 * (p_ll + p_rr) - rho_e_avg = 0.5f0 * (rho_e_ll + rho_e_rr) + rho_e_internal_avg = 0.5f0 * (rho_e_internal_ll + rho_e_internal_rr) p_v_dot_n_avg = 0.5f0 * (p_ll * v_dot_n_rr + p_rr * v_dot_n_ll) # chain rule from Terashima From aacc0ebc794727f8cf6238e379db1cef3df95711 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Wed, 11 Feb 2026 10:33:19 -0600 Subject: [PATCH 286/360] fix rho_e -> rho_e_internal --- .../nonideal_compressible_euler_1d.jl | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/equations/nonideal_compressible_euler_1d.jl b/src/equations/nonideal_compressible_euler_1d.jl index ccf937af61a..31c7d499fc2 100644 --- a/src/equations/nonideal_compressible_euler_1d.jl +++ b/src/equations/nonideal_compressible_euler_1d.jl @@ -117,15 +117,15 @@ Approximately pressure equilibrium preserving with conservation (APEC) flux from rho_ll = u_ll[1] rho_rr = u_rr[1] - rho_e_ll = energy_internal(u_ll, equations) - rho_e_rr = energy_internal(u_rr, equations) + rho_e_internal_ll = energy_internal(u_ll, equations) + rho_e_internal_rr = energy_internal(u_rr, equations) p_ll = pressure(V_ll, T_ll, eos) p_rr = pressure(V_rr, T_rr, eos) rho_avg = 0.5f0 * (rho_ll + rho_rr) v1_avg = 0.5f0 * (v1_ll + v1_rr) p_avg = 0.5f0 * (p_ll + p_rr) - rho_e_avg = 0.5f0 * (rho_e_ll + rho_e_rr) + rho_e_internal_avg = 0.5f0 * (rho_e_internal_ll + rho_e_internal_rr) p_v1_avg = 0.5f0 * (p_ll * v1_rr + p_rr * v1_ll) # chain rule from Terashima @@ -137,7 +137,7 @@ Approximately pressure equilibrium preserving with conservation (APEC) flux from # for an ideal gas EOS. drho_e_internal_drho_p_ll = drho_e_internal_drho_at_const_p(V_ll, T_ll, eos) drho_e_internal_drho_p_rr = drho_e_internal_drho_at_const_p(V_rr, T_rr, eos) - rho_e_internal_corrected_v1_avg = (rho_e_avg - + rho_e_internal_corrected_v1_avg = (rho_e_internal_avg - 0.25f0 * (drho_e_internal_drho_p_rr - drho_e_internal_drho_p_ll) * @@ -171,25 +171,25 @@ by Terashima, Ly, Ihme (2025). rho_ll, rho_v1_ll, _ = u_ll rho_rr, rho_v1_rr, _ = u_rr - rho_e_ll = energy_internal(u_ll, equations) - rho_e_rr = energy_internal(u_rr, equations) + rho_e_internal_ll = energy_internal(u_ll, equations) + rho_e_internal_rr = energy_internal(u_rr, equations) p_ll = pressure(V_ll, T_ll, eos) p_rr = pressure(V_rr, T_rr, eos) v1_avg = 0.5f0 * (v1_ll + v1_rr) p_avg = 0.5f0 * (p_ll + p_rr) - rho_e_avg = 0.5f0 * (rho_e_ll + rho_e_rr) + rho_e_internal_avg = 0.5f0 * (rho_e_internal_ll + rho_e_internal_rr) # chain rule from Terashima # Note that `drho_e_internal_drho_p`, i.e., the derivative of the # internal energy density with respect to the density at # constant pressure is zero for an ideal gas EOS. Thus, # the following mean value reduces to - # rho_e_internal_corrected_v1_avg = rho_e_avg * v1_avg + # rho_e_internal_corrected_v1_avg = rho_e_internal_avg * v1_avg # for an ideal gas EOS. drho_e_internal_drho_p_ll = drho_e_internal_drho_at_const_p(V_ll, T_ll, eos) drho_e_internal_drho_p_rr = drho_e_internal_drho_at_const_p(V_rr, T_rr, eos) - rho_e_internal_corrected_v1_avg = (rho_e_avg - + rho_e_internal_corrected_v1_avg = (rho_e_internal_avg - 0.25f0 * (drho_e_internal_drho_p_rr - drho_e_internal_drho_p_ll) * From 88864767cd8b9894b4e82ee78eb5274522c9d5d1 Mon Sep 17 00:00:00 2001 From: Joshua Lampert <51029046+JoshuaLampert@users.noreply.github.com> Date: Wed, 11 Feb 2026 17:50:42 +0100 Subject: [PATCH 287/360] Apply suggestions from code review --- src/equations/nonideal_compressible_euler_2d.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/equations/nonideal_compressible_euler_2d.jl b/src/equations/nonideal_compressible_euler_2d.jl index 91bb30edb3e..ece5e072a74 100644 --- a/src/equations/nonideal_compressible_euler_2d.jl +++ b/src/equations/nonideal_compressible_euler_2d.jl @@ -310,7 +310,7 @@ function flux_central_terashima_etal(u_ll, u_rr, orientation::Int, v1_avg = 0.5f0 * (v1_ll + v1_rr) v2_avg = 0.5f0 * (v2_ll + v2_rr) p_avg = 0.5f0 * (p_ll + p_rr) - rho_e_internal_avg = 0.5f0 * (rho_internal_e_ll + rho_e_internal_rr) + rho_e_internal_avg = 0.5f0 * (rho_e_internal_ll + rho_e_internal_rr) # chain rule from Terashima drho_e_internal_drho_p_ll = drho_e_internal_drho_at_const_p(V_ll, T_ll, eos) From 156279fde754bcfcf73314f595fc75e568054a7c Mon Sep 17 00:00:00 2001 From: Jesse Chan <1156048+jlchan@users.noreply.github.com> Date: Wed, 11 Feb 2026 11:15:51 -0600 Subject: [PATCH 288/360] Update test/test_tree_2d_euler.jl Co-authored-by: Joshua Lampert <51029046+JoshuaLampert@users.noreply.github.com> --- test/test_tree_2d_euler.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_tree_2d_euler.jl b/test/test_tree_2d_euler.jl index 825e4b89dfc..37df79487f5 100644 --- a/test/test_tree_2d_euler.jl +++ b/test/test_tree_2d_euler.jl @@ -154,7 +154,7 @@ end @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_peng_robinson_transcritical_mixing.jl"), tspan=(0.0, 0.0003), - # note that errors are large because the solution values are O(1e5)-O(1e7) + # note that errors are large because the solution values are of the order 1e5-1e7 l2=[ 0.8908754595982343, 274.62878855174165, From f45998859bfe5d16dfe6eeb6dbb1f42fca038eea Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Wed, 11 Feb 2026 11:23:13 -0600 Subject: [PATCH 289/360] add type tests --- test/test_type.jl | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/test/test_type.jl b/test/test_type.jl index c06dfe6c3b2..0df75e97c82 100644 --- a/test/test_type.jl +++ b/test/test_type.jl @@ -264,6 +264,52 @@ isdir(outdir) && rm(outdir, recursive = true) end end + @timed_testset "NonIdeal Compressible Euler 2D" begin + for RealT in (Float32, Float64) + equations_ideal_gas = @inferred NonIdealCompressibleEulerEquations2D(IdealGas(RealT(2))) + a, b, gamma, R = RealT.((0.0, 0.0, 1.4, 287)) + equations_vdw = @inferred NonIdealCompressibleEulerEquations1D(VanDerWaals(; a, + b, + gamma, + R)) + + for equations in (equations_ideal_gas, equations_vdw) + x = SVector(zero(RealT)) + t = zero(RealT) + u = u_ll = u_rr = u_inner = cons = SVector(one(RealT), one(RealT), + one(RealT), one(RealT)) + orientation = 1 + direction = 1 + + surface_flux_function = flux_lax_friedrichs + + @test eltype(@inferred flux(u, orientation, equations)) == RealT + @test eltype(@inferred flux_terashima_etal(u, u, orientation, equations)) == + RealT + @test eltype(@inferred flux_central_terashima_etal(u, u, orientation, + equations)) == RealT + @test eltype(@inferred min_max_speed_davis(u_ll, u_rr, orientation, + equations)) == + RealT + + @test eltype(@inferred Trixi.max_abs_speeds(u, equations)) == RealT + @test eltype(@inferred cons2prim(u, equations)) == RealT + @test eltype(@inferred Trixi.cons2thermo(u, equations)) == RealT + @test eltype(@inferred prim2cons(u, equations)) == RealT + @test eltype(@inferred cons2entropy(u, equations)) == RealT + # TODO: if entropy2cons is implemented, add a test + + @test typeof(@inferred Trixi.density(u, equations)) == RealT + @test eltype(@inferred velocity(u, equations)) == RealT + @test typeof(@inferred velocity(u, orientation, equations)) == RealT + @test typeof(@inferred pressure(u, equations)) == RealT + @test typeof(@inferred density_pressure(u, equations)) == RealT + @test typeof(@inferred entropy(cons, equations)) == RealT + @test typeof(@inferred energy_internal(cons, equations)) == RealT + end + end + end + @timed_testset "Compressible Euler 2D" begin for RealT in (Float32, Float64) # set gamma = 2 for the coupling convergence test From 8a5dd2d7d6572b7eba17062e71952666e651d7be Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Thu, 12 Feb 2026 00:05:45 -0600 Subject: [PATCH 290/360] add 2D nonideal entropy potentials --- .../nonideal_compressible_euler_2d.jl | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/equations/nonideal_compressible_euler_2d.jl b/src/equations/nonideal_compressible_euler_2d.jl index ece5e072a74..59b6e87888f 100644 --- a/src/equations/nonideal_compressible_euler_2d.jl +++ b/src/equations/nonideal_compressible_euler_2d.jl @@ -568,4 +568,31 @@ end rho_e_internal = rho_e_total - 0.5f0 * (rho_v1^2 + rho_v2^2) / rho return rho_e_internal end + +""" + entropy_potential(u, orientation_or_normal_direction, equations::NonIdealCompressibleEulerEquations2D) + +Calculate the entropy potential, which for the compressible Euler equations with general +EOS is ``p v_i / T`` for the choice of entropy ``S(u) = \rho s``. +""" +@inline function entropy_potential(u, orientation::Int, + equations::NonIdealCompressibleEulerEquations2D) + eos = equations.equation_of_state + V, v1, v2, T = cons2thermo(u, equations) + p = pressure(V, T, eos) + if orientation == 1 + return p * v1 / T + else # if orientation == 2 + return p * v2 / T + end +end + +@inline function entropy_potential(u, normal_direction, + equations::NonIdealCompressibleEulerEquations2D) + eos = equations.equation_of_state + V, v1, v2, T = cons2thermo(u, equations) + v_normal = v1 * normal_direction[1] + v2 * normal_direction[2] + p = pressure(V, T, eos) + return p * v_normal / T +end end # @muladd From 629f9aa9f1600122d939533232eb03aa6b4c2556 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Thu, 12 Feb 2026 18:48:14 -0600 Subject: [PATCH 291/360] switch to passing in volume integrals instead of fluxes --- src/solvers/dg.jl | 47 ++++++++++------------- src/solvers/dgsem/calc_volume_integral.jl | 17 ++++---- 2 files changed, 29 insertions(+), 35 deletions(-) diff --git a/src/solvers/dg.jl b/src/solvers/dg.jl index 540bd229b7b..aa54aeb6698 100644 --- a/src/solvers/dg.jl +++ b/src/solvers/dg.jl @@ -283,45 +283,37 @@ function Base.show(io::IO, mime::MIME"text/plain", end """ - VolumeIntegralEntropyCorrection(indicator; - volume_flux_dg=flux_central, - volume_flux_fv=flux_lax_friedrichs) + VolumeIntegralEntropyCorrection(indicator, + volume_integral_default, + volume_integral_entropy_stable) Entropy correction volume integral type for DG methods using a convex blending of -the **first-order** finite volume method with numerical flux `volume_flux_fv` and the -[`VolumeIntegralFluxDifferencing`](@ref) with volume flux `volume_flux_dg`. This is -intended to be used with [`IndicatorEntropyCorrection`](@ref), which determines the -amount of blending based on the violation of a cell entropy equality by the volume -integral. +a `volume_integral_default` (for example, [`VolumeIntegralWeakForm`](@ref)) and +`volume_integral_entropy_stable` (for example, [`VolumeIntegralPureLGLFiniteVolume`](@ref) +with an entropy stable finite volume flux). + +This is intended to be used with [`IndicatorEntropyCorrection`](@ref), which determines the +amount of blending based on the violation of a cell entropy equality by the volume integral. -`scaling ≥ 1` scales the DG-FV blending parameter ``\\alpha``(see the -[tutorial on shock-capturing](https://trixi-framework.github.io/TrixiDocumentation/stable/tutorials/shock_capturing/#Shock-capturing-with-flux-differencing)) +The parameter `scaling ≥ 1` in [`IndicatorEntropyCorrection`](@ref) scales the DG-FV blending +parameter ``\\alpha``(see the [tutorial on shock-capturing](https://trixi-framework.github.io/TrixiDocumentation/stable/tutorials/shock_capturing/#Shock-capturing-with-flux-differencing)) by a constant, increasing the amount of the subcell FV added in (up to 1, i.e., pure subcell FV). -This can be used to add shock capturing-like behavior. -Note though that ``\\alpha`` is computed here from the entropy defect, **not** using [`IndicatorHennemannGassner`](@ref). +This can be used to add shock capturing-like behavior. Note though that ``\\alpha`` is computed +here from the entropy defect, **not** using [`IndicatorHennemannGassner`](@ref). The use of `VolumeIntegralEntropyCorrection` requires either `entropy_potential(u, orientation, equations)` for TreeMesh, or `entropy_potential(u, normal_direction, equations)` for other mesh types to be defined. """ -struct VolumeIntegralEntropyCorrection{VolumeFluxDG, VolumeFluxFV, Indicator} <: +struct VolumeIntegralEntropyCorrection{VolumeIntegralDefault, + VolumeIntegralEntropyStable, Indicator} <: AbstractVolumeIntegralShockCapturing - volume_flux_dg::VolumeFluxDG # symmetric, e.g. split-form or entropy-conservative - volume_flux_fv::VolumeFluxFV # non-symmetric in general, e.g. entropy-dissipative + volume_integral_default::VolumeIntegralDefault + volume_integral_entropy_stable::VolumeIntegralEntropyStable indicator::Indicator end -function VolumeIntegralEntropyCorrection(indicator; - volume_flux_dg = flux_central, - volume_flux_fv = flux_lax_friedrichs) - return VolumeIntegralEntropyCorrection{typeof(volume_flux_dg), - typeof(volume_flux_fv), - typeof(indicator)}(volume_flux_dg, - volume_flux_fv, - indicator) -end - function Base.show(io::IO, mime::MIME"text/plain", integral::VolumeIntegralEntropyCorrection) @nospecialize integral # reduce precompilation time @@ -330,8 +322,9 @@ function Base.show(io::IO, mime::MIME"text/plain", show(io, integral) else summary_header(io, "VolumeIntegralEntropyCorrection") - summary_line(io, "volume flux DG", integral.volume_flux_dg) - summary_line(io, "volume flux FV", integral.volume_flux_fv) + summary_line(io, "default volume integral", integral.volume_integral_default) + summary_line(io, "stable volume integral", + integral.volume_integral_entropy_stable) summary_line(io, "indicator", integral.indicator |> typeof |> nameof) show(increment_indent(io), mime, integral.indicator) summary_footer(io) diff --git a/src/solvers/dgsem/calc_volume_integral.jl b/src/solvers/dgsem/calc_volume_integral.jl index ee858bd6cdb..44eb8bb3a70 100644 --- a/src/solvers/dgsem/calc_volume_integral.jl +++ b/src/solvers/dgsem/calc_volume_integral.jl @@ -191,7 +191,7 @@ function calc_volume_integral!(du, u, mesh, have_nonconservative_terms, equations, volume_integral::VolumeIntegralEntropyCorrection, dg::DGSEM, cache) - (; volume_flux_dg, volume_flux_fv, indicator) = volume_integral + (; volume_integral_default, volume_integral_entropy_stable, indicator) = volume_integral (; scaling) = indicator (; alpha) = indicator.cache du_element_threaded = indicator.cache.volume_integral_values_threaded @@ -199,9 +199,10 @@ function calc_volume_integral!(du, u, mesh, resize!(alpha, nelements(dg, cache)) @threaded for element in eachelement(dg, cache) - flux_differencing_kernel!(du, u, element, mesh, - have_nonconservative_terms, equations, - volume_flux_dg, dg, cache) + # run default volume integral + volume_integral_kernel!(du, u, element, mesh, + have_nonconservative_terms, equations, + volume_integral_default, dg, cache) # Check entropy production of "high order" volume integral. # @@ -233,10 +234,10 @@ function calc_volume_integral!(du, u, mesh, # Reset pure flux-differencing volume integral du[.., element] .= zero(eltype(du)) - # Calculate FV volume integral contribution - fv_kernel!(du, u, mesh, - have_nonconservative_terms, equations, - volume_flux_fv, dg, cache, element) + # Calculate entropy stable volume integral contribution + volume_integral_kernel!(du, u, element, mesh, + have_nonconservative_terms, equations, + volume_integral_entropy_stable, dg, cache) # Calculate difference between high and low order FV integral; # this should be made entropy dissipative if entropy_residual > 0. From de603c69e2583633103fb533d40bf47836dd05a9 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Thu, 12 Feb 2026 18:48:23 -0600 Subject: [PATCH 292/360] update tests --- ...r_euler_modified_sod_entropy_correction.jl | 9 ++++-- test/test_tree_1d_euler.jl | 4 +-- test/test_tree_2d_euler.jl | 30 +++++++++---------- 3 files changed, 23 insertions(+), 20 deletions(-) diff --git a/examples/tree_1d_dgsem/elixir_euler_modified_sod_entropy_correction.jl b/examples/tree_1d_dgsem/elixir_euler_modified_sod_entropy_correction.jl index 819bf85e3c7..418249e2760 100644 --- a/examples/tree_1d_dgsem/elixir_euler_modified_sod_entropy_correction.jl +++ b/examples/tree_1d_dgsem/elixir_euler_modified_sod_entropy_correction.jl @@ -20,9 +20,12 @@ volume_flux = flux_central surface_flux = flux_lax_friedrichs basis = LobattoLegendreBasis(3) indicator = IndicatorEntropyCorrection(equations, basis) -volume_integral = VolumeIntegralEntropyCorrection(indicator; - volume_flux_dg = volume_flux, - volume_flux_fv = surface_flux) +volume_integral_default = VolumeIntegralFluxDifferencing(volume_flux) +volume_integral_entropy_stable = VolumeIntegralPureLGLFiniteVolumeO2(basis, + volume_flux_fv = surface_flux) +volume_integral = VolumeIntegralEntropyCorrection(volume_integral_default, + volume_integral_entropy_stable, + indicator) solver = DGSEM(basis, surface_flux, volume_integral) coordinates_min = 0.0 diff --git a/test/test_tree_1d_euler.jl b/test/test_tree_1d_euler.jl index e5994870541..119771cb2c6 100644 --- a/test/test_tree_1d_euler.jl +++ b/test/test_tree_1d_euler.jl @@ -663,8 +663,8 @@ end @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_modified_sod_entropy_correction.jl"), tspan=(0.0, 0.1), - l2=[0.17918607435161274, 0.3021079725486445, 0.5919229917120694], - linf=[0.6787210671799078, 0.8094457929285299, 1.9399759495474393]) + l2=[0.17986404413131177, 0.3020862626945975, 0.5933007649757316], + linf=[0.6864216508255041, 0.8099707951909875, 1.9586133195104065]) # Ensure that we do not have excessive memory allocations # (e.g., from type instabilities) diff --git a/test/test_tree_2d_euler.jl b/test/test_tree_2d_euler.jl index a462790f524..ca463661c26 100644 --- a/test/test_tree_2d_euler.jl +++ b/test/test_tree_2d_euler.jl @@ -649,24 +649,24 @@ end # adding `scaling = 2` increases the amount of subcell FV blended in by # a factor of 2. If this is not added, the KHI simulation crashes with a # positivity violation at some time t < 3. - solver=DGSEM(LobattoLegendreBasis(3), - flux_lax_friedrichs, - VolumeIntegralEntropyCorrection(IndicatorEntropyCorrection(equations, - LobattoLegendreBasis(3); - scaling = 2), - volume_flux_dg = flux_central, - volume_flux_fv = flux_lax_friedrichs)), + solver=DGSEM(basis, flux_lax_friedrichs, + VolumeIntegralEntropyCorrection(VolumeIntegralWeakForm(), + VolumeIntegralPureLGLFiniteVolumeO2(basis, + volume_flux_fv = flux_lax_friedrichs), + IndicatorEntropyCorrection(equations, + basis; + scaling = 2))), l2=[ - 0.5478424814984363, - 0.2040045541508178, - 0.22224623172603333, - 0.1688290550316014 + 0.5511948462411194, + 0.20453805360357122, + 0.22336234116471693, + 0.17123395645103476 ], linf=[ - 2.0077477196844606, - 0.7856525305298842, - 0.6907389422800942, - 0.6256780117443306 + 1.9695032535249968, + 0.8157528342084577, + 0.6872258423454486, + 0.628912187598794 ]) end From 9c9a4e0099a6d034a7a7cd8bade484db7797c9b2 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Thu, 12 Feb 2026 18:56:50 -0600 Subject: [PATCH 293/360] add jet example --- examples/tree_2d_dgsem/elixir_euler_jet.jl | 151 +++++++++++++++++++++ 1 file changed, 151 insertions(+) create mode 100644 examples/tree_2d_dgsem/elixir_euler_jet.jl diff --git a/examples/tree_2d_dgsem/elixir_euler_jet.jl b/examples/tree_2d_dgsem/elixir_euler_jet.jl new file mode 100644 index 00000000000..8716bb08e4a --- /dev/null +++ b/examples/tree_2d_dgsem/elixir_euler_jet.jl @@ -0,0 +1,151 @@ +using OrdinaryDiffEqSSPRK +using Trixi + +############################################################################### +# semidiscretization of the compressible Euler equations + +eos = IdealGas(5 / 3) +eos = PengRobinson() +equations = NonIdealCompressibleEulerEquations2D(eos) + +function initial_condition_jet(x, t, equations::NonIdealCompressibleEulerEquations2D) + eos = equations.equation_of_state + + v1, v2 = 0.0, 0.0 + + # rho = 45 # kg/m3 + # T = 290.2 # K, about 4 MPa + + # from Ma et al + rho = 45.5 + T = 300 + + V = inv(rho) + e_internal = energy_internal_specific(V, T, eos) + # cv = 287 / (equations.gamma - 1.0) + # e_internal = cv * T + + return SVector(rho, rho * v1, rho * v2, rho * (e_internal + 0.5 * (v1^2 + v2^2))) +end + +function boundary_condition_jet(x, t, equations::NonIdealCompressibleEulerEquations2D) + eos = equations.equation_of_state + + h = 0.0022 # 2.2 mm + if abs(x[2]) < 0.5 * h + rho = 500 # kg / m3 + v1 = 100 # m/s + v2 = 0.0 + T = 124.6 # K + + # from Ma et al + rho = 911 + T = 80 + + V = inv(rho) + e_internal = energy_internal_specific(V, T, eos) + + # # to match pressure inside for ideal gas + # p_interior = pressure(initial_condition_jet(x, t, equations), equations) + # e_internal = Trixi.energy_internal(prim2cons(SVector(rho, 0, 0, p_interior), equations), + # equations) / rho + + return SVector(rho, rho * v1, rho * v2, rho * (e_internal + 0.5 * (v1^2 + v2^2))) + else + return initial_condition_jet(x, t, equations) + end +end + +# u_jet = boundary_condition_jet([0,0], 0, equations) +# u_interior = initial_condition_jet([0, 0], 0, equations) +# pressure(u_interior, equations) +# pressure(u_jet, equations) + +@inline function boundary_condition_subsonic_outflow(u_inner, orientation::Integer, + direction, x, t, + surface_flux_function, + equations::NonIdealCompressibleEulerEquations2D) + V, v1, v2, T = cons2thermo(u_inner, equations) + + # For subsonic boundary: take pressure from initial condition + p = pressure(V, T, equations.equation_of_state) + + # invert for new temperature given p, V + T = 1 + tol = 100 * eps(RealT) + dp = pressure(V, T, eos) - p + iter = 1 + while abs(dp) / abs(p) > tol && iter < 100 + dp = pressure(V, T, eos) - p + dpdT_V = ForwardDiff.derivative(T -> pressure(V, T, eos), T) + T = max(tol, T - dp / dpdT_V) + iter += 1 + end + if iter == 100 + @warn "Solver for temperature(V, p) did not converge" + end + + thermo = SVector(V, v1, v2, T) + u_surface = thermo2cons(thermo, equations) + + return flux(u_surface, orientation, equations) +end + +initial_condition = initial_condition_jet + +volume_flux = flux_central_terashima_etal +surface_flux = flux_lax_friedrichs +basis = LobattoLegendreBasis(3) +indicator = IndicatorEntropyCorrection(equations, basis; scaling = 2.5) +volume_integral_default = VolumeIntegralFluxDifferencing(volume_flux) +volume_integral_entropy_stable = VolumeIntegralPureLGLFiniteVolumeO2(basis, + volume_flux_fv = surface_flux) + +volume_integral = VolumeIntegralEntropyCorrection(volume_integral_default, + volume_integral_entropy_stable, + indicator) + +solver = DGSEM(basis, surface_flux, volume_integral) + +h = 0.0022 # 2.2 mm +coordinates_min = (0.0, -16 * h) +coordinates_max = (32 * h, 16 * h) +mesh = TreeMesh(coordinates_min, coordinates_max, + initial_refinement_level = 7, + n_cells_max = 30_000, + periodicity = (false, true)) + +boundary_condition_inflow = BoundaryConditionDirichlet(boundary_condition_jet) +# boundary_condition_inflow = boundary_condition_subsonic_inflow + +boundary_conditions = (x_neg = boundary_condition_inflow, + x_pos = boundary_condition_slip_wall) +semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver; + boundary_conditions = boundary_conditions) + +############################################################################### +# ODE solvers, callbacks etc. + +tspan = (0.0, 1e-3) +ode = semidiscretize(semi, tspan) + +summary_callback = SummaryCallback() + +analysis_interval = 2000 +analysis_callback = AnalysisCallback(semi, interval = analysis_interval) + +alive_callback = AliveCallback(analysis_interval = analysis_interval) + +############################################################################### +# run the simulation + +callbacks = CallbackSet(summary_callback, + analysis_callback, + alive_callback) +sol = solve(ode, SSPRK43(); + abstol = 1e-6, reltol = 1e-4, + saveat = LinRange(tspan..., 10), + ode_default_options()..., callback = callbacks); + +using Plots +plot(ScalarPlotData2D(Trixi.density, sol.u[end], semi), dpi = 300) From f2c284e2b3552ced3cf456b36b36316f12a97ffe Mon Sep 17 00:00:00 2001 From: jlchan Date: Thu, 12 Feb 2026 19:06:58 -0600 Subject: [PATCH 294/360] adding daru tenaud --- .../elixir_navier_stokes_daru_tenaud.jl | 172 ++++++++++++++++++ 1 file changed, 172 insertions(+) create mode 100644 examples/tree_2d_dgsem/elixir_navier_stokes_daru_tenaud.jl diff --git a/examples/tree_2d_dgsem/elixir_navier_stokes_daru_tenaud.jl b/examples/tree_2d_dgsem/elixir_navier_stokes_daru_tenaud.jl new file mode 100644 index 00000000000..04d015170de --- /dev/null +++ b/examples/tree_2d_dgsem/elixir_navier_stokes_daru_tenaud.jl @@ -0,0 +1,172 @@ +using OrdinaryDiffEqLowStorageRK +using OrdinaryDiffEqSSPRK +using Trixi + +############################################################################### +# semidiscretization of the ideal compressible Navier-Stokes equations + +prandtl_number() = 0.73 + +function initial_condition_kelvin_helmholtz_instability(x, t, + equations::CompressibleEulerEquations2D) + # change discontinuity to tanh + # typical resolution 128^2, 256^2 + # domain size is [-1,+1]^2 + slope = 15 + B = tanh(slope * x[2] + 7.5) - tanh(slope * x[2] - 7.5) + rho = 0.5 + 0.75 * B + v1 = 0.5 * (B - 1) + v2 = 0.1 * sin(2 * pi * x[1]) + p = 1.0 + return prim2cons(SVector(rho, v1, v2, p), equations) +end + +function initial_condition_blast_wave(x, t, equations::CompressibleEulerEquations2D) + RealT = eltype(x) + inicenter = SVector(0.25, 0.25) + x_norm = x[1] - inicenter[1] + y_norm = x[2] - inicenter[2] + r = sqrt(x_norm^2 + y_norm^2) + phi = atan(y_norm, x_norm) + sin_phi, cos_phi = sincos(phi) + + # Calculate primitive variables + r0 = 0.25f0 + rho = r > r0 ? one(1e-1) : RealT(1.1691) + v1 = r > r0 ? zero(RealT) : RealT(0.1882) * cos_phi + v2 = r > r0 ? zero(RealT) : RealT(0.1882) * sin_phi + # p = r > 0.25f0 ? RealT(1.0E-2) : RealT(1.245) + p = r > r0 ? RealT(1e-1) : RealT(1.245) + + return prim2cons(SVector(rho, v1, v2, p), equations) +end + +function initial_condition_daru(x, t, equations) + RealT = eltype(x) + v1 = zero(RealT) + v2 = zero(RealT) + + rho_rr = 120.0 + # rho_rr = 60.0 + + rho = x[1] > 0.5f0 ? 1.2 : rho_rr + p = x[1] > 0.5f0 ? 1.2 / equations.gamma : rho_rr / equations.gamma + # rho = x[1] > 0.5f0 ? 1.2 : 24.0 + # p = x[1] > 0.5f0 ? 1.2 / equations.gamma : 24.0 / equations.gamma + # rho = 59.4 * tanh(-25*(x[1] - 0.5)) + 60.6 + if abs(x[1] - 0.5f0) < 1e3 * eps() + rho = 0.5 * (1.2 + rho_rr) + end + p = rho / equations.gamma + + + return prim2cons(SVector(rho, v1, v2, p), equations) +end + +coordinates_min = (0.0, 0.0) # minimum coordinates (min(x), min(y)) +coordinates_max = (1.0, 1.0) # maximum coordinates (max(x), max(y)) +tspan = (0.0, 1.0) +initial_condition = initial_condition_daru +periodicity = (false, false) +# mu() = 5e-3 # Re = 200 +# mu() = 2e-3 # Re = 500 +mu() = 1e-3 # Re = 1000 + +# coordinates_min = (-1.0, -1.0) # minimum coordinates (min(x), min(y)) +# coordinates_max = (1.0, 1.0) # maximum coordinates (max(x), max(y)) +# tspan = (0.0, 1.5) +# initial_condition = initial_condition_blast_wave +# # tspan = (0.0, 5.0) +# # initial_condition = initial_condition_kelvin_helmholtz_instability +# periodicity = (true, true) +# # periodicity = (false, false) + +equations = CompressibleEulerEquations2D(1.4) +equations_parabolic = CompressibleNavierStokesDiffusion2D(equations, mu = mu(), + Prandtl = prandtl_number(), + gradient_variables = GradientVariablesEntropy()) + +volume_flux = flux_central +surface_flux = flux_lax_friedrichs +basis = LobattoLegendreBasis(3) +volume_integral = VolumeIntegralEntropyCorrection(equations, basis; + volume_flux_dg = volume_flux, + volume_flux_fv = surface_flux) +dg = DGSEM(basis, surface_flux, volume_integral) + + +# Create a uniformly refined mesh with periodic boundaries +initial_refinement_level = 9 +mesh = TreeMesh(coordinates_min, coordinates_max, + initial_refinement_level = initial_refinement_level, + periodicity=periodicity, n_cells_max = 400_000) + +# BC types +boundary_condition_noslip_wall = + BoundaryConditionNavierStokesWall(NoSlip((x, t, equations_parabolic) -> (0.0, 0.0)), + Adiabatic((x, t, equations_parabolic) -> 0.0)) + +# define inviscid boundary conditions +boundary_conditions_hyperbolic = (; x_neg = boundary_condition_slip_wall, + x_pos = boundary_condition_slip_wall, + y_neg = boundary_condition_slip_wall, + y_pos = boundary_condition_slip_wall) + +# define viscous boundary conditions +boundary_conditions_parabolic = (; x_neg = boundary_condition_noslip_wall, + x_pos = boundary_condition_noslip_wall, + y_neg = boundary_condition_noslip_wall, + y_pos = boundary_condition_noslip_wall) + +# solver_parabolic = ViscousFormulationBassiRebay1() +solver_parabolic = ViscousFormulationLocalDG() +semi = SemidiscretizationHyperbolicParabolic(mesh, (equations, equations_parabolic), + initial_condition, dg; + solver_parabolic = solver_parabolic, + boundary_conditions = (boundary_conditions_hyperbolic, + boundary_conditions_parabolic)) + + +############################################################################### +# ODE solvers, callbacks etc. + +# Create ODE problem with time span `tspan` +ode = semidiscretize(semi, tspan) + +summary_callback = SummaryCallback() +alive_callback = AliveCallback(alive_interval = 100) +callbacks = CallbackSet(summary_callback, alive_callback) +# analysis_interval = 1000 +# analysis_callback = AnalysisCallback(semi, interval = analysis_interval) +# callbacks = CallbackSet(summary_callback, alive_callback, analysis_callback) #, amr_callback) + +############################################################################### +# run the simulation + +# stage_limiter! = PositivityPreservingLimiterZhangShu(thresholds = (1.0e-6, 1.0e-6), +# variables = (Trixi.density, pressure)) +# solver = SSPRK43(stage_limiter!, stage_limiter!) +solver = SSPRK43() + +sol = solve(ode, solver; abstol = 1e-6, reltol = 1e-4, + ode_default_options()..., callback = callbacks) + +using JLD2 +@save "DaruTenaudRe1000_VolumeIntegralEntropyCorrection.jld2" sol +#@save "DaruTenaudRe1000_polydeg_3_elements_512.jld2" sol +# @save "DaruTenaudRe1000_polydeg_$(Trixi.polydeg(dg.basis))_elements_$(2^initial_refinement_level).jld2" sol +# @save "DaruTenaudRe1000_polydeg_$(Trixi.polydeg(dg.basis))_elements_$(2^initial_refinement_level)_shock_capturing_amax_p5.jld2" sol + +using Plots +# plot(PlotData2D(sol)["rho"]) + +u = Trixi.wrap_array(sol.u[end], semi) +T = [Trixi.temperature(get_node_vars(u, equations, dg, i, j, elements), equations_parabolic) + for i in eachnode(dg), j in eachnode(dg), elements in eachelement(dg, semi.cache)] +plot(ScalarPlotData2D(T, semi)) +plot!(clims=(0.4, 1.2), xlims=(0.4, 1.0), ylims=(0, 0.25)) + +# ECAV_coefficient = [semi.cache.artificial_viscosity.coefficients[elements] +# for i in eachnode(dg), j in eachnode(dg), elements in eachelement(dg, semi.cache)] +# plot(ScalarPlotData2D(ECAV_coefficient, semi)) + From e070f05c9e205a549e454a000d9b3e4370e1a684 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Thu, 12 Feb 2026 20:29:32 -0600 Subject: [PATCH 295/360] fix test --- test/test_tree_1d_euler.jl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/test_tree_1d_euler.jl b/test/test_tree_1d_euler.jl index 119771cb2c6..1ae14ad9544 100644 --- a/test/test_tree_1d_euler.jl +++ b/test/test_tree_1d_euler.jl @@ -688,10 +688,10 @@ end @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_nonideal_density_wave.jl"), solver=DGSEM(LobattoLegendreBasis(3), flux_lax_friedrichs, - VolumeIntegralEntropyCorrection(IndicatorEntropyCorrection(equations, - LobattoLegendreBasis(3)), - volume_flux_dg = flux_central, - volume_flux_fv = flux_lax_friedrichs)), + VolumeIntegralEntropyCorrection(VolumeIntegralWeakForm(), + VolumeIntegralPureLGLFiniteVolume(flux_lax_friedrichs), + IndicatorEntropyCorrection(equations, + LobattoLegendreBasis(3)))), tspan=(0.0, 0.1), l2=[ 0.0017122131198515455, From c2eea3ae93a356bb0f6c673f9219f3300ade97ae Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Fri, 13 Feb 2026 08:25:12 -0600 Subject: [PATCH 296/360] fix 2d test --- test/test_tree_2d_euler.jl | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/test/test_tree_2d_euler.jl b/test/test_tree_2d_euler.jl index ca463661c26..5742d90a3cf 100644 --- a/test/test_tree_2d_euler.jl +++ b/test/test_tree_2d_euler.jl @@ -114,10 +114,11 @@ end @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_density_wave.jl"), solver=DGSEM(LobattoLegendreBasis(3), flux_lax_friedrichs, - VolumeIntegralEntropyCorrection(IndicatorEntropyCorrection(equations, - LobattoLegendreBasis(3)), - volume_flux_dg = flux_central, - volume_flux_fv = flux_lax_friedrichs)), + VolumeIntegralEntropyCorrection(VolumeIntegralWeakForm(), + VolumeIntegralPureLGLFiniteVolume(flux_lax_friedrichs), + IndicatorEntropyCorrection(equations, + basis; + scaling = 2))), tspan=(0.0, 0.1), l2=[ 0.02871253948076796, From 97504dd3f3573bf713745dec1d2d92b761c5b995 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Fri, 13 Feb 2026 08:25:16 -0600 Subject: [PATCH 297/360] format --- test/test_tree_1d_euler.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_tree_1d_euler.jl b/test/test_tree_1d_euler.jl index 1ae14ad9544..a2905e9fa2e 100644 --- a/test/test_tree_1d_euler.jl +++ b/test/test_tree_1d_euler.jl @@ -688,7 +688,7 @@ end @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_nonideal_density_wave.jl"), solver=DGSEM(LobattoLegendreBasis(3), flux_lax_friedrichs, - VolumeIntegralEntropyCorrection(VolumeIntegralWeakForm(), + VolumeIntegralEntropyCorrection(VolumeIntegralWeakForm(), VolumeIntegralPureLGLFiniteVolume(flux_lax_friedrichs), IndicatorEntropyCorrection(equations, LobattoLegendreBasis(3)))), From 4e214bcc45579196d77305bf801f30fd292fe918 Mon Sep 17 00:00:00 2001 From: Jesse Chan <1156048+jlchan@users.noreply.github.com> Date: Fri, 13 Feb 2026 10:49:15 -0600 Subject: [PATCH 298/360] Apply suggestions from code review Co-authored-by: Hendrik Ranocha --- src/auxiliary/math.jl | 4 ++-- src/callbacks_step/analysis_dg1d.jl | 2 +- src/equations/compressible_euler_1d.jl | 2 +- src/solvers/dgsem/calc_volume_integral.jl | 2 ++ 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/auxiliary/math.jl b/src/auxiliary/math.jl index 9bff0a557ed..8ee1ff5f2fa 100644 --- a/src/auxiliary/math.jl +++ b/src/auxiliary/math.jl @@ -446,7 +446,7 @@ end return 0.5f0 * (sign(sl) + sign(sr)) * max(abs(sl), abs(sr)) end -# regularized approximation to the ratio a / b which is numerically stable -# for b close to zero. +# Regularized approximation to the ratio a / b, which is numerically stable +# for b close to zero and approaches zero as b -> 0. @inline regularized_ratio(a, b) = a * b / (eps(typeof(b)) + b^2) end # @muladd diff --git a/src/callbacks_step/analysis_dg1d.jl b/src/callbacks_step/analysis_dg1d.jl index b40203419fb..5f61f657573 100644 --- a/src/callbacks_step/analysis_dg1d.jl +++ b/src/callbacks_step/analysis_dg1d.jl @@ -120,7 +120,7 @@ function calc_error_norms(func, u, t, analyzer, end # Use quadrature to numerically integrate a single element. -# We do not multiply with the Jacobian to stay in reference space. +# We do not multiply by the Jacobian to stay in reference space. # This avoids the need to divide the RHS of the DG scheme by the Jacobian when computing # the time derivative of entropy, see `entropy_change_reference_element`. function integrate_reference_element(func::Func, u, element, diff --git a/src/equations/compressible_euler_1d.jl b/src/equations/compressible_euler_1d.jl index 4583482332f..957fe3f407b 100644 --- a/src/equations/compressible_euler_1d.jl +++ b/src/equations/compressible_euler_1d.jl @@ -1153,7 +1153,7 @@ end entropy_potential(u, orientation::Int, equations::AbstractCompressibleEulerEquations) Calculate the entropy potential, which for the compressible Euler equations is simply -the momentum for the choice of mathematical entropy ``S(u) = \frac{\rho s}{\gamma - 1}`` +the momentum for the choice of mathematical [`entropy`](@ref) ``S(u) = \frac{\rho s}{\gamma - 1}`` with thermodynamic entropy ``s = \ln(p) - \gamma \ln(\rho)``. ## References diff --git a/src/solvers/dgsem/calc_volume_integral.jl b/src/solvers/dgsem/calc_volume_integral.jl index 44eb8bb3a70..bf41dc743a0 100644 --- a/src/solvers/dgsem/calc_volume_integral.jl +++ b/src/solvers/dgsem/calc_volume_integral.jl @@ -232,6 +232,8 @@ function calc_volume_integral!(du, u, mesh, @views du_FD_element .= du[.., element] # Reset pure flux-differencing volume integral + # Note that this assumes that the volume terms are computed first, + # before any surface terms are added. du[.., element] .= zero(eltype(du)) # Calculate entropy stable volume integral contribution From 30c1b45ddbc6dab02b27bb26c2372bf2c967c8d6 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Fri, 13 Feb 2026 13:50:19 -0600 Subject: [PATCH 299/360] fix negative sign --- src/equations/nonideal_compressible_euler_1d.jl | 2 +- src/equations/nonideal_compressible_euler_2d.jl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/equations/nonideal_compressible_euler_1d.jl b/src/equations/nonideal_compressible_euler_1d.jl index 1db292ef8d3..c8ff53cff51 100644 --- a/src/equations/nonideal_compressible_euler_1d.jl +++ b/src/equations/nonideal_compressible_euler_1d.jl @@ -327,7 +327,7 @@ end entropy_potential(u, orientation::Int, equations::NonIdealCompressibleEulerEquations1D) Calculate the entropy potential, which for the compressible Euler equations with general -EOS is ``p v_i / T`` for the choice of entropy ``S(u) = \rho s``. +EOS is ``p v_i / T`` for the choice of entropy ``S(u) = -\rho s``. """ @inline function entropy_potential(u, orientation::Int, equations::NonIdealCompressibleEulerEquations1D) diff --git a/src/equations/nonideal_compressible_euler_2d.jl b/src/equations/nonideal_compressible_euler_2d.jl index 0303921e6f2..3eca3b5aa70 100644 --- a/src/equations/nonideal_compressible_euler_2d.jl +++ b/src/equations/nonideal_compressible_euler_2d.jl @@ -573,7 +573,7 @@ end entropy_potential(u, orientation_or_normal_direction, equations::NonIdealCompressibleEulerEquations2D) Calculate the entropy potential, which for the compressible Euler equations with general -EOS is ``p v_i / T`` for the choice of entropy ``S(u) = \rho s``. +EOS is ``p v_i / T`` for the choice of entropy ``S(u) = -\rho s``. """ @inline function entropy_potential(u, orientation::Int, equations::NonIdealCompressibleEulerEquations2D) From 28a81156376766d1c272192c305cdea39ebcf446 Mon Sep 17 00:00:00 2001 From: Jesse Chan <1156048+jlchan@users.noreply.github.com> Date: Fri, 13 Feb 2026 13:50:57 -0600 Subject: [PATCH 300/360] Update src/equations/compressible_euler_1d.jl Co-authored-by: Hendrik Ranocha --- src/equations/compressible_euler_1d.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/equations/compressible_euler_1d.jl b/src/equations/compressible_euler_1d.jl index 957fe3f407b..5012ba982f4 100644 --- a/src/equations/compressible_euler_1d.jl +++ b/src/equations/compressible_euler_1d.jl @@ -1150,7 +1150,8 @@ of total energy and kinetic energy. end @doc raw""" - entropy_potential(u, orientation::Int, equations::AbstractCompressibleEulerEquations) + entropy_potential(u, orientation::Int, + equations::AbstractCompressibleEulerEquations) Calculate the entropy potential, which for the compressible Euler equations is simply the momentum for the choice of mathematical [`entropy`](@ref) ``S(u) = \frac{\rho s}{\gamma - 1}`` From d48d212a2372d023d804697d84a03c4f9c2c6226 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Fri, 13 Feb 2026 15:19:05 -0600 Subject: [PATCH 301/360] remove VolumeIntegralEntropyCorrection from exports --- src/Trixi.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Trixi.jl b/src/Trixi.jl index 378140edcc8..13d87f30409 100644 --- a/src/Trixi.jl +++ b/src/Trixi.jl @@ -272,7 +272,7 @@ export DG, VolumeIntegralAdaptive, IndicatorEntropyChange, IndicatorHennemannGassner, VolumeIntegralUpwind, - VolumeIntegralEntropyCorrection, IndicatorEntropyCorrection, + IndicatorEntropyCorrection, IndicatorEntropyCorrectionWithShockCapturing SurfaceIntegralWeakForm, SurfaceIntegralStrongForm, SurfaceIntegralUpwind, MortarL2 From 04c5134c57c33cc2dd71cb9ce7b5d6ad26119497 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Fri, 13 Feb 2026 15:19:22 -0600 Subject: [PATCH 302/360] switch to using VolumeIntegralAdaptive --- test/test_tree_1d_euler.jl | 8 ++++---- test/test_tree_2d_euler.jl | 24 ++++++++++++------------ 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/test/test_tree_1d_euler.jl b/test/test_tree_1d_euler.jl index a2905e9fa2e..09b4f558fe1 100644 --- a/test/test_tree_1d_euler.jl +++ b/test/test_tree_1d_euler.jl @@ -688,10 +688,10 @@ end @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_nonideal_density_wave.jl"), solver=DGSEM(LobattoLegendreBasis(3), flux_lax_friedrichs, - VolumeIntegralEntropyCorrection(VolumeIntegralWeakForm(), - VolumeIntegralPureLGLFiniteVolume(flux_lax_friedrichs), - IndicatorEntropyCorrection(equations, - LobattoLegendreBasis(3)))), + VolumeIntegralAdaptive(IndicatorEntropyCorrection(equations, + LobattoLegendreBasis(3)), + VolumeIntegralWeakForm(), + VolumeIntegralPureLGLFiniteVolume(flux_lax_friedrichs))), tspan=(0.0, 0.1), l2=[ 0.0017122131198515455, diff --git a/test/test_tree_2d_euler.jl b/test/test_tree_2d_euler.jl index ac87a24be9b..f2a08f1b8b7 100644 --- a/test/test_tree_2d_euler.jl +++ b/test/test_tree_2d_euler.jl @@ -114,11 +114,11 @@ end @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_density_wave.jl"), solver=DGSEM(LobattoLegendreBasis(3), flux_lax_friedrichs, - VolumeIntegralEntropyCorrection(VolumeIntegralWeakForm(), - VolumeIntegralPureLGLFiniteVolume(flux_lax_friedrichs), - IndicatorEntropyCorrection(equations, - basis; - scaling = 2))), + VolumeIntegralAdaptive(IndicatorEntropyCorrection(equations, + basis; + scaling = 2), + VolumeIntegralWeakForm(), + VolumeIntegralPureLGLFiniteVolume(flux_lax_friedrichs))), tspan=(0.0, 0.1), l2=[ 0.02871253948076796, @@ -676,19 +676,19 @@ end @test_allocations(Trixi.rhs!, semi, sol, 1000) end -@trixi_testset "elixir_euler_kelvin_helmholtz_instability.jl (VolumeIntegralEntropyCorrection)" begin +@trixi_testset "elixir_euler_kelvin_helmholtz_instability.jl (with entropy correction)" begin @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_kelvin_helmholtz_instability.jl"), # adding `scaling = 2` increases the amount of subcell FV blended in by # a factor of 2. If this is not added, the KHI simulation crashes with a # positivity violation at some time t < 3. solver=DGSEM(basis, flux_lax_friedrichs, - VolumeIntegralEntropyCorrection(VolumeIntegralWeakForm(), - VolumeIntegralPureLGLFiniteVolumeO2(basis, - volume_flux_fv = flux_lax_friedrichs), - IndicatorEntropyCorrection(equations, - basis; - scaling = 2))), + VolumeIntegralAdaptive(IndicatorEntropyCorrection(equations, + basis; + scaling = 2), + VolumeIntegralWeakForm(), + VolumeIntegralPureLGLFiniteVolumeO2(basis, + volume_flux_fv = flux_lax_friedrichs))), l2=[ 0.5511948462411194, 0.20453805360357122, From 1a656ea1dddb69aaf3f8211e97b7bb23d4159792 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Fri, 13 Feb 2026 15:20:20 -0600 Subject: [PATCH 303/360] update docstring --- src/solvers/dgsem/indicators.jl | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/solvers/dgsem/indicators.jl b/src/solvers/dgsem/indicators.jl index 5183b271984..0ab0ae5fd55 100644 --- a/src/solvers/dgsem/indicators.jl +++ b/src/solvers/dgsem/indicators.jl @@ -372,12 +372,22 @@ Indicator used for entropy correction using subcell FV schemes, where the blending is determined so that the volume integral entropy production is the same or more than that of an EC scheme. -`scaling ≥ 1` scales the DG-FV blending parameter ``\\alpha``(see the -[tutorial on shock-capturing](https://trixi-framework.github.io/TrixiDocumentation/stable/tutorials/shock_capturing/#Shock-capturing-with-flux-differencing)) +This is intended to guide the convex blending of a `volume_integral_default` +(for example, [`VolumeIntegralWeakForm`](@ref)) and `volume_integral_stabilized` +(for example, [`VolumeIntegralPureLGLFiniteVolume`](@ref) with an entropy stable +finite volume flux). + +The parameter `scaling ≥ 1` in [`IndicatorEntropyCorrection`](@ref) scales the DG-FV blending +parameter ``\\alpha``(see the [tutorial on shock-capturing](https://trixi-framework.github.io/TrixiDocumentation/stable/tutorials/shock_capturing/#Shock-capturing-with-flux-differencing)) by a constant, increasing the amount of the subcell FV added in (up to 1, i.e., pure subcell FV). -This can be used to add shock capturing-like behavior. +This can be used to add shock capturing-like behavior. Note though that ``\\alpha`` is computed +here from the entropy defect, **not** using [`IndicatorHennemannGassner`](@ref). + +The use of `IndicatorEntropyCorrection` requires either + `entropy_potential(u, orientation, equations)` for TreeMesh, or + `entropy_potential(u, normal_direction, equations)` for other mesh types +to be defined. -See also [`VolumeIntegralEntropyCorrection`](@ref). """ struct IndicatorEntropyCorrection{Cache, ScalingT} <: AbstractIndicator cache::Cache From a5c44b27d1253774e6e4b1d816dd5c87ecf20d93 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Fri, 13 Feb 2026 15:20:29 -0600 Subject: [PATCH 304/360] add back comma --- src/Trixi.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Trixi.jl b/src/Trixi.jl index 13d87f30409..191ec5edd1f 100644 --- a/src/Trixi.jl +++ b/src/Trixi.jl @@ -272,7 +272,7 @@ export DG, VolumeIntegralAdaptive, IndicatorEntropyChange, IndicatorHennemannGassner, VolumeIntegralUpwind, - IndicatorEntropyCorrection, IndicatorEntropyCorrectionWithShockCapturing + IndicatorEntropyCorrection, IndicatorEntropyCorrectionWithShockCapturing, SurfaceIntegralWeakForm, SurfaceIntegralStrongForm, SurfaceIntegralUpwind, MortarL2 From f4fb6562489191044aec421f4ed1794b09ae9279 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Fri, 13 Feb 2026 15:21:51 -0600 Subject: [PATCH 305/360] remove unused `calc_volume_integral!` routines --- src/solvers/dgsem/calc_volume_integral.jl | 61 ++++++++++++----------- 1 file changed, 31 insertions(+), 30 deletions(-) diff --git a/src/solvers/dgsem/calc_volume_integral.jl b/src/solvers/dgsem/calc_volume_integral.jl index b620d002909..0e137c9201f 100644 --- a/src/solvers/dgsem/calc_volume_integral.jl +++ b/src/solvers/dgsem/calc_volume_integral.jl @@ -191,36 +191,37 @@ function calc_volume_integral!(du, u, mesh, return nothing end -function calc_volume_integral!(du, u, mesh, - have_nonconservative_terms, equations, - volume_integral::VolumeIntegralPureLGLFiniteVolume, - dg::DGSEM, cache) - @unpack volume_flux_fv = volume_integral - - # Calculate LGL FV volume integral - @threaded for element in eachelement(dg, cache) - fv_kernel!(du, u, mesh, - have_nonconservative_terms, equations, - volume_flux_fv, dg, cache, element, true) - end - - return nothing -end - -function calc_volume_integral!(du, u, mesh, - have_nonconservative_terms, equations, - volume_integral::VolumeIntegralPureLGLFiniteVolumeO2, - dg::DGSEM, cache) - @unpack sc_interface_coords, volume_flux_fv, reconstruction_mode, slope_limiter = volume_integral - - # Calculate LGL second-order FV volume integral - @threaded for element in eachelement(dg, cache) - fvO2_kernel!(du, u, mesh, - have_nonconservative_terms, equations, - volume_flux_fv, dg, cache, element, - sc_interface_coords, reconstruction_mode, slope_limiter, true) - end - +""" + VolumeIntegralEntropyCorrection(indicator, + volume_integral_default, + volume_integral_entropy_stable) + +Entropy correction volume integral type for DG methods using a convex blending of +a `volume_integral_default` (for example, [`VolumeIntegralWeakForm`](@ref)) and +`volume_integral_entropy_stable` (for example, [`VolumeIntegralPureLGLFiniteVolume`](@ref) +with an entropy stable finite volume flux). + +This is intended to be used with [`IndicatorEntropyCorrection`](@ref), which determines the +amount of blending based on the violation of a cell entropy equality by the volume integral. + +The parameter `scaling ≥ 1` in [`IndicatorEntropyCorrection`](@ref) scales the DG-FV blending +parameter ``\\alpha``(see the [tutorial on shock-capturing](https://trixi-framework.github.io/TrixiDocumentation/stable/tutorials/shock_capturing/#Shock-capturing-with-flux-differencing)) +by a constant, increasing the amount of the subcell FV added in (up to 1, i.e., pure subcell FV). +This can be used to add shock capturing-like behavior. Note though that ``\\alpha`` is computed +here from the entropy defect, **not** using [`IndicatorHennemannGassner`](@ref). + +The use of `VolumeIntegralEntropyCorrection` requires either + `entropy_potential(u, orientation, equations)` for TreeMesh, or + `entropy_potential(u, normal_direction, equations)` for other mesh types +to be defined. +""" + +const VolumeIntegralEntropyCorrection = VolumeIntegralAdaptive{<:IndicatorEntropyCorrection} + +function get_element_variables!(element_variables, u, mesh, equations, + volume_integral::VolumeIntegralEntropyCorrection, + dg, cache) + element_variables[:indicator_shock_capturing] = volume_integral.indicator.cache.alpha return nothing end From d634ccab3fd99dbc8905f7565a16c0e9d8764418 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Fri, 13 Feb 2026 15:22:27 -0600 Subject: [PATCH 306/360] remove VolumeIntegralEntropyCorrection superceded by VolumeIntegralAdaptive --- src/solvers/dg.jl | 56 ----------------------------------------------- 1 file changed, 56 deletions(-) diff --git a/src/solvers/dg.jl b/src/solvers/dg.jl index 7d646280f47..ac7910374eb 100644 --- a/src/solvers/dg.jl +++ b/src/solvers/dg.jl @@ -356,62 +356,6 @@ function create_cache(mesh, equations, return (; cache_default..., cache_stabilized...) end -""" - VolumeIntegralEntropyCorrection(indicator, - volume_integral_default, - volume_integral_entropy_stable) - -Entropy correction volume integral type for DG methods using a convex blending of -a `volume_integral_default` (for example, [`VolumeIntegralWeakForm`](@ref)) and -`volume_integral_entropy_stable` (for example, [`VolumeIntegralPureLGLFiniteVolume`](@ref) -with an entropy stable finite volume flux). - -This is intended to be used with [`IndicatorEntropyCorrection`](@ref), which determines the -amount of blending based on the violation of a cell entropy equality by the volume integral. - -The parameter `scaling ≥ 1` in [`IndicatorEntropyCorrection`](@ref) scales the DG-FV blending -parameter ``\\alpha``(see the [tutorial on shock-capturing](https://trixi-framework.github.io/TrixiDocumentation/stable/tutorials/shock_capturing/#Shock-capturing-with-flux-differencing)) -by a constant, increasing the amount of the subcell FV added in (up to 1, i.e., pure subcell FV). -This can be used to add shock capturing-like behavior. Note though that ``\\alpha`` is computed -here from the entropy defect, **not** using [`IndicatorHennemannGassner`](@ref). - -The use of `VolumeIntegralEntropyCorrection` requires either - `entropy_potential(u, orientation, equations)` for TreeMesh, or - `entropy_potential(u, normal_direction, equations)` for other mesh types -to be defined. -""" -struct VolumeIntegralEntropyCorrection{VolumeIntegralDefault, - VolumeIntegralEntropyStable, Indicator} <: - AbstractVolumeIntegralShockCapturing - volume_integral_default::VolumeIntegralDefault - volume_integral_entropy_stable::VolumeIntegralEntropyStable - indicator::Indicator -end - -function Base.show(io::IO, mime::MIME"text/plain", - integral::VolumeIntegralEntropyCorrection) - @nospecialize integral # reduce precompilation time - - if get(io, :compact, false) - show(io, integral) - else - summary_header(io, "VolumeIntegralEntropyCorrection") - summary_line(io, "default volume integral", integral.volume_integral_default) - summary_line(io, "stable volume integral", - integral.volume_integral_entropy_stable) - summary_line(io, "indicator", integral.indicator |> typeof |> nameof) - show(increment_indent(io), mime, integral.indicator) - summary_footer(io) - end -end - -function get_element_variables!(element_variables, u, mesh, equations, - volume_integral::VolumeIntegralEntropyCorrection, - dg, cache) - element_variables[:indicator_shock_capturing] = volume_integral.indicator.cache.alpha - return nothing -end - # Abstract supertype for first-order `VolumeIntegralPureLGLFiniteVolume` and # second-order `VolumeIntegralPureLGLFiniteVolumeO2` subcell-based finite volume # volume integrals. From c0c65e8f68cd17f9de77a19d90cf7266f225e2d1 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Fri, 13 Feb 2026 15:22:38 -0600 Subject: [PATCH 307/360] update Sod to include shock capturing --- ...ixir_euler_modified_sod_entropy_correction.jl | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/examples/tree_1d_dgsem/elixir_euler_modified_sod_entropy_correction.jl b/examples/tree_1d_dgsem/elixir_euler_modified_sod_entropy_correction.jl index 418249e2760..69d1ec41212 100644 --- a/examples/tree_1d_dgsem/elixir_euler_modified_sod_entropy_correction.jl +++ b/examples/tree_1d_dgsem/elixir_euler_modified_sod_entropy_correction.jl @@ -19,13 +19,21 @@ initial_condition = initial_condition_modified_sod volume_flux = flux_central surface_flux = flux_lax_friedrichs basis = LobattoLegendreBasis(3) -indicator = IndicatorEntropyCorrection(equations, basis) +indicator_ec = IndicatorEntropyCorrection(equations, basis) +indicator_sc = IndicatorHennemannGassner(equations, basis, + alpha_max = 0.5, + alpha_min = 0.001, + alpha_smooth = true, + variable = density_pressure) +indicator = IndicatorEntropyCorrectionWithShockCapturing(indicator_ec, + indicator_sc) + volume_integral_default = VolumeIntegralFluxDifferencing(volume_flux) volume_integral_entropy_stable = VolumeIntegralPureLGLFiniteVolumeO2(basis, volume_flux_fv = surface_flux) -volume_integral = VolumeIntegralEntropyCorrection(volume_integral_default, - volume_integral_entropy_stable, - indicator) +volume_integral = VolumeIntegralAdaptive(indicator, + volume_integral_default, + volume_integral_entropy_stable) solver = DGSEM(basis, surface_flux, volume_integral) coordinates_min = 0.0 From 6f4fa25546c95c4a200bdae0c236dce16f3d4d74 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Fri, 13 Feb 2026 15:24:04 -0600 Subject: [PATCH 308/360] add indicator EC with SC --- src/solvers/dgsem/indicators.jl | 52 +++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/src/solvers/dgsem/indicators.jl b/src/solvers/dgsem/indicators.jl index 0ab0ae5fd55..c1f1eefdb1b 100644 --- a/src/solvers/dgsem/indicators.jl +++ b/src/solvers/dgsem/indicators.jl @@ -432,4 +432,56 @@ function Base.show(io::IO, ::MIME"text/plain", indicator::IndicatorEntropyCorrec summary_box(io, "IndicatorEntropyCorrection") return nothing end + +""" + IndicatorEntropyCorrectionWithShockCapturing(indicator_shock_capturing, + indicator_entropy_correction) + +Indicator used for entropy correction using subcell FV schemes, where the blending +is taken to be the maximum between a blending determined by shock capturing +(indicator_shock_capturing) and a blending determined so that the volume integral +entropy production is the same or more than that of an EC scheme (indicator_entropy_correction). + +This is intended to guide the convex blending of a `volume_integral_default` (for +example, [`VolumeIntegralWeakForm`](@ref)) and `volume_integral_stabilized` (for +example, [`VolumeIntegralPureLGLFiniteVolume`](@ref) with an entropy stable finite +volume flux). + +The use of `IndicatorEntropyCorrection` requires either + `entropy_potential(u, orientation, equations)` for TreeMesh, or + `entropy_potential(u, normal_direction, equations)` for other mesh types +to be defined. + +""" +struct IndicatorEntropyCorrectionWithShockCapturing <: AbstractIndicator + indicator_entropy_correction::IndicatorEntropyCorrection + indicator_shock_capturing::AbstractIndicator +end + +function Base.show(io::IO, indicator::IndicatorEntropyCorrectionWithShockCapturing) + @nospecialize indicator # reduce precompilation time + print(io, "IndicatorEntropyCorrectionWithShockCapturing") + # print(io, "IndicatorEntropyCorrectionWithShockCapturing(") + # print(io, indicator.indicator_entropy_correction) + # print(io, ", ") + # print(io, indicator.indicator_shock_capturing |> typeof |> nameof) + # print(io, ")") + return nothing +end + +function Base.show(io::IO, ::MIME"text/plain", + indicator::IndicatorEntropyCorrectionWithShockCapturing) + @nospecialize indicator # reduce precompilation time + + if get(io, :compact, false) + show(io, indicator) + else + setup = [ + "indicator EC" => indicator.indicator_entropy_correction, + "indicator SC" => indicator.indicator_shock_capturing |> typeof |> nameof + ] + summary_box(io, "IndicatorEntropyCorrectionWithShockCapturing", setup) + end + return nothing +end end # @muladd From b4b03664cd8b92089652de1ba3b79276ad51ffbd Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Fri, 13 Feb 2026 15:24:19 -0600 Subject: [PATCH 309/360] fix calc integral with entropy correction --- src/solvers/dgsem/calc_volume_integral.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/solvers/dgsem/calc_volume_integral.jl b/src/solvers/dgsem/calc_volume_integral.jl index 0e137c9201f..e6c20ab2ced 100644 --- a/src/solvers/dgsem/calc_volume_integral.jl +++ b/src/solvers/dgsem/calc_volume_integral.jl @@ -229,7 +229,7 @@ function calc_volume_integral!(du, u, mesh, have_nonconservative_terms, equations, volume_integral::VolumeIntegralEntropyCorrection, dg::DGSEM, cache) - (; volume_integral_default, volume_integral_entropy_stable, indicator) = volume_integral + (; volume_integral_default, volume_integral_stabilized, indicator) = volume_integral (; scaling) = indicator (; alpha) = indicator.cache du_element_threaded = indicator.cache.volume_integral_values_threaded @@ -277,7 +277,7 @@ function calc_volume_integral!(du, u, mesh, # Calculate entropy stable volume integral contribution volume_integral_kernel!(du, u, element, mesh, have_nonconservative_terms, equations, - volume_integral_entropy_stable, dg, cache) + volume_integral_stabilized, dg, cache) # Calculate difference between high and low order FV integral; # this should be made entropy dissipative if entropy_residual > 0. From 1f3e190eee08b4a2a32b6c4383b3475ad05cd6ad Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Fri, 13 Feb 2026 15:24:34 -0600 Subject: [PATCH 310/360] enable shock capturing + entropy correction --- src/solvers/dgsem/calc_volume_integral.jl | 99 +++++++++++++++++++++++ 1 file changed, 99 insertions(+) diff --git a/src/solvers/dgsem/calc_volume_integral.jl b/src/solvers/dgsem/calc_volume_integral.jl index e6c20ab2ced..a288c97ae66 100644 --- a/src/solvers/dgsem/calc_volume_integral.jl +++ b/src/solvers/dgsem/calc_volume_integral.jl @@ -303,4 +303,103 @@ function calc_volume_integral!(du, u, mesh, return nothing end + +const VolumeIntegralEntropyCorrectionWithShockCapturing = VolumeIntegralAdaptive{<:IndicatorEntropyCorrectionWithShockCapturing} + +function get_element_variables!(element_variables, u, mesh, equations, + volume_integral::VolumeIntegralEntropyCorrectionWithShockCapturing, + dg, cache) + element_variables[:indicator_shock_capturing] = volume_integral.indicator_entropy_correction.cache.alpha + return nothing +end + +function calc_volume_integral!(du, u, mesh, + have_nonconservative_terms, equations, + volume_integral::VolumeIntegralEntropyCorrectionWithShockCapturing, + dg::DGSEM, cache) + (; volume_integral_default, volume_integral_stabilized, indicator) = volume_integral + (; indicator_entropy_correction, indicator_shock_capturing) = indicator + (; scaling) = indicator_entropy_correction + (; alpha) = indicator_entropy_correction.cache # TODO: remove array since it's duplicated in indicator_shock_capturing? + du_element_threaded = indicator_entropy_correction.cache.volume_integral_values_threaded + + resize!(alpha, nelements(dg, cache)) + + # Calculate DG-FV blending factors α a-priori for: u_{DG-FV} = u_DG * (1 - α) + u_FV * α + alpha_shock_capturing = @trixi_timeit timer() "blending factors" indicator_shock_capturing(u, + mesh, + equations, + dg, + cache) + + @threaded for element in eachelement(dg, cache) + + # run default volume integral + volume_integral_kernel!(du, u, element, mesh, + have_nonconservative_terms, equations, + volume_integral_default, dg, cache) + + # Check entropy production of "high order" volume integral. + # + # Note that, for `TreeMesh`, both volume and surface integrals are calculated + # on the reference element. For other mesh types, because the volume integral + # incorporates the scaled contravariant vectors, the surface integral should + # be calculated on the physical element instead. + # + # Minus sign because of the flipped sign of the volume term in the DG RHS. + # No scaling by inverse Jacobian here, as there is no Jacobian multiplication + # in `integrate_reference_element`. + dS_volume_integral = -entropy_change_reference_element(du, u, element, + mesh, equations, + dg, cache) + + # Compute true entropy change given by surface integral of the entropy potential + dS_true = surface_integral(entropy_potential, u, element, + mesh, equations, dg, cache) + + # This quantity should be ≤ 0 for an entropy stable volume integral, and + # exactly zero for an entropy conservative volume integral. + entropy_residual = dS_volume_integral - dS_true + + if entropy_residual > 0 + # Store "high order" result + du_FD_element = du_element_threaded[Threads.threadid()] + @views du_FD_element .= du[.., element] + + # Reset pure flux-differencing volume integral + # Note that this assumes that the volume terms are computed first, + # before any surface terms are added. + du[.., element] .= zero(eltype(du)) + + # Calculate entropy stable volume integral contribution + volume_integral_kernel!(du, u, element, mesh, + have_nonconservative_terms, equations, + volume_integral_stabilized, dg, cache) + + # Calculate difference between high and low order FV integral; + # this should be made entropy dissipative if entropy_residual > 0. + @views du_FD_element .= (du_FD_element .- du[.., element]) + + entropy_dissipation = entropy_change_reference_element(du_FD_element, u, + element, + mesh, equations, + dg, cache) + + # Calculate DG-FV blending factor as the minimum between the entropy correction + # indicator and shock capturing indicator + # TODO: replacing this with a differentiable version of `min` + ratio = regularized_ratio(-entropy_residual, entropy_dissipation) + alpha_element = min(1, max(alpha_shock_capturing[element], scaling * ratio)) + + # Save blending coefficient for visualization + alpha[element] = alpha_element + + # Blend the high order method back in + @views du[.., element] .= du[.., element] .+ + (1 - alpha_element) .* du_FD_element + end + end + + return nothing +end end # @muladd From 59bfc8db4dee929af33d3c91f7bc36a3227a3d6f Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Fri, 13 Feb 2026 15:24:40 -0600 Subject: [PATCH 311/360] update test --- test/test_tree_1d_euler.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_tree_1d_euler.jl b/test/test_tree_1d_euler.jl index 09b4f558fe1..d62cc5697cb 100644 --- a/test/test_tree_1d_euler.jl +++ b/test/test_tree_1d_euler.jl @@ -663,8 +663,8 @@ end @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_modified_sod_entropy_correction.jl"), tspan=(0.0, 0.1), - l2=[0.17986404413131177, 0.3020862626945975, 0.5933007649757316], - linf=[0.6864216508255041, 0.8099707951909875, 1.9586133195104065]) + l2=[0.18205439515402314, 0.30149291355224794, 0.5976365681857497], + linf=[0.7019546220790203, 0.8107371518443405, 1.9963507053838896]) # Ensure that we do not have excessive memory allocations # (e.g., from type instabilities) From f164e18a1e3b67eafb43400930de4aa1b69bba7c Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Fri, 13 Feb 2026 15:27:13 -0600 Subject: [PATCH 312/360] format elixir --- .../elixir_navier_stokes_daru_tenaud.jl | 41 +++++++++---------- 1 file changed, 19 insertions(+), 22 deletions(-) diff --git a/examples/tree_2d_dgsem/elixir_navier_stokes_daru_tenaud.jl b/examples/tree_2d_dgsem/elixir_navier_stokes_daru_tenaud.jl index 04d015170de..f5df436636d 100644 --- a/examples/tree_2d_dgsem/elixir_navier_stokes_daru_tenaud.jl +++ b/examples/tree_2d_dgsem/elixir_navier_stokes_daru_tenaud.jl @@ -20,7 +20,7 @@ function initial_condition_kelvin_helmholtz_instability(x, t, p = 1.0 return prim2cons(SVector(rho, v1, v2, p), equations) end - + function initial_condition_blast_wave(x, t, equations::CompressibleEulerEquations2D) RealT = eltype(x) inicenter = SVector(0.25, 0.25) @@ -46,7 +46,7 @@ function initial_condition_daru(x, t, equations) v1 = zero(RealT) v2 = zero(RealT) - rho_rr = 120.0 + rho_rr = 120.0 # rho_rr = 60.0 rho = x[1] > 0.5f0 ? 1.2 : rho_rr @@ -59,7 +59,6 @@ function initial_condition_daru(x, t, equations) end p = rho / equations.gamma - return prim2cons(SVector(rho, v1, v2, p), equations) end @@ -94,39 +93,37 @@ volume_integral = VolumeIntegralEntropyCorrection(equations, basis; volume_flux_fv = surface_flux) dg = DGSEM(basis, surface_flux, volume_integral) - # Create a uniformly refined mesh with periodic boundaries initial_refinement_level = 9 mesh = TreeMesh(coordinates_min, coordinates_max, initial_refinement_level = initial_refinement_level, - periodicity=periodicity, n_cells_max = 400_000) + periodicity = periodicity, n_cells_max = 400_000) # BC types -boundary_condition_noslip_wall = - BoundaryConditionNavierStokesWall(NoSlip((x, t, equations_parabolic) -> (0.0, 0.0)), - Adiabatic((x, t, equations_parabolic) -> 0.0)) +boundary_condition_noslip_wall = BoundaryConditionNavierStokesWall(NoSlip((x, t, equations_parabolic) -> (0.0, + 0.0)), + Adiabatic((x, t, equations_parabolic) -> 0.0)) # define inviscid boundary conditions boundary_conditions_hyperbolic = (; x_neg = boundary_condition_slip_wall, - x_pos = boundary_condition_slip_wall, - y_neg = boundary_condition_slip_wall, - y_pos = boundary_condition_slip_wall) + x_pos = boundary_condition_slip_wall, + y_neg = boundary_condition_slip_wall, + y_pos = boundary_condition_slip_wall) # define viscous boundary conditions boundary_conditions_parabolic = (; x_neg = boundary_condition_noslip_wall, - x_pos = boundary_condition_noslip_wall, - y_neg = boundary_condition_noslip_wall, - y_pos = boundary_condition_noslip_wall) + x_pos = boundary_condition_noslip_wall, + y_neg = boundary_condition_noslip_wall, + y_pos = boundary_condition_noslip_wall) # solver_parabolic = ViscousFormulationBassiRebay1() solver_parabolic = ViscousFormulationLocalDG() semi = SemidiscretizationHyperbolicParabolic(mesh, (equations, equations_parabolic), - initial_condition, dg; - solver_parabolic = solver_parabolic, - boundary_conditions = (boundary_conditions_hyperbolic, + initial_condition, dg; + solver_parabolic = solver_parabolic, + boundary_conditions = (boundary_conditions_hyperbolic, boundary_conditions_parabolic)) - ############################################################################### # ODE solvers, callbacks etc. @@ -148,7 +145,7 @@ callbacks = CallbackSet(summary_callback, alive_callback) # solver = SSPRK43(stage_limiter!, stage_limiter!) solver = SSPRK43() -sol = solve(ode, solver; abstol = 1e-6, reltol = 1e-4, +sol = solve(ode, solver; abstol = 1e-6, reltol = 1e-4, ode_default_options()..., callback = callbacks) using JLD2 @@ -161,12 +158,12 @@ using Plots # plot(PlotData2D(sol)["rho"]) u = Trixi.wrap_array(sol.u[end], semi) -T = [Trixi.temperature(get_node_vars(u, equations, dg, i, j, elements), equations_parabolic) +T = [Trixi.temperature(get_node_vars(u, equations, dg, i, j, elements), + equations_parabolic) for i in eachnode(dg), j in eachnode(dg), elements in eachelement(dg, semi.cache)] plot(ScalarPlotData2D(T, semi)) -plot!(clims=(0.4, 1.2), xlims=(0.4, 1.0), ylims=(0, 0.25)) +plot!(clims = (0.4, 1.2), xlims = (0.4, 1.0), ylims = (0, 0.25)) # ECAV_coefficient = [semi.cache.artificial_viscosity.coefficients[elements] # for i in eachnode(dg), j in eachnode(dg), elements in eachelement(dg, semi.cache)] # plot(ScalarPlotData2D(ECAV_coefficient, semi)) - From fff9fc54492083953a966fc37f9740928c82bc92 Mon Sep 17 00:00:00 2001 From: jlchan Date: Fri, 13 Feb 2026 15:49:20 -0600 Subject: [PATCH 313/360] adding keyword constructor for IndicatorEntropyCorrectionWithShockCapturing --- src/solvers/dgsem/indicators.jl | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/solvers/dgsem/indicators.jl b/src/solvers/dgsem/indicators.jl index c1f1eefdb1b..0b2cc4722d5 100644 --- a/src/solvers/dgsem/indicators.jl +++ b/src/solvers/dgsem/indicators.jl @@ -434,8 +434,8 @@ function Base.show(io::IO, ::MIME"text/plain", indicator::IndicatorEntropyCorrec end """ - IndicatorEntropyCorrectionWithShockCapturing(indicator_shock_capturing, - indicator_entropy_correction) + IndicatorEntropyCorrectionWithShockCapturing(; indicator_shock_capturing, + indicator_entropy_correction) Indicator used for entropy correction using subcell FV schemes, where the blending is taken to be the maximum between a blending determined by shock capturing @@ -458,6 +458,12 @@ struct IndicatorEntropyCorrectionWithShockCapturing <: AbstractIndicator indicator_shock_capturing::AbstractIndicator end +function IndicatorEntropyCorrectionWithShockCapturing(; indicator_shock_capturing, + indicator_entropy_correction) + return IndicatorEntropyCorrectionWithShockCapturing(indicator_shock_capturing, + indicator_entropy_correction) +end + function Base.show(io::IO, indicator::IndicatorEntropyCorrectionWithShockCapturing) @nospecialize indicator # reduce precompilation time print(io, "IndicatorEntropyCorrectionWithShockCapturing") From 72a012f7e96ee857cf60bf993e914562ba3da249 Mon Sep 17 00:00:00 2001 From: jlchan Date: Fri, 13 Feb 2026 15:54:08 -0600 Subject: [PATCH 314/360] fix --- src/solvers/dgsem/indicators.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/solvers/dgsem/indicators.jl b/src/solvers/dgsem/indicators.jl index 0b2cc4722d5..0dff77d7ab4 100644 --- a/src/solvers/dgsem/indicators.jl +++ b/src/solvers/dgsem/indicators.jl @@ -460,8 +460,8 @@ end function IndicatorEntropyCorrectionWithShockCapturing(; indicator_shock_capturing, indicator_entropy_correction) - return IndicatorEntropyCorrectionWithShockCapturing(indicator_shock_capturing, - indicator_entropy_correction) + return IndicatorEntropyCorrectionWithShockCapturing(indicator_entropy_correction, + indicator_shock_capturing) end function Base.show(io::IO, indicator::IndicatorEntropyCorrectionWithShockCapturing) From f0eed49833398929690ee6f76cb872d986176b2e Mon Sep 17 00:00:00 2001 From: jlchan Date: Fri, 13 Feb 2026 15:54:22 -0600 Subject: [PATCH 315/360] removing untested elixirs --- examples/tree_2d_dgsem/elixir_euler_jet.jl | 151 ---------------- .../elixir_navier_stokes_daru_tenaud.jl | 169 ------------------ 2 files changed, 320 deletions(-) delete mode 100644 examples/tree_2d_dgsem/elixir_euler_jet.jl delete mode 100644 examples/tree_2d_dgsem/elixir_navier_stokes_daru_tenaud.jl diff --git a/examples/tree_2d_dgsem/elixir_euler_jet.jl b/examples/tree_2d_dgsem/elixir_euler_jet.jl deleted file mode 100644 index 8716bb08e4a..00000000000 --- a/examples/tree_2d_dgsem/elixir_euler_jet.jl +++ /dev/null @@ -1,151 +0,0 @@ -using OrdinaryDiffEqSSPRK -using Trixi - -############################################################################### -# semidiscretization of the compressible Euler equations - -eos = IdealGas(5 / 3) -eos = PengRobinson() -equations = NonIdealCompressibleEulerEquations2D(eos) - -function initial_condition_jet(x, t, equations::NonIdealCompressibleEulerEquations2D) - eos = equations.equation_of_state - - v1, v2 = 0.0, 0.0 - - # rho = 45 # kg/m3 - # T = 290.2 # K, about 4 MPa - - # from Ma et al - rho = 45.5 - T = 300 - - V = inv(rho) - e_internal = energy_internal_specific(V, T, eos) - # cv = 287 / (equations.gamma - 1.0) - # e_internal = cv * T - - return SVector(rho, rho * v1, rho * v2, rho * (e_internal + 0.5 * (v1^2 + v2^2))) -end - -function boundary_condition_jet(x, t, equations::NonIdealCompressibleEulerEquations2D) - eos = equations.equation_of_state - - h = 0.0022 # 2.2 mm - if abs(x[2]) < 0.5 * h - rho = 500 # kg / m3 - v1 = 100 # m/s - v2 = 0.0 - T = 124.6 # K - - # from Ma et al - rho = 911 - T = 80 - - V = inv(rho) - e_internal = energy_internal_specific(V, T, eos) - - # # to match pressure inside for ideal gas - # p_interior = pressure(initial_condition_jet(x, t, equations), equations) - # e_internal = Trixi.energy_internal(prim2cons(SVector(rho, 0, 0, p_interior), equations), - # equations) / rho - - return SVector(rho, rho * v1, rho * v2, rho * (e_internal + 0.5 * (v1^2 + v2^2))) - else - return initial_condition_jet(x, t, equations) - end -end - -# u_jet = boundary_condition_jet([0,0], 0, equations) -# u_interior = initial_condition_jet([0, 0], 0, equations) -# pressure(u_interior, equations) -# pressure(u_jet, equations) - -@inline function boundary_condition_subsonic_outflow(u_inner, orientation::Integer, - direction, x, t, - surface_flux_function, - equations::NonIdealCompressibleEulerEquations2D) - V, v1, v2, T = cons2thermo(u_inner, equations) - - # For subsonic boundary: take pressure from initial condition - p = pressure(V, T, equations.equation_of_state) - - # invert for new temperature given p, V - T = 1 - tol = 100 * eps(RealT) - dp = pressure(V, T, eos) - p - iter = 1 - while abs(dp) / abs(p) > tol && iter < 100 - dp = pressure(V, T, eos) - p - dpdT_V = ForwardDiff.derivative(T -> pressure(V, T, eos), T) - T = max(tol, T - dp / dpdT_V) - iter += 1 - end - if iter == 100 - @warn "Solver for temperature(V, p) did not converge" - end - - thermo = SVector(V, v1, v2, T) - u_surface = thermo2cons(thermo, equations) - - return flux(u_surface, orientation, equations) -end - -initial_condition = initial_condition_jet - -volume_flux = flux_central_terashima_etal -surface_flux = flux_lax_friedrichs -basis = LobattoLegendreBasis(3) -indicator = IndicatorEntropyCorrection(equations, basis; scaling = 2.5) -volume_integral_default = VolumeIntegralFluxDifferencing(volume_flux) -volume_integral_entropy_stable = VolumeIntegralPureLGLFiniteVolumeO2(basis, - volume_flux_fv = surface_flux) - -volume_integral = VolumeIntegralEntropyCorrection(volume_integral_default, - volume_integral_entropy_stable, - indicator) - -solver = DGSEM(basis, surface_flux, volume_integral) - -h = 0.0022 # 2.2 mm -coordinates_min = (0.0, -16 * h) -coordinates_max = (32 * h, 16 * h) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 7, - n_cells_max = 30_000, - periodicity = (false, true)) - -boundary_condition_inflow = BoundaryConditionDirichlet(boundary_condition_jet) -# boundary_condition_inflow = boundary_condition_subsonic_inflow - -boundary_conditions = (x_neg = boundary_condition_inflow, - x_pos = boundary_condition_slip_wall) -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver; - boundary_conditions = boundary_conditions) - -############################################################################### -# ODE solvers, callbacks etc. - -tspan = (0.0, 1e-3) -ode = semidiscretize(semi, tspan) - -summary_callback = SummaryCallback() - -analysis_interval = 2000 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval) - -alive_callback = AliveCallback(analysis_interval = analysis_interval) - -############################################################################### -# run the simulation - -callbacks = CallbackSet(summary_callback, - analysis_callback, - alive_callback) -sol = solve(ode, SSPRK43(); - abstol = 1e-6, reltol = 1e-4, - saveat = LinRange(tspan..., 10), - ode_default_options()..., callback = callbacks); - -using Plots -plot(ScalarPlotData2D(Trixi.density, sol.u[end], semi), dpi = 300) diff --git a/examples/tree_2d_dgsem/elixir_navier_stokes_daru_tenaud.jl b/examples/tree_2d_dgsem/elixir_navier_stokes_daru_tenaud.jl deleted file mode 100644 index f5df436636d..00000000000 --- a/examples/tree_2d_dgsem/elixir_navier_stokes_daru_tenaud.jl +++ /dev/null @@ -1,169 +0,0 @@ -using OrdinaryDiffEqLowStorageRK -using OrdinaryDiffEqSSPRK -using Trixi - -############################################################################### -# semidiscretization of the ideal compressible Navier-Stokes equations - -prandtl_number() = 0.73 - -function initial_condition_kelvin_helmholtz_instability(x, t, - equations::CompressibleEulerEquations2D) - # change discontinuity to tanh - # typical resolution 128^2, 256^2 - # domain size is [-1,+1]^2 - slope = 15 - B = tanh(slope * x[2] + 7.5) - tanh(slope * x[2] - 7.5) - rho = 0.5 + 0.75 * B - v1 = 0.5 * (B - 1) - v2 = 0.1 * sin(2 * pi * x[1]) - p = 1.0 - return prim2cons(SVector(rho, v1, v2, p), equations) -end - -function initial_condition_blast_wave(x, t, equations::CompressibleEulerEquations2D) - RealT = eltype(x) - inicenter = SVector(0.25, 0.25) - x_norm = x[1] - inicenter[1] - y_norm = x[2] - inicenter[2] - r = sqrt(x_norm^2 + y_norm^2) - phi = atan(y_norm, x_norm) - sin_phi, cos_phi = sincos(phi) - - # Calculate primitive variables - r0 = 0.25f0 - rho = r > r0 ? one(1e-1) : RealT(1.1691) - v1 = r > r0 ? zero(RealT) : RealT(0.1882) * cos_phi - v2 = r > r0 ? zero(RealT) : RealT(0.1882) * sin_phi - # p = r > 0.25f0 ? RealT(1.0E-2) : RealT(1.245) - p = r > r0 ? RealT(1e-1) : RealT(1.245) - - return prim2cons(SVector(rho, v1, v2, p), equations) -end - -function initial_condition_daru(x, t, equations) - RealT = eltype(x) - v1 = zero(RealT) - v2 = zero(RealT) - - rho_rr = 120.0 - # rho_rr = 60.0 - - rho = x[1] > 0.5f0 ? 1.2 : rho_rr - p = x[1] > 0.5f0 ? 1.2 / equations.gamma : rho_rr / equations.gamma - # rho = x[1] > 0.5f0 ? 1.2 : 24.0 - # p = x[1] > 0.5f0 ? 1.2 / equations.gamma : 24.0 / equations.gamma - # rho = 59.4 * tanh(-25*(x[1] - 0.5)) + 60.6 - if abs(x[1] - 0.5f0) < 1e3 * eps() - rho = 0.5 * (1.2 + rho_rr) - end - p = rho / equations.gamma - - return prim2cons(SVector(rho, v1, v2, p), equations) -end - -coordinates_min = (0.0, 0.0) # minimum coordinates (min(x), min(y)) -coordinates_max = (1.0, 1.0) # maximum coordinates (max(x), max(y)) -tspan = (0.0, 1.0) -initial_condition = initial_condition_daru -periodicity = (false, false) -# mu() = 5e-3 # Re = 200 -# mu() = 2e-3 # Re = 500 -mu() = 1e-3 # Re = 1000 - -# coordinates_min = (-1.0, -1.0) # minimum coordinates (min(x), min(y)) -# coordinates_max = (1.0, 1.0) # maximum coordinates (max(x), max(y)) -# tspan = (0.0, 1.5) -# initial_condition = initial_condition_blast_wave -# # tspan = (0.0, 5.0) -# # initial_condition = initial_condition_kelvin_helmholtz_instability -# periodicity = (true, true) -# # periodicity = (false, false) - -equations = CompressibleEulerEquations2D(1.4) -equations_parabolic = CompressibleNavierStokesDiffusion2D(equations, mu = mu(), - Prandtl = prandtl_number(), - gradient_variables = GradientVariablesEntropy()) - -volume_flux = flux_central -surface_flux = flux_lax_friedrichs -basis = LobattoLegendreBasis(3) -volume_integral = VolumeIntegralEntropyCorrection(equations, basis; - volume_flux_dg = volume_flux, - volume_flux_fv = surface_flux) -dg = DGSEM(basis, surface_flux, volume_integral) - -# Create a uniformly refined mesh with periodic boundaries -initial_refinement_level = 9 -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = initial_refinement_level, - periodicity = periodicity, n_cells_max = 400_000) - -# BC types -boundary_condition_noslip_wall = BoundaryConditionNavierStokesWall(NoSlip((x, t, equations_parabolic) -> (0.0, - 0.0)), - Adiabatic((x, t, equations_parabolic) -> 0.0)) - -# define inviscid boundary conditions -boundary_conditions_hyperbolic = (; x_neg = boundary_condition_slip_wall, - x_pos = boundary_condition_slip_wall, - y_neg = boundary_condition_slip_wall, - y_pos = boundary_condition_slip_wall) - -# define viscous boundary conditions -boundary_conditions_parabolic = (; x_neg = boundary_condition_noslip_wall, - x_pos = boundary_condition_noslip_wall, - y_neg = boundary_condition_noslip_wall, - y_pos = boundary_condition_noslip_wall) - -# solver_parabolic = ViscousFormulationBassiRebay1() -solver_parabolic = ViscousFormulationLocalDG() -semi = SemidiscretizationHyperbolicParabolic(mesh, (equations, equations_parabolic), - initial_condition, dg; - solver_parabolic = solver_parabolic, - boundary_conditions = (boundary_conditions_hyperbolic, - boundary_conditions_parabolic)) - -############################################################################### -# ODE solvers, callbacks etc. - -# Create ODE problem with time span `tspan` -ode = semidiscretize(semi, tspan) - -summary_callback = SummaryCallback() -alive_callback = AliveCallback(alive_interval = 100) -callbacks = CallbackSet(summary_callback, alive_callback) -# analysis_interval = 1000 -# analysis_callback = AnalysisCallback(semi, interval = analysis_interval) -# callbacks = CallbackSet(summary_callback, alive_callback, analysis_callback) #, amr_callback) - -############################################################################### -# run the simulation - -# stage_limiter! = PositivityPreservingLimiterZhangShu(thresholds = (1.0e-6, 1.0e-6), -# variables = (Trixi.density, pressure)) -# solver = SSPRK43(stage_limiter!, stage_limiter!) -solver = SSPRK43() - -sol = solve(ode, solver; abstol = 1e-6, reltol = 1e-4, - ode_default_options()..., callback = callbacks) - -using JLD2 -@save "DaruTenaudRe1000_VolumeIntegralEntropyCorrection.jld2" sol -#@save "DaruTenaudRe1000_polydeg_3_elements_512.jld2" sol -# @save "DaruTenaudRe1000_polydeg_$(Trixi.polydeg(dg.basis))_elements_$(2^initial_refinement_level).jld2" sol -# @save "DaruTenaudRe1000_polydeg_$(Trixi.polydeg(dg.basis))_elements_$(2^initial_refinement_level)_shock_capturing_amax_p5.jld2" sol - -using Plots -# plot(PlotData2D(sol)["rho"]) - -u = Trixi.wrap_array(sol.u[end], semi) -T = [Trixi.temperature(get_node_vars(u, equations, dg, i, j, elements), - equations_parabolic) - for i in eachnode(dg), j in eachnode(dg), elements in eachelement(dg, semi.cache)] -plot(ScalarPlotData2D(T, semi)) -plot!(clims = (0.4, 1.2), xlims = (0.4, 1.0), ylims = (0, 0.25)) - -# ECAV_coefficient = [semi.cache.artificial_viscosity.coefficients[elements] -# for i in eachnode(dg), j in eachnode(dg), elements in eachelement(dg, semi.cache)] -# plot(ScalarPlotData2D(ECAV_coefficient, semi)) From 906d22aa82d7c356faa50725069ac034950bb903 Mon Sep 17 00:00:00 2001 From: jlchan Date: Fri, 13 Feb 2026 16:08:34 -0600 Subject: [PATCH 316/360] adding back dropped --- src/callbacks_step/analysis_dg2d.jl | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/callbacks_step/analysis_dg2d.jl b/src/callbacks_step/analysis_dg2d.jl index 0f19ac2b02f..e50d7adbff6 100644 --- a/src/callbacks_step/analysis_dg2d.jl +++ b/src/callbacks_step/analysis_dg2d.jl @@ -220,6 +220,21 @@ function entropy_change_reference_element(du::AbstractArray{<:Any, 4}, u, end end +# This version is intended to be used with `du_element` (`du` over a single element) +function entropy_change_reference_element(du_element::AbstractArray{<:Any, 3}, u, + element, + mesh::AbstractMesh{2}, + equations, dg::DGSEM, cache, args...) + return integrate_reference_element(u, element, mesh, equations, dg, cache, + du_element) do u, i, j, element, equations, dg, + du_element + u_node = get_node_vars(u, equations, dg, i, j, element) + du_node = get_node_vars(du_element, equations, dg, i, j) + + dot(cons2entropy(u_node, equations), du_node) + end +end + # calculate surface integral of func(u, equations) * normal on the reference element. function surface_integral(func::Func, u, element, mesh::TreeMesh{2}, equations, dg::DGSEM, cache, From 1ac23446e658eefaad07dbba128dcda8af04c515 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Fri, 13 Feb 2026 21:38:56 -0600 Subject: [PATCH 317/360] fix type instability --- src/solvers/dgsem/indicators.jl | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/solvers/dgsem/indicators.jl b/src/solvers/dgsem/indicators.jl index 0dff77d7ab4..d002f8fbb28 100644 --- a/src/solvers/dgsem/indicators.jl +++ b/src/solvers/dgsem/indicators.jl @@ -453,9 +453,10 @@ The use of `IndicatorEntropyCorrection` requires either to be defined. """ -struct IndicatorEntropyCorrectionWithShockCapturing <: AbstractIndicator - indicator_entropy_correction::IndicatorEntropyCorrection - indicator_shock_capturing::AbstractIndicator +struct IndicatorEntropyCorrectionWithShockCapturing{IndicatorEC, IndicatorSC} <: + AbstractIndicator + indicator_entropy_correction::IndicatorEC + indicator_shock_capturing::IndicatorSC end function IndicatorEntropyCorrectionWithShockCapturing(; indicator_shock_capturing, From 0dba8c37936197a3b7a15bc125c6af6068ce6ece Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Fri, 13 Feb 2026 21:39:04 -0600 Subject: [PATCH 318/360] fix test --- test/test_tree_2d_euler.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_tree_2d_euler.jl b/test/test_tree_2d_euler.jl index f2a08f1b8b7..ef3acbe7874 100644 --- a/test/test_tree_2d_euler.jl +++ b/test/test_tree_2d_euler.jl @@ -115,7 +115,7 @@ end solver=DGSEM(LobattoLegendreBasis(3), flux_lax_friedrichs, VolumeIntegralAdaptive(IndicatorEntropyCorrection(equations, - basis; + LobattoLegendreBasis(3); scaling = 2), VolumeIntegralWeakForm(), VolumeIntegralPureLGLFiniteVolume(flux_lax_friedrichs))), From e652177cd90b3eccd96921e4035781d301ced4f9 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Fri, 13 Feb 2026 22:06:39 -0600 Subject: [PATCH 319/360] fix test --- test/test_tree_2d_euler.jl | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/test/test_tree_2d_euler.jl b/test/test_tree_2d_euler.jl index ef3acbe7874..d694ac0fa09 100644 --- a/test/test_tree_2d_euler.jl +++ b/test/test_tree_2d_euler.jl @@ -121,16 +121,16 @@ end VolumeIntegralPureLGLFiniteVolume(flux_lax_friedrichs))), tspan=(0.0, 0.1), l2=[ - 0.02871253948076796, - 0.0028712539480767046, - 0.00574250789615359, - 0.0007178134870180938 + 0.029511330869009502, + 0.0029511330869010097, + 0.0059022661738019925, + 0.0007377832717238577 ], linf=[ - 0.07201294589822727, - 0.007201294589823548, - 0.014402589179645153, - 0.0018003236474513074 + 0.07260068733104652, + 0.007260068733104114, + 0.01452013746620906, + 0.0018150171832971296 ]) # Ensure that we do not have excessive memory allocations # (e.g., from type instabilities) From 6e42bc8d1c87170f94659f037de0d32409ea6613 Mon Sep 17 00:00:00 2001 From: Jesse Chan <1156048+jlchan@users.noreply.github.com> Date: Sun, 15 Feb 2026 14:59:40 -0600 Subject: [PATCH 320/360] Apply suggestions from code review Co-authored-by: Daniel Doehring --- src/solvers/dgsem/calc_volume_integral.jl | 1 - 1 file changed, 1 deletion(-) diff --git a/src/solvers/dgsem/calc_volume_integral.jl b/src/solvers/dgsem/calc_volume_integral.jl index a288c97ae66..aa4a7be1fd0 100644 --- a/src/solvers/dgsem/calc_volume_integral.jl +++ b/src/solvers/dgsem/calc_volume_integral.jl @@ -215,7 +215,6 @@ The use of `VolumeIntegralEntropyCorrection` requires either `entropy_potential(u, normal_direction, equations)` for other mesh types to be defined. """ - const VolumeIntegralEntropyCorrection = VolumeIntegralAdaptive{<:IndicatorEntropyCorrection} function get_element_variables!(element_variables, u, mesh, equations, From ba42f9061293ff45b1a806c97a724fa1bfc2cbf7 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Sun, 15 Feb 2026 15:00:04 -0600 Subject: [PATCH 321/360] remove unnecessary docstring --- src/equations/compressible_euler_2d.jl | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/src/equations/compressible_euler_2d.jl b/src/equations/compressible_euler_2d.jl index cf733e97c3a..042ead1f711 100644 --- a/src/equations/compressible_euler_2d.jl +++ b/src/equations/compressible_euler_2d.jl @@ -2235,19 +2235,6 @@ end return energy_total(cons, equations) - energy_kinetic(cons, equations) end -@doc raw""" - entropy_potential(u, orientation::Int, - equations::AbstractCompressibleEulerEquations) - -Calculate the entropy potential, which for the compressible Euler equations is simply -the momentum for the choice of mathematical [`entropy`](@ref) ``S(u) = \frac{\rho s}{\gamma - 1}`` -with thermodynamic entropy ``s = \ln(p) - \gamma \ln(\rho)``. - -## References -- Eitan Tadmor (2003) - Entropy stability theory for difference approximations of nonlinear conservation laws and related time-dependent problems - [DOI: 10.1017/S0962492902000156](https://doi.org/10.1017/S0962492902000156) -""" @inline function entropy_potential(u, orientation::Int, equations::CompressibleEulerEquations2D) if orientation == 1 From 52bb55f12e150ac09d4c021ceda83b06c6a24b7b Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Mon, 16 Feb 2026 20:03:52 -0600 Subject: [PATCH 322/360] move definitions and rename --- src/solvers/dg.jl | 11 +++++++++++ src/solvers/dgsem/calc_volume_integral.jl | 11 +---------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/solvers/dg.jl b/src/solvers/dg.jl index 0ae311b517b..75371b84ae9 100644 --- a/src/solvers/dg.jl +++ b/src/solvers/dg.jl @@ -423,6 +423,17 @@ function create_cache(mesh, equations, return (; cache_default..., cache_stabilized...) end +# `VolumeIntegralEntropyCorrectionShockCapturingCombined` combines the entropy correction +# indicator with a heuristic shock capturing indicator. +const VolumeIntegralEntropyCorrectionShockCapturingCombined = VolumeIntegralAdaptive{<:IndicatorEntropyCorrectionWithShockCapturing} + +function get_element_variables!(element_variables, u, mesh, equations, + volume_integral::VolumeIntegralEntropyCorrectionShockCapturingCombined, + dg, cache) + element_variables[:indicator_shock_capturing] = volume_integral.indicator_entropy_correction.cache.alpha + return nothing +end + # Abstract supertype for first-order `VolumeIntegralPureLGLFiniteVolume` and # second-order `VolumeIntegralPureLGLFiniteVolumeO2` subcell-based finite volume # volume integrals. diff --git a/src/solvers/dgsem/calc_volume_integral.jl b/src/solvers/dgsem/calc_volume_integral.jl index 7df389abe00..b3d3b4ac775 100644 --- a/src/solvers/dgsem/calc_volume_integral.jl +++ b/src/solvers/dgsem/calc_volume_integral.jl @@ -263,18 +263,9 @@ function calc_volume_integral!(du, u, mesh, return nothing end -const VolumeIntegralEntropyCorrectionWithShockCapturing = VolumeIntegralAdaptive{<:IndicatorEntropyCorrectionWithShockCapturing} - -function get_element_variables!(element_variables, u, mesh, equations, - volume_integral::VolumeIntegralEntropyCorrectionWithShockCapturing, - dg, cache) - element_variables[:indicator_shock_capturing] = volume_integral.indicator_entropy_correction.cache.alpha - return nothing -end - function calc_volume_integral!(du, u, mesh, have_nonconservative_terms, equations, - volume_integral::VolumeIntegralEntropyCorrectionWithShockCapturing, + volume_integral::VolumeIntegralEntropyCorrectionShockCapturingCombined, dg::DGSEM, cache) (; volume_integral_default, volume_integral_stabilized, indicator) = volume_integral (; indicator_entropy_correction, indicator_shock_capturing) = indicator From 2e8ed009f68ec0498ca1a4b2ff8de3f374ac8c98 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Mon, 16 Feb 2026 20:24:40 -0600 Subject: [PATCH 323/360] renaming and moving definitions back --- src/Trixi.jl | 2 +- src/solvers/dg.jl | 11 ----------- src/solvers/dgsem/calc_volume_integral.jl | 11 +++++++++++ src/solvers/dgsem/indicators.jl | 24 +++++++++++------------ 4 files changed, 24 insertions(+), 24 deletions(-) diff --git a/src/Trixi.jl b/src/Trixi.jl index 9a6b7c7d3e6..99425ec6323 100644 --- a/src/Trixi.jl +++ b/src/Trixi.jl @@ -273,7 +273,7 @@ export DG, VolumeIntegralAdaptive, IndicatorEntropyChange, IndicatorHennemannGassner, VolumeIntegralUpwind, - IndicatorEntropyCorrection, IndicatorEntropyCorrectionWithShockCapturing, + IndicatorEntropyCorrection, IndicatorEntropyCorrectionShockCapturingCombined, SurfaceIntegralWeakForm, SurfaceIntegralStrongForm, SurfaceIntegralUpwind, MortarL2 diff --git a/src/solvers/dg.jl b/src/solvers/dg.jl index 75371b84ae9..0ae311b517b 100644 --- a/src/solvers/dg.jl +++ b/src/solvers/dg.jl @@ -423,17 +423,6 @@ function create_cache(mesh, equations, return (; cache_default..., cache_stabilized...) end -# `VolumeIntegralEntropyCorrectionShockCapturingCombined` combines the entropy correction -# indicator with a heuristic shock capturing indicator. -const VolumeIntegralEntropyCorrectionShockCapturingCombined = VolumeIntegralAdaptive{<:IndicatorEntropyCorrectionWithShockCapturing} - -function get_element_variables!(element_variables, u, mesh, equations, - volume_integral::VolumeIntegralEntropyCorrectionShockCapturingCombined, - dg, cache) - element_variables[:indicator_shock_capturing] = volume_integral.indicator_entropy_correction.cache.alpha - return nothing -end - # Abstract supertype for first-order `VolumeIntegralPureLGLFiniteVolume` and # second-order `VolumeIntegralPureLGLFiniteVolumeO2` subcell-based finite volume # volume integrals. diff --git a/src/solvers/dgsem/calc_volume_integral.jl b/src/solvers/dgsem/calc_volume_integral.jl index b3d3b4ac775..fdbbf3be84e 100644 --- a/src/solvers/dgsem/calc_volume_integral.jl +++ b/src/solvers/dgsem/calc_volume_integral.jl @@ -263,6 +263,17 @@ function calc_volume_integral!(du, u, mesh, return nothing end +# `VolumeIntegralEntropyCorrectionShockCapturingCombined` combines the entropy correction +# indicator with a heuristic shock capturing indicator. +const VolumeIntegralEntropyCorrectionShockCapturingCombined = VolumeIntegralAdaptive{<:IndicatorEntropyCorrectionShockCapturingCombined} + +function get_element_variables!(element_variables, u, mesh, equations, + volume_integral::VolumeIntegralEntropyCorrectionShockCapturingCombined, + dg, cache) + element_variables[:indicator_shock_capturing] = volume_integral.indicator_entropy_correction.cache.alpha + return nothing +end + function calc_volume_integral!(du, u, mesh, have_nonconservative_terms, equations, volume_integral::VolumeIntegralEntropyCorrectionShockCapturingCombined, diff --git a/src/solvers/dgsem/indicators.jl b/src/solvers/dgsem/indicators.jl index d002f8fbb28..a3b3cd8dd38 100644 --- a/src/solvers/dgsem/indicators.jl +++ b/src/solvers/dgsem/indicators.jl @@ -434,8 +434,8 @@ function Base.show(io::IO, ::MIME"text/plain", indicator::IndicatorEntropyCorrec end """ - IndicatorEntropyCorrectionWithShockCapturing(; indicator_shock_capturing, - indicator_entropy_correction) + IndicatorEntropyCorrectionShockCapturingCombined(; indicator_shock_capturing, + indicator_entropy_correction) Indicator used for entropy correction using subcell FV schemes, where the blending is taken to be the maximum between a blending determined by shock capturing @@ -453,22 +453,22 @@ The use of `IndicatorEntropyCorrection` requires either to be defined. """ -struct IndicatorEntropyCorrectionWithShockCapturing{IndicatorEC, IndicatorSC} <: +struct IndicatorEntropyCorrectionShockCapturingCombined{IndicatorEC, IndicatorSC} <: AbstractIndicator indicator_entropy_correction::IndicatorEC indicator_shock_capturing::IndicatorSC end -function IndicatorEntropyCorrectionWithShockCapturing(; indicator_shock_capturing, - indicator_entropy_correction) - return IndicatorEntropyCorrectionWithShockCapturing(indicator_entropy_correction, - indicator_shock_capturing) +function IndicatorEntropyCorrectionShockCapturingCombined(; indicator_shock_capturing, + indicator_entropy_correction) + return IndicatorEntropyCorrectionShockCapturingCombined(indicator_entropy_correction, + indicator_shock_capturing) end -function Base.show(io::IO, indicator::IndicatorEntropyCorrectionWithShockCapturing) +function Base.show(io::IO, indicator::IndicatorEntropyCorrectionShockCapturingCombined) @nospecialize indicator # reduce precompilation time - print(io, "IndicatorEntropyCorrectionWithShockCapturing") - # print(io, "IndicatorEntropyCorrectionWithShockCapturing(") + print(io, "IndicatorEntropyCorrectionShockCapturingCombined") + # print(io, "IndicatorEntropyCorrectionShockCapturingCombined(") # print(io, indicator.indicator_entropy_correction) # print(io, ", ") # print(io, indicator.indicator_shock_capturing |> typeof |> nameof) @@ -477,7 +477,7 @@ function Base.show(io::IO, indicator::IndicatorEntropyCorrectionWithShockCapturi end function Base.show(io::IO, ::MIME"text/plain", - indicator::IndicatorEntropyCorrectionWithShockCapturing) + indicator::IndicatorEntropyCorrectionShockCapturingCombined) @nospecialize indicator # reduce precompilation time if get(io, :compact, false) @@ -487,7 +487,7 @@ function Base.show(io::IO, ::MIME"text/plain", "indicator EC" => indicator.indicator_entropy_correction, "indicator SC" => indicator.indicator_shock_capturing |> typeof |> nameof ] - summary_box(io, "IndicatorEntropyCorrectionWithShockCapturing", setup) + summary_box(io, "IndicatorEntropyCorrectionShockCapturingCombined", setup) end return nothing end From a408b073864579da258259e68ff3b7d897b544bf Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Tue, 17 Feb 2026 13:13:36 -0600 Subject: [PATCH 324/360] move definitions around --- src/solvers/dg.jl | 15 +++++++++++++++ src/solvers/dgsem/calc_volume_integral.jl | 11 ----------- src/solvers/dgsem/dgsem.jl | 1 - 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/src/solvers/dg.jl b/src/solvers/dg.jl index 0ae311b517b..eff903682e0 100644 --- a/src/solvers/dg.jl +++ b/src/solvers/dg.jl @@ -982,6 +982,21 @@ AdaptorAMR(mesh, dg::DG) = AdaptorL2(dg.basis) # DGSEM (discontinuous Galerkin spectral element method) include("dgsem/dgsem.jl") +# `VolumeIntegralEntropyCorrectionShockCapturingCombined` combines the entropy correction +# indicator with a heuristic shock capturing indicator. +# We define this here since it needs to be defined after the `indicators.jl` are included +# in `dgsem.jl`, but before `calc_volume_integral.jl` is included. +const VolumeIntegralEntropyCorrectionShockCapturingCombined = VolumeIntegralAdaptive{<:IndicatorEntropyCorrectionShockCapturingCombined} + +function get_element_variables!(element_variables, u, mesh, equations, + volume_integral::VolumeIntegralEntropyCorrectionShockCapturingCombined, + dg, cache) + element_variables[:indicator_shock_capturing] = volume_integral.indicator_entropy_correction.cache.alpha + return nothing +end + +include("dgsem/calc_volume_integral.jl") + # Finite difference methods using summation by parts (SBP) operators # These methods are very similar to DG methods since they also impose interface # and boundary conditions weakly. Thus, these methods can re-use a lot of diff --git a/src/solvers/dgsem/calc_volume_integral.jl b/src/solvers/dgsem/calc_volume_integral.jl index fdbbf3be84e..b3d3b4ac775 100644 --- a/src/solvers/dgsem/calc_volume_integral.jl +++ b/src/solvers/dgsem/calc_volume_integral.jl @@ -263,17 +263,6 @@ function calc_volume_integral!(du, u, mesh, return nothing end -# `VolumeIntegralEntropyCorrectionShockCapturingCombined` combines the entropy correction -# indicator with a heuristic shock capturing indicator. -const VolumeIntegralEntropyCorrectionShockCapturingCombined = VolumeIntegralAdaptive{<:IndicatorEntropyCorrectionShockCapturingCombined} - -function get_element_variables!(element_variables, u, mesh, equations, - volume_integral::VolumeIntegralEntropyCorrectionShockCapturingCombined, - dg, cache) - element_variables[:indicator_shock_capturing] = volume_integral.indicator_entropy_correction.cache.alpha - return nothing -end - function calc_volume_integral!(du, u, mesh, have_nonconservative_terms, equations, volume_integral::VolumeIntegralEntropyCorrectionShockCapturingCombined, diff --git a/src/solvers/dgsem/dgsem.jl b/src/solvers/dgsem/dgsem.jl index 3b706e0f09c..079f205abda 100644 --- a/src/solvers/dgsem/dgsem.jl +++ b/src/solvers/dgsem/dgsem.jl @@ -134,5 +134,4 @@ end include("containers.jl") include("indicators.jl") -include("calc_volume_integral.jl") end # @muladd From 13aea87c8d130dfb40005427c117960a8a7f6c4e Mon Sep 17 00:00:00 2001 From: Daniel_Doehring Date: Wed, 18 Feb 2026 12:24:16 +0100 Subject: [PATCH 325/360] restructure --- ...r_euler_modified_sod_entropy_correction.jl | 4 +- src/solvers/dg.jl | 15 ----- src/solvers/dgsem/calc_volume_integral.jl | 34 ---------- src/solvers/dgsem/compute_u_mean.jl | 67 +++++++++++++++++++ src/solvers/dgsem/dgsem.jl | 63 ++--------------- src/solvers/dgsem/volume_integrals.jl | 53 +++++++++++++++ 6 files changed, 126 insertions(+), 110 deletions(-) create mode 100644 src/solvers/dgsem/compute_u_mean.jl create mode 100644 src/solvers/dgsem/volume_integrals.jl diff --git a/examples/tree_1d_dgsem/elixir_euler_modified_sod_entropy_correction.jl b/examples/tree_1d_dgsem/elixir_euler_modified_sod_entropy_correction.jl index 69d1ec41212..d0e3588f65e 100644 --- a/examples/tree_1d_dgsem/elixir_euler_modified_sod_entropy_correction.jl +++ b/examples/tree_1d_dgsem/elixir_euler_modified_sod_entropy_correction.jl @@ -25,8 +25,8 @@ indicator_sc = IndicatorHennemannGassner(equations, basis, alpha_min = 0.001, alpha_smooth = true, variable = density_pressure) -indicator = IndicatorEntropyCorrectionWithShockCapturing(indicator_ec, - indicator_sc) +indicator = IndicatorEntropyCorrectionShockCapturingCombined(indicator_ec, + indicator_sc) volume_integral_default = VolumeIntegralFluxDifferencing(volume_flux) volume_integral_entropy_stable = VolumeIntegralPureLGLFiniteVolumeO2(basis, diff --git a/src/solvers/dg.jl b/src/solvers/dg.jl index eff903682e0..0ae311b517b 100644 --- a/src/solvers/dg.jl +++ b/src/solvers/dg.jl @@ -982,21 +982,6 @@ AdaptorAMR(mesh, dg::DG) = AdaptorL2(dg.basis) # DGSEM (discontinuous Galerkin spectral element method) include("dgsem/dgsem.jl") -# `VolumeIntegralEntropyCorrectionShockCapturingCombined` combines the entropy correction -# indicator with a heuristic shock capturing indicator. -# We define this here since it needs to be defined after the `indicators.jl` are included -# in `dgsem.jl`, but before `calc_volume_integral.jl` is included. -const VolumeIntegralEntropyCorrectionShockCapturingCombined = VolumeIntegralAdaptive{<:IndicatorEntropyCorrectionShockCapturingCombined} - -function get_element_variables!(element_variables, u, mesh, equations, - volume_integral::VolumeIntegralEntropyCorrectionShockCapturingCombined, - dg, cache) - element_variables[:indicator_shock_capturing] = volume_integral.indicator_entropy_correction.cache.alpha - return nothing -end - -include("dgsem/calc_volume_integral.jl") - # Finite difference methods using summation by parts (SBP) operators # These methods are very similar to DG methods since they also impose interface # and boundary conditions weakly. Thus, these methods can re-use a lot of diff --git a/src/solvers/dgsem/calc_volume_integral.jl b/src/solvers/dgsem/calc_volume_integral.jl index b3d3b4ac775..09b6a575f99 100644 --- a/src/solvers/dgsem/calc_volume_integral.jl +++ b/src/solvers/dgsem/calc_volume_integral.jl @@ -151,39 +151,6 @@ function calc_volume_integral!(du, u, mesh, return nothing end -""" - VolumeIntegralEntropyCorrection(indicator, - volume_integral_default, - volume_integral_entropy_stable) - -Entropy correction volume integral type for DG methods using a convex blending of -a `volume_integral_default` (for example, [`VolumeIntegralWeakForm`](@ref)) and -`volume_integral_entropy_stable` (for example, [`VolumeIntegralPureLGLFiniteVolume`](@ref) -with an entropy stable finite volume flux). - -This is intended to be used with [`IndicatorEntropyCorrection`](@ref), which determines the -amount of blending based on the violation of a cell entropy equality by the volume integral. - -The parameter `scaling ≥ 1` in [`IndicatorEntropyCorrection`](@ref) scales the DG-FV blending -parameter ``\\alpha``(see the [tutorial on shock-capturing](https://trixi-framework.github.io/TrixiDocumentation/stable/tutorials/shock_capturing/#Shock-capturing-with-flux-differencing)) -by a constant, increasing the amount of the subcell FV added in (up to 1, i.e., pure subcell FV). -This can be used to add shock capturing-like behavior. Note though that ``\\alpha`` is computed -here from the entropy defect, **not** using [`IndicatorHennemannGassner`](@ref). - -The use of `VolumeIntegralEntropyCorrection` requires either - `entropy_potential(u, orientation, equations)` for TreeMesh, or - `entropy_potential(u, normal_direction, equations)` for other mesh types -to be defined. -""" -const VolumeIntegralEntropyCorrection = VolumeIntegralAdaptive{<:IndicatorEntropyCorrection} - -function get_element_variables!(element_variables, u, mesh, equations, - volume_integral::VolumeIntegralEntropyCorrection, - dg, cache) - element_variables[:indicator_shock_capturing] = volume_integral.indicator.cache.alpha - return nothing -end - function calc_volume_integral!(du, u, mesh, have_nonconservative_terms, equations, volume_integral::VolumeIntegralEntropyCorrection, @@ -283,7 +250,6 @@ function calc_volume_integral!(du, u, mesh, cache) @threaded for element in eachelement(dg, cache) - # run default volume integral volume_integral_kernel!(du, u, element, mesh, have_nonconservative_terms, equations, diff --git a/src/solvers/dgsem/compute_u_mean.jl b/src/solvers/dgsem/compute_u_mean.jl new file mode 100644 index 00000000000..8c683c50201 --- /dev/null +++ b/src/solvers/dgsem/compute_u_mean.jl @@ -0,0 +1,67 @@ +# By default, Julia/LLVM does not use fused multiply-add operations (FMAs). +# Since these FMAs can increase the performance of many numerical algorithms, +# we need to opt-in explicitly. +# See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. +@muladd begin +#! format: noindent + +# `compute_u_mean` used in: +# (Stage-) Callbacks `EntropyBoundedLimiter` and `PositivityPreservingLimiterZhangShu` + +# positional arguments `mesh` and `cache` passed in to match signature of 2D/3D functions +@inline function compute_u_mean(u::AbstractArray{<:Any, 3}, element, + mesh::AbstractMesh{1}, equations, dg::DGSEM, cache) + @unpack weights = dg.basis + + u_mean = zero(get_node_vars(u, equations, dg, 1, element)) + for i in eachnode(dg) + u_node = get_node_vars(u, equations, dg, i, element) + u_mean += u_node * weights[i] + end + # normalize with the total volume + # note that the reference element is [-1,1], thus the weights sum to 2 + return 0.5f0 * u_mean +end + +@inline function compute_u_mean(u::AbstractArray{<:Any, 4}, element, + mesh::AbstractMesh{2}, equations, dg::DGSEM, cache) + @unpack weights = dg.basis + @unpack inverse_jacobian = cache.elements + + node_volume = zero(real(mesh)) + total_volume = zero(node_volume) + + u_mean = zero(get_node_vars(u, equations, dg, 1, 1, element)) + for j in eachnode(dg), i in eachnode(dg) + volume_jacobian = abs(inv(get_inverse_jacobian(inverse_jacobian, mesh, + i, j, element))) + node_volume = weights[i] * weights[j] * volume_jacobian + total_volume += node_volume + + u_node = get_node_vars(u, equations, dg, i, j, element) + u_mean += u_node * node_volume + end + return u_mean / total_volume # normalize with the total volume +end + +@inline function compute_u_mean(u::AbstractArray{<:Any, 5}, element, + mesh::AbstractMesh{3}, equations, dg::DGSEM, cache) + @unpack weights = dg.basis + @unpack inverse_jacobian = cache.elements + + node_volume = zero(real(mesh)) + total_volume = zero(node_volume) + + u_mean = zero(get_node_vars(u, equations, dg, 1, 1, 1, element)) + for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg) + volume_jacobian = abs(inv(get_inverse_jacobian(inverse_jacobian, mesh, + i, j, k, element))) + node_volume = weights[i] * weights[j] * weights[k] * volume_jacobian + total_volume += node_volume + + u_node = get_node_vars(u, equations, dg, i, j, k, element) + u_mean += u_node * node_volume + end + return u_mean / total_volume # normalize with the total volume +end +end # @muladd diff --git a/src/solvers/dgsem/dgsem.jl b/src/solvers/dgsem/dgsem.jl index 079f205abda..5a47a95c381 100644 --- a/src/solvers/dgsem/dgsem.jl +++ b/src/solvers/dgsem/dgsem.jl @@ -72,66 +72,11 @@ end Base.summary(io::IO, dg::DGSEM) = print(io, "DGSEM(polydeg=$(polydeg(dg)))") -# `compute_u_mean` used in: -# (Stage-) Callbacks `EntropyBoundedLimiter` and `PositivityPreservingLimiterZhangShu` - -# positional arguments `mesh` and `cache` passed in to match signature of 2D/3D functions -@inline function compute_u_mean(u::AbstractArray{<:Any, 3}, element, - mesh::AbstractMesh{1}, equations, dg::DGSEM, cache) - @unpack weights = dg.basis - - u_mean = zero(get_node_vars(u, equations, dg, 1, element)) - for i in eachnode(dg) - u_node = get_node_vars(u, equations, dg, i, element) - u_mean += u_node * weights[i] - end - # normalize with the total volume - # note that the reference element is [-1,1], thus the weights sum to 2 - return 0.5f0 * u_mean -end - -@inline function compute_u_mean(u::AbstractArray{<:Any, 4}, element, - mesh::AbstractMesh{2}, equations, dg::DGSEM, cache) - @unpack weights = dg.basis - @unpack inverse_jacobian = cache.elements - - node_volume = zero(real(mesh)) - total_volume = zero(node_volume) - - u_mean = zero(get_node_vars(u, equations, dg, 1, 1, element)) - for j in eachnode(dg), i in eachnode(dg) - volume_jacobian = abs(inv(get_inverse_jacobian(inverse_jacobian, mesh, - i, j, element))) - node_volume = weights[i] * weights[j] * volume_jacobian - total_volume += node_volume - - u_node = get_node_vars(u, equations, dg, i, j, element) - u_mean += u_node * node_volume - end - return u_mean / total_volume # normalize with the total volume -end - -@inline function compute_u_mean(u::AbstractArray{<:Any, 5}, element, - mesh::AbstractMesh{3}, equations, dg::DGSEM, cache) - @unpack weights = dg.basis - @unpack inverse_jacobian = cache.elements - - node_volume = zero(real(mesh)) - total_volume = zero(node_volume) - - u_mean = zero(get_node_vars(u, equations, dg, 1, 1, 1, element)) - for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg) - volume_jacobian = abs(inv(get_inverse_jacobian(inverse_jacobian, mesh, - i, j, k, element))) - node_volume = weights[i] * weights[j] * weights[k] * volume_jacobian - total_volume += node_volume - - u_node = get_node_vars(u, equations, dg, i, j, k, element) - u_mean += u_node * node_volume - end - return u_mean / total_volume # normalize with the total volume -end +include("compute_u_mean.jl") include("containers.jl") include("indicators.jl") + +incclude("volume_integrals.jl") +include("calc_volume_integral.jl") end # @muladd diff --git a/src/solvers/dgsem/volume_integrals.jl b/src/solvers/dgsem/volume_integrals.jl new file mode 100644 index 00000000000..4fad0155335 --- /dev/null +++ b/src/solvers/dgsem/volume_integrals.jl @@ -0,0 +1,53 @@ +# By default, Julia/LLVM does not use fused multiply-add operations (FMAs). +# Since these FMAs can increase the performance of many numerical algorithms, +# we need to opt-in explicitly. +# See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. +@muladd begin +#! format: noindent + +# `VolumeIntegralEntropyCorrectionShockCapturingCombined` combines the entropy correction +# indicator with a heuristic shock capturing indicator. +# We define this here since it needs to be defined after the `indicators.jl` are included +# in `dgsem.jl`, but before `calc_volume_integral.jl` is included. +const VolumeIntegralEntropyCorrectionShockCapturingCombined = VolumeIntegralAdaptive{<:IndicatorEntropyCorrectionShockCapturingCombined} + +function get_element_variables!(element_variables, u, mesh, equations, + volume_integral::VolumeIntegralEntropyCorrectionShockCapturingCombined, + dg, cache) + element_variables[:indicator_shock_capturing] = volume_integral.indicator_entropy_correction.cache.alpha + return nothing +end + +""" + VolumeIntegralEntropyCorrection(indicator, + volume_integral_default, + volume_integral_entropy_stable) + +Entropy correction volume integral type for DG methods using a convex blending of +a `volume_integral_default` (for example, [`VolumeIntegralWeakForm`](@ref)) and +`volume_integral_entropy_stable` (for example, [`VolumeIntegralPureLGLFiniteVolume`](@ref) +with an entropy stable finite volume flux). + +This is intended to be used with [`IndicatorEntropyCorrection`](@ref), which determines the +amount of blending based on the violation of a cell entropy equality by the volume integral. + +The parameter `scaling ≥ 1` in [`IndicatorEntropyCorrection`](@ref) scales the DG-FV blending +parameter ``\\alpha``(see the [tutorial on shock-capturing](https://trixi-framework.github.io/TrixiDocumentation/stable/tutorials/shock_capturing/#Shock-capturing-with-flux-differencing)) +by a constant, increasing the amount of the subcell FV added in (up to 1, i.e., pure subcell FV). +This can be used to add shock capturing-like behavior. Note though that ``\\alpha`` is computed +here from the entropy defect, **not** using [`IndicatorHennemannGassner`](@ref). + +The use of `VolumeIntegralEntropyCorrection` requires either + `entropy_potential(u, orientation, equations)` for TreeMesh, or + `entropy_potential(u, normal_direction, equations)` for other mesh types +to be defined. +""" +const VolumeIntegralEntropyCorrection = VolumeIntegralAdaptive{<:IndicatorEntropyCorrection} + +function get_element_variables!(element_variables, u, mesh, equations, + volume_integral::VolumeIntegralEntropyCorrection, + dg, cache) + element_variables[:indicator_shock_capturing] = volume_integral.indicator.cache.alpha + return nothing +end +end # @muladd From b3795b368b8ce9aa5fe1af45ceaa940c427e1236 Mon Sep 17 00:00:00 2001 From: Daniel_Doehring Date: Wed, 18 Feb 2026 12:25:38 +0100 Subject: [PATCH 326/360] rm --- src/solvers/dgsem/volume_integrals.jl | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/solvers/dgsem/volume_integrals.jl b/src/solvers/dgsem/volume_integrals.jl index 4fad0155335..d62afe83a18 100644 --- a/src/solvers/dgsem/volume_integrals.jl +++ b/src/solvers/dgsem/volume_integrals.jl @@ -7,8 +7,6 @@ # `VolumeIntegralEntropyCorrectionShockCapturingCombined` combines the entropy correction # indicator with a heuristic shock capturing indicator. -# We define this here since it needs to be defined after the `indicators.jl` are included -# in `dgsem.jl`, but before `calc_volume_integral.jl` is included. const VolumeIntegralEntropyCorrectionShockCapturingCombined = VolumeIntegralAdaptive{<:IndicatorEntropyCorrectionShockCapturingCombined} function get_element_variables!(element_variables, u, mesh, equations, From 78dd0b8fca00364136563cd91c84e3e210fb8250 Mon Sep 17 00:00:00 2001 From: Daniel_Doehring Date: Wed, 18 Feb 2026 12:35:08 +0100 Subject: [PATCH 327/360] merge --- src/equations/compressible_euler_1d.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/equations/compressible_euler_1d.jl b/src/equations/compressible_euler_1d.jl index 5012ba982f4..2acf3f4022a 100644 --- a/src/equations/compressible_euler_1d.jl +++ b/src/equations/compressible_euler_1d.jl @@ -1150,7 +1150,7 @@ of total energy and kinetic energy. end @doc raw""" - entropy_potential(u, orientation::Int, + entropy_potential(u, orientation_or_normal_direction, equations::AbstractCompressibleEulerEquations) Calculate the entropy potential, which for the compressible Euler equations is simply From 3384d62235ee0ba65af159859abb02932bcbd691 Mon Sep 17 00:00:00 2001 From: Daniel Doehring Date: Wed, 18 Feb 2026 16:50:03 +0100 Subject: [PATCH 328/360] Update src/solvers/dgsem/dgsem.jl --- src/solvers/dgsem/dgsem.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/solvers/dgsem/dgsem.jl b/src/solvers/dgsem/dgsem.jl index 5a47a95c381..40928dee2f2 100644 --- a/src/solvers/dgsem/dgsem.jl +++ b/src/solvers/dgsem/dgsem.jl @@ -77,6 +77,6 @@ include("compute_u_mean.jl") include("containers.jl") include("indicators.jl") -incclude("volume_integrals.jl") +include("volume_integrals.jl") include("calc_volume_integral.jl") end # @muladd From 79ef816c8be8fddd9202b521029ca27559241d7f Mon Sep 17 00:00:00 2001 From: Daniel_Doehring Date: Wed, 18 Feb 2026 22:44:53 +0100 Subject: [PATCH 329/360] restruct --- src/solvers/dgsem/calc_volume_integral.jl | 142 ++++++++++++---------- 1 file changed, 77 insertions(+), 65 deletions(-) diff --git a/src/solvers/dgsem/calc_volume_integral.jl b/src/solvers/dgsem/calc_volume_integral.jl index 09b6a575f99..cc09015935e 100644 --- a/src/solvers/dgsem/calc_volume_integral.jl +++ b/src/solvers/dgsem/calc_volume_integral.jl @@ -97,6 +97,81 @@ end return nothing end +@inline function volume_integral_kernel!(du, u, element, mesh, + have_nonconservative_terms, equations, + volume_integral::VolumeIntegralEntropyCorrection, + dg::DGSEM, cache) + @unpack volume_integral_default, volume_integral_stabilized, indicator = volume_integral + @unpack scaling = indicator + @unpack alpha = indicator.cache + du_element_threaded = indicator.cache.volume_integral_values_threaded + + # run default volume integral + volume_integral_kernel!(du, u, element, mesh, + have_nonconservative_terms, equations, + volume_integral_default, dg, cache) + + # Check entropy production of "high order" volume integral. + # + # Note that, for `TreeMesh`, both volume and surface integrals are calculated + # on the reference element. For other mesh types, because the volume integral + # incorporates the scaled contravariant vectors, the surface integral should + # be calculated on the physical element instead. + # + # Minus sign because of the flipped sign of the volume term in the DG RHS. + # No scaling by inverse Jacobian here, as there is no Jacobian multiplication + # in `integrate_reference_element`. + dS_volume_integral = -entropy_change_reference_element(du, u, element, + mesh, equations, + dg, cache) + + # Compute true entropy change given by surface integral of the entropy potential + dS_true = surface_integral_reference_element(entropy_potential, u, element, + mesh, equations, dg, cache) + + # This quantity should be ≤ 0 for an entropy stable volume integral, and + # exactly zero for an entropy conservative volume integral. + entropy_residual = dS_volume_integral - dS_true + + if entropy_residual > 0 + # Store "high order" result + du_FD_element = du_element_threaded[Threads.threadid()] + @views du_FD_element .= du[.., element] + + # Reset pure flux-differencing volume integral + # Note that this assumes that the volume terms are computed first, + # before any surface terms are added. + du[.., element] .= zero(eltype(du)) + + # Calculate entropy stable volume integral contribution + volume_integral_kernel!(du, u, element, mesh, + have_nonconservative_terms, equations, + volume_integral_stabilized, dg, cache) + + # Calculate difference between high and low order FV integral; + # this should be made entropy dissipative if entropy_residual > 0. + @views du_FD_element .= (du_FD_element .- du[.., element]) + + entropy_dissipation = entropy_change_reference_element(du_FD_element, u, + element, + mesh, equations, + dg, cache) + + # Calculate DG-FV blending factor + ratio = regularized_ratio(-entropy_residual, entropy_dissipation) + alpha_element = min(1, scaling * ratio) # TODO: replacing this with a differentiable version of `min` + + # Save blending coefficient for visualization + alpha[element] = alpha_element + + # Blend the high order method back in + @views du[.., element] .= du[.., element] .+ + (1 - alpha_element) .* du_FD_element + end + + return nothing +end + function calc_volume_integral!(du, u, mesh, have_nonconservative_terms, equations, volume_integral, dg::DGSEM, cache) @@ -155,76 +230,13 @@ function calc_volume_integral!(du, u, mesh, have_nonconservative_terms, equations, volume_integral::VolumeIntegralEntropyCorrection, dg::DGSEM, cache) - (; volume_integral_default, volume_integral_stabilized, indicator) = volume_integral - (; scaling) = indicator - (; alpha) = indicator.cache - du_element_threaded = indicator.cache.volume_integral_values_threaded - + @unpack alpha = volume_integral.indicator.cache resize!(alpha, nelements(dg, cache)) @threaded for element in eachelement(dg, cache) - # run default volume integral volume_integral_kernel!(du, u, element, mesh, have_nonconservative_terms, equations, - volume_integral_default, dg, cache) - - # Check entropy production of "high order" volume integral. - # - # Note that, for `TreeMesh`, both volume and surface integrals are calculated - # on the reference element. For other mesh types, because the volume integral - # incorporates the scaled contravariant vectors, the surface integral should - # be calculated on the physical element instead. - # - # Minus sign because of the flipped sign of the volume term in the DG RHS. - # No scaling by inverse Jacobian here, as there is no Jacobian multiplication - # in `integrate_reference_element`. - dS_volume_integral = -entropy_change_reference_element(du, u, element, - mesh, equations, - dg, cache) - - # Compute true entropy change given by surface integral of the entropy potential - dS_true = surface_integral(entropy_potential, u, element, - mesh, equations, dg, cache) - - # This quantity should be ≤ 0 for an entropy stable volume integral, and - # exactly zero for an entropy conservative volume integral. - entropy_residual = dS_volume_integral - dS_true - - if entropy_residual > 0 - # Store "high order" result - du_FD_element = du_element_threaded[Threads.threadid()] - @views du_FD_element .= du[.., element] - - # Reset pure flux-differencing volume integral - # Note that this assumes that the volume terms are computed first, - # before any surface terms are added. - du[.., element] .= zero(eltype(du)) - - # Calculate entropy stable volume integral contribution - volume_integral_kernel!(du, u, element, mesh, - have_nonconservative_terms, equations, - volume_integral_stabilized, dg, cache) - - # Calculate difference between high and low order FV integral; - # this should be made entropy dissipative if entropy_residual > 0. - @views du_FD_element .= (du_FD_element .- du[.., element]) - - entropy_dissipation = entropy_change_reference_element(du_FD_element, u, - element, - mesh, equations, - dg, cache) - - # Calculate DG-FV blending factor - ratio = regularized_ratio(-entropy_residual, entropy_dissipation) - alpha_element = min(1, scaling * ratio) # TODO: replacing this with a differentiable version of `min` - - # Save blending coefficient for visualization - alpha[element] = alpha_element - - # Blend the high order method back in - @views du[.., element] .= du[.., element] .+ - (1 - alpha_element) .* du_FD_element - end + volume_integral, dg, cache) end return nothing From ccbec0df492f1b926b6e54c5e03946f970f92710 Mon Sep 17 00:00:00 2001 From: Hendrik Ranocha Date: Thu, 19 Feb 2026 05:45:19 +0100 Subject: [PATCH 330/360] Update src/callbacks_step/analysis_dg1d.jl --- src/callbacks_step/analysis_dg1d.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/callbacks_step/analysis_dg1d.jl b/src/callbacks_step/analysis_dg1d.jl index 5f61f657573..e4298819a73 100644 --- a/src/callbacks_step/analysis_dg1d.jl +++ b/src/callbacks_step/analysis_dg1d.jl @@ -169,9 +169,9 @@ function entropy_change_reference_element(du_element::AbstractArray{<:Any, 2}, end # calculate surface integral of func(u, equations) * normal on the reference element. -function surface_integral(func::Func, u, element, - mesh::TreeMesh{1}, equations, dg::DGSEM, cache, - args...) where {Func} +function surface_integral_reference_element(func::Func, u, element, + mesh::TreeMesh{1}, equations, dg::DGSEM, cache, + args...) where {Func} u_left = get_node_vars(u, equations, dg, 1, element) u_right = get_node_vars(u, equations, dg, nnodes(dg), element) From a5547b55c10e9530e36416ea67528fc1533609a6 Mon Sep 17 00:00:00 2001 From: Hendrik Ranocha Date: Thu, 19 Feb 2026 05:45:57 +0100 Subject: [PATCH 331/360] format --- src/callbacks_step/analysis_dg1d.jl | 3 ++- src/solvers/dgsem/calc_volume_integral.jl | 14 +++++++------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/callbacks_step/analysis_dg1d.jl b/src/callbacks_step/analysis_dg1d.jl index e4298819a73..8379d7777cc 100644 --- a/src/callbacks_step/analysis_dg1d.jl +++ b/src/callbacks_step/analysis_dg1d.jl @@ -170,7 +170,8 @@ end # calculate surface integral of func(u, equations) * normal on the reference element. function surface_integral_reference_element(func::Func, u, element, - mesh::TreeMesh{1}, equations, dg::DGSEM, cache, + mesh::TreeMesh{1}, equations, dg::DGSEM, + cache, args...) where {Func} u_left = get_node_vars(u, equations, dg, 1, element) u_right = get_node_vars(u, equations, dg, nnodes(dg), element) diff --git a/src/solvers/dgsem/calc_volume_integral.jl b/src/solvers/dgsem/calc_volume_integral.jl index cc09015935e..0232b4d27c4 100644 --- a/src/solvers/dgsem/calc_volume_integral.jl +++ b/src/solvers/dgsem/calc_volume_integral.jl @@ -122,12 +122,12 @@ end # No scaling by inverse Jacobian here, as there is no Jacobian multiplication # in `integrate_reference_element`. dS_volume_integral = -entropy_change_reference_element(du, u, element, - mesh, equations, - dg, cache) + mesh, equations, + dg, cache) # Compute true entropy change given by surface integral of the entropy potential dS_true = surface_integral_reference_element(entropy_potential, u, element, - mesh, equations, dg, cache) + mesh, equations, dg, cache) # This quantity should be ≤ 0 for an entropy stable volume integral, and # exactly zero for an entropy conservative volume integral. @@ -153,9 +153,9 @@ end @views du_FD_element .= (du_FD_element .- du[.., element]) entropy_dissipation = entropy_change_reference_element(du_FD_element, u, - element, - mesh, equations, - dg, cache) + element, + mesh, equations, + dg, cache) # Calculate DG-FV blending factor ratio = regularized_ratio(-entropy_residual, entropy_dissipation) @@ -166,7 +166,7 @@ end # Blend the high order method back in @views du[.., element] .= du[.., element] .+ - (1 - alpha_element) .* du_FD_element + (1 - alpha_element) .* du_FD_element end return nothing From a9bef906ac9c6053d9d523b3de2045570f164611 Mon Sep 17 00:00:00 2001 From: Hendrik Ranocha Date: Thu, 19 Feb 2026 06:31:56 +0100 Subject: [PATCH 332/360] Fix surface_integral -> surface_integral_reference_element --- src/solvers/dgsem/calc_volume_integral.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/solvers/dgsem/calc_volume_integral.jl b/src/solvers/dgsem/calc_volume_integral.jl index 0232b4d27c4..52aaa55c6cf 100644 --- a/src/solvers/dgsem/calc_volume_integral.jl +++ b/src/solvers/dgsem/calc_volume_integral.jl @@ -282,8 +282,8 @@ function calc_volume_integral!(du, u, mesh, dg, cache) # Compute true entropy change given by surface integral of the entropy potential - dS_true = surface_integral(entropy_potential, u, element, - mesh, equations, dg, cache) + dS_true = surface_integral_reference_element(entropy_potential, u, element, + mesh, equations, dg, cache) # This quantity should be ≤ 0 for an entropy stable volume integral, and # exactly zero for an entropy conservative volume integral. From 5472b692856ce7760a4057ce1545d2ed57008573 Mon Sep 17 00:00:00 2001 From: Jesse Chan <1156048+jlchan@users.noreply.github.com> Date: Thu, 19 Feb 2026 09:14:22 -0600 Subject: [PATCH 333/360] Apply suggestions from code review Co-authored-by: Hendrik Ranocha --- src/callbacks_step/analysis_dg1d.jl | 2 +- src/equations/compressible_euler_1d.jl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/callbacks_step/analysis_dg1d.jl b/src/callbacks_step/analysis_dg1d.jl index 8379d7777cc..b9216385e04 100644 --- a/src/callbacks_step/analysis_dg1d.jl +++ b/src/callbacks_step/analysis_dg1d.jl @@ -129,7 +129,7 @@ function integrate_reference_element(func::Func, u, element, @unpack weights = dg.basis # Initialize integral with zeros of the right shape - element_integral = zero(func(u, 1, 1, equations, dg, args...)) + element_integral = zero(func(u, 1, element, equations, dg, args...)) for i in eachnode(dg) element_integral += weights[i] * diff --git a/src/equations/compressible_euler_1d.jl b/src/equations/compressible_euler_1d.jl index 2acf3f4022a..de5917da17e 100644 --- a/src/equations/compressible_euler_1d.jl +++ b/src/equations/compressible_euler_1d.jl @@ -1154,7 +1154,7 @@ end equations::AbstractCompressibleEulerEquations) Calculate the entropy potential, which for the compressible Euler equations is simply -the momentum for the choice of mathematical [`entropy`](@ref) ``S(u) = \frac{\rho s}{\gamma - 1}`` +the momentum for the choice of mathematical [`entropy`](@ref) ``S(u) = -\frac{\rho s}{\gamma - 1}`` with thermodynamic entropy ``s = \ln(p) - \gamma \ln(\rho)``. ## References From aec27cd3ed8efbe55871e602b7fe48139ee9ff16 Mon Sep 17 00:00:00 2001 From: Daniel_Doehring Date: Thu, 19 Feb 2026 23:13:27 +0100 Subject: [PATCH 334/360] analysis --- src/callbacks_step/analysis_dg1d.jl | 6 +++--- src/callbacks_step/analysis_dg2d.jl | 3 +-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/callbacks_step/analysis_dg1d.jl b/src/callbacks_step/analysis_dg1d.jl index 377a55a15d2..80ea31ec0f0 100644 --- a/src/callbacks_step/analysis_dg1d.jl +++ b/src/callbacks_step/analysis_dg1d.jl @@ -153,9 +153,9 @@ function entropy_change_reference_element(du, u, element, end end -function entropy_change_reference_element(du_element::AbstractArray{<:Any, 2}, - u, element, - mesh::AbstractMesh{1}, +# This version is intended to be used with `du_element` (`du` over a single element) +function entropy_change_reference_element(du_element::AbstractArray{<:Any, 2}, u, + element, mesh::AbstractMesh{1}, equations, dg::DGSEM, cache, args...) return integrate_reference_element(u, element, mesh, equations, dg, cache, du_element) do u, i, element, equations, dg, diff --git a/src/callbacks_step/analysis_dg2d.jl b/src/callbacks_step/analysis_dg2d.jl index c0d8d335a1f..e3fe18a8275 100644 --- a/src/callbacks_step/analysis_dg2d.jl +++ b/src/callbacks_step/analysis_dg2d.jl @@ -221,8 +221,7 @@ end # This version is intended to be used with `du_element` (`du` over a single element) function entropy_change_reference_element(du_element::AbstractArray{<:Any, 3}, u, - element, - mesh::AbstractMesh{2}, + element, mesh::AbstractMesh{2}, equations, dg::DGSEM, cache, args...) return integrate_reference_element(u, element, mesh, equations, dg, cache, du_element) do u, i, j, element, equations, dg, From c176ee8a8d2ba0c8897544877380e07b3a6ba245 Mon Sep 17 00:00:00 2001 From: Jesse Chan <1156048+jlchan@users.noreply.github.com> Date: Fri, 20 Feb 2026 08:01:10 -0600 Subject: [PATCH 335/360] Apply suggestions from code review Co-authored-by: Hendrik Ranocha --- src/equations/nonideal_compressible_euler_1d.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/equations/nonideal_compressible_euler_1d.jl b/src/equations/nonideal_compressible_euler_1d.jl index c8ff53cff51..6ea87321fcf 100644 --- a/src/equations/nonideal_compressible_euler_1d.jl +++ b/src/equations/nonideal_compressible_euler_1d.jl @@ -327,7 +327,7 @@ end entropy_potential(u, orientation::Int, equations::NonIdealCompressibleEulerEquations1D) Calculate the entropy potential, which for the compressible Euler equations with general -EOS is ``p v_i / T`` for the choice of entropy ``S(u) = -\rho s``. +EOS is ``p v_i / T`` for the choice of [`entropy`](@ref) ``S(u) = -\rho s``. """ @inline function entropy_potential(u, orientation::Int, equations::NonIdealCompressibleEulerEquations1D) From 176059e3694a34b8c122dc92d9e35c5b1e7090c9 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Fri, 20 Feb 2026 09:04:06 -0500 Subject: [PATCH 336/360] unify docstring --- src/equations/nonideal_compressible_euler_1d.jl | 2 +- src/equations/nonideal_compressible_euler_2d.jl | 6 ------ 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/src/equations/nonideal_compressible_euler_1d.jl b/src/equations/nonideal_compressible_euler_1d.jl index 6ea87321fcf..8be152343bd 100644 --- a/src/equations/nonideal_compressible_euler_1d.jl +++ b/src/equations/nonideal_compressible_euler_1d.jl @@ -324,7 +324,7 @@ end end """ - entropy_potential(u, orientation::Int, equations::NonIdealCompressibleEulerEquations1D) + entropy_potential(u, orientation::Int, equations::AbstractNonIdealCompressibleEulerEquations) Calculate the entropy potential, which for the compressible Euler equations with general EOS is ``p v_i / T`` for the choice of [`entropy`](@ref) ``S(u) = -\rho s``. diff --git a/src/equations/nonideal_compressible_euler_2d.jl b/src/equations/nonideal_compressible_euler_2d.jl index 3eca3b5aa70..1a51195007b 100644 --- a/src/equations/nonideal_compressible_euler_2d.jl +++ b/src/equations/nonideal_compressible_euler_2d.jl @@ -569,12 +569,6 @@ end return rho_e_internal end -""" - entropy_potential(u, orientation_or_normal_direction, equations::NonIdealCompressibleEulerEquations2D) - -Calculate the entropy potential, which for the compressible Euler equations with general -EOS is ``p v_i / T`` for the choice of entropy ``S(u) = -\rho s``. -""" @inline function entropy_potential(u, orientation::Int, equations::NonIdealCompressibleEulerEquations2D) eos = equations.equation_of_state From 05b99965a459f11e994bc67059fa9c04331f0c07 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Fri, 20 Feb 2026 09:11:30 -0500 Subject: [PATCH 337/360] add NEWS.md entry --- NEWS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/NEWS.md b/NEWS.md index 868332feb15..d2bdc5848c1 100644 --- a/NEWS.md +++ b/NEWS.md @@ -20,6 +20,7 @@ This enables in particular adaptive mesh refinement for that solver-mesh combina `volume_integral_blend_high_order`, and `volume_integral_blend_low_order` besides the usual `indicator` argument. In particular, `volume_integral_default` may be e.g. `VolumeIntegralWeakForm` or `VolumeIntegralAdaptive`, i.e., the non-stabilized elements/cells are no longer restricted to `VolumeIntegralFluxDifferencing` only ([#2802]). +- Added `IndicatorEntropyCorrection`. When combined with `VolumeIntegralAdaptive`, this blends together a stabilized and non-stabilized volume integral based on the violation of a volume entropy condition. `IndicatorEntropyCorrectionShockCapturingCombined` additionally guides the blending by taking the maximum of the entropy correction indicator and a shock capturing indicator ([#2764]). ## Changes when updating to v0.15 from v0.14.x From fd3e243228678b8bbd8bc59acf940960e11f890a Mon Sep 17 00:00:00 2001 From: Daniel_Doehring Date: Fri, 20 Feb 2026 15:55:31 +0100 Subject: [PATCH 338/360] comment --- src/solvers/dgsem/indicators.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/solvers/dgsem/indicators.jl b/src/solvers/dgsem/indicators.jl index a3b3cd8dd38..44866e1b222 100644 --- a/src/solvers/dgsem/indicators.jl +++ b/src/solvers/dgsem/indicators.jl @@ -397,7 +397,7 @@ end # this method is used when the indicator is constructed as for shock-capturing volume integrals function IndicatorEntropyCorrection(equations::AbstractEquations, basis::LobattoLegendreBasis; - scaling = true) + scaling = true) # true = 1 in floating point multiplication cache = create_cache(IndicatorEntropyCorrection, equations, basis) return IndicatorEntropyCorrection{typeof(cache), typeof(scaling)}(cache, scaling) end From 7a4ce6f2bad3ecbc20b8fa9e116d0fe328c8a64a Mon Sep 17 00:00:00 2001 From: Jesse Chan <1156048+jlchan@users.noreply.github.com> Date: Sat, 21 Feb 2026 13:32:09 -0600 Subject: [PATCH 339/360] Apply suggestions from code review Co-authored-by: Hendrik Ranocha --- src/equations/nonideal_compressible_euler_1d.jl | 5 +++-- src/solvers/dgsem/indicators.jl | 17 ++++++++--------- src/solvers/dgsem/volume_integrals.jl | 4 ++-- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/equations/nonideal_compressible_euler_1d.jl b/src/equations/nonideal_compressible_euler_1d.jl index 94149b4c278..253a2421b6c 100644 --- a/src/equations/nonideal_compressible_euler_1d.jl +++ b/src/equations/nonideal_compressible_euler_1d.jl @@ -330,10 +330,11 @@ end end """ - entropy_potential(u, orientation::Int, equations::AbstractNonIdealCompressibleEulerEquations) + entropy_potential(u, orientation_or_normal_direction, + equations::AbstractNonIdealCompressibleEulerEquations) Calculate the entropy potential, which for the compressible Euler equations with general -EOS is ``p v_i / T`` for the choice of [`entropy`](@ref) ``S(u) = -\rho s``. +EOS is ``p v_{\text{normal}} / T`` for the choice of [`entropy`](@ref) ``S(u) = -\rho s``. """ @inline function entropy_potential(u, orientation::Int, equations::NonIdealCompressibleEulerEquations1D) diff --git a/src/solvers/dgsem/indicators.jl b/src/solvers/dgsem/indicators.jl index 44866e1b222..5858bbad341 100644 --- a/src/solvers/dgsem/indicators.jl +++ b/src/solvers/dgsem/indicators.jl @@ -370,7 +370,7 @@ end Indicator used for entropy correction using subcell FV schemes, where the blending is determined so that the volume integral entropy production is the -same or more than that of an EC scheme. +same or more than that of an entropy-conservative (EC) scheme. This is intended to guide the convex blending of a `volume_integral_default` (for example, [`VolumeIntegralWeakForm`](@ref)) and `volume_integral_stabilized` @@ -384,8 +384,8 @@ This can be used to add shock capturing-like behavior. Note though that ``\\alph here from the entropy defect, **not** using [`IndicatorHennemannGassner`](@ref). The use of `IndicatorEntropyCorrection` requires either - `entropy_potential(u, orientation, equations)` for TreeMesh, or - `entropy_potential(u, normal_direction, equations)` for other mesh types +`entropy_potential(u, orientation, equations)` for TreeMesh, or +`entropy_potential(u, normal_direction, equations)` for other mesh types to be defined. """ @@ -439,19 +439,18 @@ end Indicator used for entropy correction using subcell FV schemes, where the blending is taken to be the maximum between a blending determined by shock capturing -(indicator_shock_capturing) and a blending determined so that the volume integral -entropy production is the same or more than that of an EC scheme (indicator_entropy_correction). +(`indicator_shock_capturing`) and a blending determined so that the volume integral +entropy production is the same or more than that of an EC scheme (`indicator_entropy_correction`). This is intended to guide the convex blending of a `volume_integral_default` (for example, [`VolumeIntegralWeakForm`](@ref)) and `volume_integral_stabilized` (for example, [`VolumeIntegralPureLGLFiniteVolume`](@ref) with an entropy stable finite volume flux). -The use of `IndicatorEntropyCorrection` requires either - `entropy_potential(u, orientation, equations)` for TreeMesh, or - `entropy_potential(u, normal_direction, equations)` for other mesh types +The use of `IndicatorEntropyCorrectionShockCapturingCombined` requires either +`entropy_potential(u, orientation, equations)` for TreeMesh, or +`entropy_potential(u, normal_direction, equations)` for other mesh types to be defined. - """ struct IndicatorEntropyCorrectionShockCapturingCombined{IndicatorEC, IndicatorSC} <: AbstractIndicator diff --git a/src/solvers/dgsem/volume_integrals.jl b/src/solvers/dgsem/volume_integrals.jl index d62afe83a18..afe139b072a 100644 --- a/src/solvers/dgsem/volume_integrals.jl +++ b/src/solvers/dgsem/volume_integrals.jl @@ -36,8 +36,8 @@ This can be used to add shock capturing-like behavior. Note though that ``\\alph here from the entropy defect, **not** using [`IndicatorHennemannGassner`](@ref). The use of `VolumeIntegralEntropyCorrection` requires either - `entropy_potential(u, orientation, equations)` for TreeMesh, or - `entropy_potential(u, normal_direction, equations)` for other mesh types +`entropy_potential(u, orientation, equations)` for TreeMesh, or +`entropy_potential(u, normal_direction, equations)` for other mesh types to be defined. """ const VolumeIntegralEntropyCorrection = VolumeIntegralAdaptive{<:IndicatorEntropyCorrection} From dbebe9777bc224d7dc1143dc28966c6fcdc85f47 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Sat, 21 Feb 2026 13:40:26 -0600 Subject: [PATCH 340/360] clarify comments on volume/surface integrals --- src/solvers/dgsem/calc_volume_integral.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/solvers/dgsem/calc_volume_integral.jl b/src/solvers/dgsem/calc_volume_integral.jl index c5649e0d79f..6f9e1245757 100644 --- a/src/solvers/dgsem/calc_volume_integral.jl +++ b/src/solvers/dgsem/calc_volume_integral.jl @@ -116,9 +116,9 @@ end # Check entropy production of "high order" volume integral. # - # Note that, for `TreeMesh`, both volume and surface integrals are calculated - # on the reference element. For other mesh types, because the volume integral - # incorporates the scaled contravariant vectors, the surface integral should + # Note that, for `TreeMesh`, `dS_volume_integral` and `dS_true` are calculated + # on the reference element. For other mesh types, because ``dS_volume_integral` + # incorporates the scaled contravariant vectors, `dS_true` should # be calculated on the physical element instead. # # Minus sign because of the flipped sign of the volume term in the DG RHS. From 956e1c64eb5e23c9752e1ec6b812b16e1d4a6605 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Sat, 21 Feb 2026 13:40:32 -0600 Subject: [PATCH 341/360] fix indicator printing --- src/solvers/dgsem/indicators.jl | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/solvers/dgsem/indicators.jl b/src/solvers/dgsem/indicators.jl index 5858bbad341..db1349f854d 100644 --- a/src/solvers/dgsem/indicators.jl +++ b/src/solvers/dgsem/indicators.jl @@ -466,12 +466,11 @@ end function Base.show(io::IO, indicator::IndicatorEntropyCorrectionShockCapturingCombined) @nospecialize indicator # reduce precompilation time - print(io, "IndicatorEntropyCorrectionShockCapturingCombined") - # print(io, "IndicatorEntropyCorrectionShockCapturingCombined(") - # print(io, indicator.indicator_entropy_correction) - # print(io, ", ") - # print(io, indicator.indicator_shock_capturing |> typeof |> nameof) - # print(io, ")") + print(io, "IndicatorEntropyCorrectionShockCapturingCombined(") + print(io, indicator.indicator_entropy_correction) + print(io, ", ") + print(io, indicator.indicator_shock_capturing |> typeof |> nameof) + print(io, ")") return nothing end From 7b02fe57d15766c9e2e53de852b040f6fe875815 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Sat, 21 Feb 2026 14:01:44 -0600 Subject: [PATCH 342/360] remove one-element specialization of `entropy_change_reference_element` --- src/callbacks_step/analysis_dg1d.jl | 14 -------------- src/callbacks_step/analysis_dg2d.jl | 14 -------------- src/solvers/dgsem/calc_volume_integral.jl | 23 +++++++++++++---------- 3 files changed, 13 insertions(+), 38 deletions(-) diff --git a/src/callbacks_step/analysis_dg1d.jl b/src/callbacks_step/analysis_dg1d.jl index 80ea31ec0f0..e53df1dd4c3 100644 --- a/src/callbacks_step/analysis_dg1d.jl +++ b/src/callbacks_step/analysis_dg1d.jl @@ -153,20 +153,6 @@ function entropy_change_reference_element(du, u, element, end end -# This version is intended to be used with `du_element` (`du` over a single element) -function entropy_change_reference_element(du_element::AbstractArray{<:Any, 2}, u, - element, mesh::AbstractMesh{1}, - equations, dg::DGSEM, cache, args...) - return integrate_reference_element(u, element, mesh, equations, dg, cache, - du_element) do u, i, element, equations, dg, - du_element - u_node = get_node_vars(u, equations, dg, i, element) - du_node = get_node_vars(du_element, equations, dg, i) - - dot(cons2entropy(u_node, equations), du_node) - end -end - # calculate surface integral of func(u, equations) * normal on the reference element. function surface_integral_reference_element(func::Func, u, element, mesh::Union{TreeMesh{1}, StructuredMesh{1}}, diff --git a/src/callbacks_step/analysis_dg2d.jl b/src/callbacks_step/analysis_dg2d.jl index e3fe18a8275..5035e831eed 100644 --- a/src/callbacks_step/analysis_dg2d.jl +++ b/src/callbacks_step/analysis_dg2d.jl @@ -219,20 +219,6 @@ function entropy_change_reference_element(du, u, element, end end -# This version is intended to be used with `du_element` (`du` over a single element) -function entropy_change_reference_element(du_element::AbstractArray{<:Any, 3}, u, - element, mesh::AbstractMesh{2}, - equations, dg::DGSEM, cache, args...) - return integrate_reference_element(u, element, mesh, equations, dg, cache, - du_element) do u, i, j, element, equations, dg, - du_element - u_node = get_node_vars(u, equations, dg, i, j, element) - du_node = get_node_vars(du_element, equations, dg, i, j) - - dot(cons2entropy(u_node, equations), du_node) - end -end - # calculate surface integral of func(u, equations) * normal on the reference element. function surface_integral_reference_element(func::Func, u, element, mesh::TreeMesh{2}, equations, dg::DGSEM, diff --git a/src/solvers/dgsem/calc_volume_integral.jl b/src/solvers/dgsem/calc_volume_integral.jl index 6f9e1245757..1a59ea99bbc 100644 --- a/src/solvers/dgsem/calc_volume_integral.jl +++ b/src/solvers/dgsem/calc_volume_integral.jl @@ -151,14 +151,17 @@ end have_nonconservative_terms, equations, volume_integral_stabilized, dg, cache) - # Calculate difference between high and low order FV integral; - # this should be made entropy dissipative if entropy_residual > 0. @views du_FD_element .= (du_FD_element .- du[.., element]) - entropy_dissipation = entropy_change_reference_element(du_FD_element, u, - element, - mesh, equations, - dg, cache) + dS_volume_integral_stabilized = entropy_change_reference_element(du, u, element, + mesh, + equations, dg, + cache) + + # Calculate difference between high and low order FV entropy production; + # this should provide positive entropy dissipation if `entropy_residual > 0`, + # assuming the stabilized volume integral is entropy stable. + entropy_dissipation = dS_volume_integral - dS_volume_integral_stabilized # Calculate DG-FV blending factor ratio = regularized_ratio(-entropy_residual, entropy_dissipation) @@ -311,10 +314,10 @@ function calc_volume_integral!(du, u, mesh, # this should be made entropy dissipative if entropy_residual > 0. @views du_FD_element .= (du_FD_element .- du[.., element]) - entropy_dissipation = entropy_change_reference_element(du_FD_element, u, - element, - mesh, equations, - dg, cache) + entropy_dissipation = -entropy_change_reference_element(du, u, + element, + mesh, equations, + dg, cache) # Calculate DG-FV blending factor as the minimum between the entropy correction # indicator and shock capturing indicator From 8196951e4e0ead79f60a1bd37b2ccbfea547e68c Mon Sep 17 00:00:00 2001 From: Jesse Chan <1156048+jlchan@users.noreply.github.com> Date: Sun, 22 Feb 2026 10:05:34 -0600 Subject: [PATCH 343/360] Apply suggestions from code review Co-authored-by: Hendrik Ranocha --- src/solvers/dgsem/calc_volume_integral.jl | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/solvers/dgsem/calc_volume_integral.jl b/src/solvers/dgsem/calc_volume_integral.jl index 1a59ea99bbc..a2a097b6300 100644 --- a/src/solvers/dgsem/calc_volume_integral.jl +++ b/src/solvers/dgsem/calc_volume_integral.jl @@ -151,8 +151,6 @@ end have_nonconservative_terms, equations, volume_integral_stabilized, dg, cache) - @views du_FD_element .= (du_FD_element .- du[.., element]) - dS_volume_integral_stabilized = entropy_change_reference_element(du, u, element, mesh, equations, dg, @@ -171,7 +169,7 @@ end alpha[element] = alpha_element # Blend the high order method back in - @views du[.., element] .= du[.., element] .+ + @views du[.., element] .= alpha .* du[.., element] .+ (1 - alpha_element) .* du_FD_element end @@ -310,10 +308,6 @@ function calc_volume_integral!(du, u, mesh, have_nonconservative_terms, equations, volume_integral_stabilized, dg, cache) - # Calculate difference between high and low order FV integral; - # this should be made entropy dissipative if entropy_residual > 0. - @views du_FD_element .= (du_FD_element .- du[.., element]) - entropy_dissipation = -entropy_change_reference_element(du, u, element, mesh, equations, @@ -329,7 +323,7 @@ function calc_volume_integral!(du, u, mesh, alpha[element] = alpha_element # Blend the high order method back in - @views du[.., element] .= du[.., element] .+ + @views du[.., element] .= alpha .* du[.., element] .+ (1 - alpha_element) .* du_FD_element end end From cd48a2f186f10978736ded5bb97c706dbc220aed Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Sun, 22 Feb 2026 10:06:44 -0600 Subject: [PATCH 344/360] fix renaming --- src/solvers/dgsem/calc_volume_integral.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/solvers/dgsem/calc_volume_integral.jl b/src/solvers/dgsem/calc_volume_integral.jl index a2a097b6300..c967945a891 100644 --- a/src/solvers/dgsem/calc_volume_integral.jl +++ b/src/solvers/dgsem/calc_volume_integral.jl @@ -169,7 +169,7 @@ end alpha[element] = alpha_element # Blend the high order method back in - @views du[.., element] .= alpha .* du[.., element] .+ + @views du[.., element] .= alpha_element .* du[.., element] .+ (1 - alpha_element) .* du_FD_element end @@ -323,7 +323,7 @@ function calc_volume_integral!(du, u, mesh, alpha[element] = alpha_element # Blend the high order method back in - @views du[.., element] .= alpha .* du[.., element] .+ + @views du[.., element] .= alpha_element .* du[.., element] .+ (1 - alpha_element) .* du_FD_element end end From 6cb9da9627df3b12055c5b815b818c589c7ff3ac Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Sun, 22 Feb 2026 11:42:51 -0600 Subject: [PATCH 345/360] make signs consistent --- src/solvers/dgsem/calc_volume_integral.jl | 26 +++++++++++++++-------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/src/solvers/dgsem/calc_volume_integral.jl b/src/solvers/dgsem/calc_volume_integral.jl index c967945a891..141e254c1e4 100644 --- a/src/solvers/dgsem/calc_volume_integral.jl +++ b/src/solvers/dgsem/calc_volume_integral.jl @@ -151,15 +151,16 @@ end have_nonconservative_terms, equations, volume_integral_stabilized, dg, cache) - dS_volume_integral_stabilized = entropy_change_reference_element(du, u, element, - mesh, - equations, dg, - cache) + dS_volume_integral_stabilized = -entropy_change_reference_element(du, u, + element, + mesh, + equations, dg, + cache) # Calculate difference between high and low order FV entropy production; # this should provide positive entropy dissipation if `entropy_residual > 0`, # assuming the stabilized volume integral is entropy stable. - entropy_dissipation = dS_volume_integral - dS_volume_integral_stabilized + entropy_dissipation = dS_volume_integral_stabilized - dS_volume_integral # Calculate DG-FV blending factor ratio = regularized_ratio(-entropy_residual, entropy_dissipation) @@ -308,10 +309,17 @@ function calc_volume_integral!(du, u, mesh, have_nonconservative_terms, equations, volume_integral_stabilized, dg, cache) - entropy_dissipation = -entropy_change_reference_element(du, u, - element, - mesh, equations, - dg, cache) + dS_volume_integral_stabilized = -entropy_change_reference_element(du, u, + element, + mesh, + equations, + dg, + cache) + + # Calculate difference between high and low order FV entropy production; + # this should provide positive entropy dissipation if `entropy_residual > 0`, + # assuming the stabilized volume integral is entropy stable. + entropy_dissipation = dS_volume_integral_stabilized - dS_volume_integral # Calculate DG-FV blending factor as the minimum between the entropy correction # indicator and shock capturing indicator From 98e38e9fcbc8f6c1440b9673bdc4b8230cc863a6 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Sun, 22 Feb 2026 20:35:06 -0600 Subject: [PATCH 346/360] improve test coverage --- .../elixir_euler_modified_sod_entropy_correction.jl | 4 ++-- test/test_tree_1d_euler.jl | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/examples/tree_1d_dgsem/elixir_euler_modified_sod_entropy_correction.jl b/examples/tree_1d_dgsem/elixir_euler_modified_sod_entropy_correction.jl index d0e3588f65e..57746eff86f 100644 --- a/examples/tree_1d_dgsem/elixir_euler_modified_sod_entropy_correction.jl +++ b/examples/tree_1d_dgsem/elixir_euler_modified_sod_entropy_correction.jl @@ -25,8 +25,8 @@ indicator_sc = IndicatorHennemannGassner(equations, basis, alpha_min = 0.001, alpha_smooth = true, variable = density_pressure) -indicator = IndicatorEntropyCorrectionShockCapturingCombined(indicator_ec, - indicator_sc) +indicator = IndicatorEntropyCorrectionShockCapturingCombined(indicator_entropy_correction = indicator_ec, + indicator_shock_capturing = indicator_sc) volume_integral_default = VolumeIntegralFluxDifferencing(volume_flux) volume_integral_entropy_stable = VolumeIntegralPureLGLFiniteVolumeO2(basis, diff --git a/test/test_tree_1d_euler.jl b/test/test_tree_1d_euler.jl index 71a9c2f27d8..a89fb717f59 100644 --- a/test/test_tree_1d_euler.jl +++ b/test/test_tree_1d_euler.jl @@ -706,6 +706,12 @@ end # Ensure that we do not have excessive memory allocations # (e.g., from type instabilities) @test_allocations(Trixi.rhs!, semi, sol, 1000) + + # Test/cover `:compact` `show` for IndicatorEntropyCorrectionShockCapturingCombined + @test_nowarn show(IOContext(IOBuffer(), :compact => true), MIME"text/plain"(), + indicator) + @test_nowarn show(IOContext(IOBuffer(), :compact => false), MIME"text/plain"(), + indicator) end @trixi_testset "elixir_euler_nonideal_transcritical_wave.jl (Peng Robinson)" begin From e966d2579dae74fb68420c59ea6ad464f9b5a380 Mon Sep 17 00:00:00 2001 From: Daniel_Doehring Date: Mon, 23 Feb 2026 09:10:28 +0100 Subject: [PATCH 347/360] Add create cache and reinit for `VolumeIntegralEntropyCorrection` --- src/solvers/dgsem/calc_volume_integral.jl | 16 ------ src/solvers/dgsem/dgsem.jl | 2 +- ...tegrals.jl => special_volume_integrals.jl} | 49 +++++++++++++++---- 3 files changed, 40 insertions(+), 27 deletions(-) rename src/solvers/dgsem/{volume_integrals.jl => special_volume_integrals.jl} (67%) diff --git a/src/solvers/dgsem/calc_volume_integral.jl b/src/solvers/dgsem/calc_volume_integral.jl index 141e254c1e4..24870ec38df 100644 --- a/src/solvers/dgsem/calc_volume_integral.jl +++ b/src/solvers/dgsem/calc_volume_integral.jl @@ -231,22 +231,6 @@ function calc_volume_integral!(du, u, mesh, return nothing end -function calc_volume_integral!(du, u, mesh, - have_nonconservative_terms, equations, - volume_integral::VolumeIntegralEntropyCorrection, - dg::DGSEM, cache) - @unpack alpha = volume_integral.indicator.cache - resize!(alpha, nelements(dg, cache)) - - @threaded for element in eachelement(dg, cache) - volume_integral_kernel!(du, u, element, mesh, - have_nonconservative_terms, equations, - volume_integral, dg, cache) - end - - return nothing -end - function calc_volume_integral!(du, u, mesh, have_nonconservative_terms, equations, volume_integral::VolumeIntegralEntropyCorrectionShockCapturingCombined, diff --git a/src/solvers/dgsem/dgsem.jl b/src/solvers/dgsem/dgsem.jl index 42780d5c2bc..e5b55fbe180 100644 --- a/src/solvers/dgsem/dgsem.jl +++ b/src/solvers/dgsem/dgsem.jl @@ -77,6 +77,6 @@ include("compute_u_mean.jl") include("containers.jl") include("indicators.jl") -include("volume_integrals.jl") +include("special_volume_integrals.jl") include("calc_volume_integral.jl") end # @muladd diff --git a/src/solvers/dgsem/volume_integrals.jl b/src/solvers/dgsem/special_volume_integrals.jl similarity index 67% rename from src/solvers/dgsem/volume_integrals.jl rename to src/solvers/dgsem/special_volume_integrals.jl index afe139b072a..c4111e99caa 100644 --- a/src/solvers/dgsem/volume_integrals.jl +++ b/src/solvers/dgsem/special_volume_integrals.jl @@ -5,16 +5,7 @@ @muladd begin #! format: noindent -# `VolumeIntegralEntropyCorrectionShockCapturingCombined` combines the entropy correction -# indicator with a heuristic shock capturing indicator. -const VolumeIntegralEntropyCorrectionShockCapturingCombined = VolumeIntegralAdaptive{<:IndicatorEntropyCorrectionShockCapturingCombined} - -function get_element_variables!(element_variables, u, mesh, equations, - volume_integral::VolumeIntegralEntropyCorrectionShockCapturingCombined, - dg, cache) - element_variables[:indicator_shock_capturing] = volume_integral.indicator_entropy_correction.cache.alpha - return nothing -end +# This file contains some specialized volume integrals that require some indicators already to be defined. """ VolumeIntegralEntropyCorrection(indicator, @@ -48,4 +39,42 @@ function get_element_variables!(element_variables, u, mesh, equations, element_variables[:indicator_shock_capturing] = volume_integral.indicator.cache.alpha return nothing end + +function create_cache(mesh, equations, + volume_integral::VolumeIntegralEntropyCorrection, + dg, cache_containers, uEltype) + cache_default = create_cache(mesh, equations, + volume_integral.volume_integral_default, + dg, cache_containers, uEltype) + cache_stabilized = create_cache(mesh, equations, + volume_integral.volume_integral_stabilized, + dg, cache_containers, uEltype) + + resize!(volume_integral.indicator.cache.alpha, nelements(dg, cache_containers)) + + return (; cache_default..., cache_stabilized...) +end + +function resize_volume_integral_cache!(cache, mesh, + volume_integral::VolumeIntegralEntropyCorrection, + new_size) + @unpack volume_integral_default, volume_integral_stabilized = volume_integral + resize_volume_integral_cache!(cache, mesh, volume_integral_default, new_size) + resize_volume_integral_cache!(cache, mesh, volume_integral_stabilized, new_size) + + resize!(volume_integral.indicator.cache.alpha, new_size) + + return nothing +end + +# `VolumeIntegralEntropyCorrectionShockCapturingCombined` combines the entropy correction +# indicator with a heuristic shock capturing indicator. +const VolumeIntegralEntropyCorrectionShockCapturingCombined = VolumeIntegralAdaptive{<:IndicatorEntropyCorrectionShockCapturingCombined} + +function get_element_variables!(element_variables, u, mesh, equations, + volume_integral::VolumeIntegralEntropyCorrectionShockCapturingCombined, + dg, cache) + element_variables[:indicator_shock_capturing] = volume_integral.indicator_entropy_correction.cache.alpha + return nothing +end end # @muladd From 65d446d07be753d39421f751136d61ad347ffa77 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Mon, 23 Feb 2026 10:16:53 -0600 Subject: [PATCH 348/360] improve test coverage, use entropy correction in mixing test move test to proper place, update vals --- ...uler_peng_robinson_transcritical_mixing.jl | 11 ++++++--- test/test_structured_2d.jl | 23 +++++++++++++++++++ test/test_tree_2d_euler.jl | 22 ------------------ 3 files changed, 31 insertions(+), 25 deletions(-) diff --git a/examples/tree_2d_dgsem/elixir_euler_peng_robinson_transcritical_mixing.jl b/examples/tree_2d_dgsem/elixir_euler_peng_robinson_transcritical_mixing.jl index e1e421e5be9..c4be02fd526 100644 --- a/examples/tree_2d_dgsem/elixir_euler_peng_robinson_transcritical_mixing.jl +++ b/examples/tree_2d_dgsem/elixir_euler_peng_robinson_transcritical_mixing.jl @@ -56,9 +56,14 @@ end initial_condition = initial_condition_transcritical_mixing volume_flux = flux_terashima_etal -volume_integral = VolumeIntegralFluxDifferencing(volume_flux) -solver = DGSEM(polydeg = 3, volume_integral = volume_integral, - surface_flux = flux_lax_friedrichs) +surface_flux = FluxPlusDissipation(volume_flux, DissipationLocalLaxFriedrichs()) + +volume_integral_default = VolumeIntegralFluxDifferencing(volume_flux) +volume_integral_entropy_stable = VolumeIntegralPureLGLFiniteVolume(surface_flux) +volume_integral = VolumeIntegralAdaptive(IndicatorEntropyCorrection(equations, basis), + volume_integral_default, + volume_integral_entropy_stable) +solver = DGSEM(basis, surface_flux, volume_integral) cells_per_dimension = (32, 16) coordinates_min = (-0.5, -0.25) diff --git a/test/test_structured_2d.jl b/test/test_structured_2d.jl index 489481ecca0..b51d1042c83 100644 --- a/test/test_structured_2d.jl +++ b/test/test_structured_2d.jl @@ -739,6 +739,29 @@ end @test_allocations(Trixi.rhs!, semi, sol, 100) end +@trixi_testset "elixir_euler_peng_robinson_transcritical_mixing" begin + @test_trixi_include(joinpath(EXAMPLES_DIR, + "elixir_euler_peng_robinson_transcritical_mixing.jl"), + tspan=(0.0, 0.0003), + # note that errors are large because the solution values are of the order 1e5-1e7 + l2=[ + 0.8907552376416852, + 274.6262332037992, + 129.95629990639333, + 94420.33529773205 + ], + linf=[ + 6.617401501819359, + 732.0947275447616, + 403.74606195408825, + 584504.7663076259 + ]) + + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + @test_allocations(Trixi.rhs!, semi, sol, 1000) +end + @trixi_testset "elixir_eulerpolytropic_convergence.jl" begin @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_eulerpolytropic_convergence.jl"), l2=[ diff --git a/test/test_tree_2d_euler.jl b/test/test_tree_2d_euler.jl index d694ac0fa09..46bc63b45c7 100644 --- a/test/test_tree_2d_euler.jl +++ b/test/test_tree_2d_euler.jl @@ -177,28 +177,6 @@ end @test_allocations(Trixi.rhs!, semi, sol, 1000) end -@trixi_testset "elixir_euler_peng_robinson_transcritical_mixing" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_euler_peng_robinson_transcritical_mixing.jl"), - tspan=(0.0, 0.0003), - # note that errors are large because the solution values are of the order 1e5-1e7 - l2=[ - 0.8908754595982343, - 274.62878855174165, - 129.95856100796416, - 94433.86273840266 - ], - linf=[ - 6.623271612803023, - 732.0924019701561, - 403.7976140940856, - 584547.9740598286 - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - @test_allocations(Trixi.rhs!, semi, sol, 1000) -end - @trixi_testset "elixir_euler_source_terms_nonperiodic.jl" begin @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_source_terms_nonperiodic.jl"), From b33190dc213d3adedd183e67588b1ab41454fa62 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Mon, 23 Feb 2026 10:35:59 -0600 Subject: [PATCH 349/360] reuse shock capturing alpha array --- src/solvers/dgsem/calc_volume_integral.jl | 2 +- src/solvers/dgsem/special_volume_integrals.jl | 15 ++++++++++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/solvers/dgsem/calc_volume_integral.jl b/src/solvers/dgsem/calc_volume_integral.jl index 24870ec38df..3042052be0e 100644 --- a/src/solvers/dgsem/calc_volume_integral.jl +++ b/src/solvers/dgsem/calc_volume_integral.jl @@ -238,7 +238,7 @@ function calc_volume_integral!(du, u, mesh, (; volume_integral_default, volume_integral_stabilized, indicator) = volume_integral (; indicator_entropy_correction, indicator_shock_capturing) = indicator (; scaling) = indicator_entropy_correction - (; alpha) = indicator_entropy_correction.cache # TODO: remove array since it's duplicated in indicator_shock_capturing? + (; alpha) = indicator_shock_capturing.cache # since `alpha` is defined in `indicator_shock_capturing`, we reuse it instead du_element_threaded = indicator_entropy_correction.cache.volume_integral_values_threaded resize!(alpha, nelements(dg, cache)) diff --git a/src/solvers/dgsem/special_volume_integrals.jl b/src/solvers/dgsem/special_volume_integrals.jl index c4111e99caa..6c480892495 100644 --- a/src/solvers/dgsem/special_volume_integrals.jl +++ b/src/solvers/dgsem/special_volume_integrals.jl @@ -74,7 +74,20 @@ const VolumeIntegralEntropyCorrectionShockCapturingCombined = VolumeIntegralAdap function get_element_variables!(element_variables, u, mesh, equations, volume_integral::VolumeIntegralEntropyCorrectionShockCapturingCombined, dg, cache) - element_variables[:indicator_shock_capturing] = volume_integral.indicator_entropy_correction.cache.alpha + # here, we reuse `indicator_shock_capturing.cache.alpha` to store the indicator variable + element_variables[:indicator_shock_capturing] = volume_integral.indicator_shock_capturing.cache.alpha + return nothing +end + +# For `VolumeIntegralEntropyCorrectionShockCapturingCombined`, we can reuse the `alpha` array from +# `indicator_shock_capturing` and avoid resizing `indicator_entropy_correction.cache.alpha` +function resize_volume_integral_cache!(cache, mesh, + volume_integral::VolumeIntegralEntropyCorrectionShockCapturingCombined, + new_size) + @unpack volume_integral_default, volume_integral_stabilized = volume_integral + resize_volume_integral_cache!(cache, mesh, volume_integral_default, new_size) + resize_volume_integral_cache!(cache, mesh, volume_integral_stabilized, new_size) + return nothing end end # @muladd From 9adb6871730d05f35b3bbec37c88a087f3a27fd4 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Mon, 23 Feb 2026 11:55:53 -0600 Subject: [PATCH 350/360] move elixir to correct spot --- .../elixir_euler_peng_robinson_transcritical_mixing.jl | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename examples/{tree_2d_dgsem => structured_2d_dgsem}/elixir_euler_peng_robinson_transcritical_mixing.jl (100%) diff --git a/examples/tree_2d_dgsem/elixir_euler_peng_robinson_transcritical_mixing.jl b/examples/structured_2d_dgsem/elixir_euler_peng_robinson_transcritical_mixing.jl similarity index 100% rename from examples/tree_2d_dgsem/elixir_euler_peng_robinson_transcritical_mixing.jl rename to examples/structured_2d_dgsem/elixir_euler_peng_robinson_transcritical_mixing.jl From 544841e4ed248fe1eab914287316d0619cbc7fc5 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Mon, 23 Feb 2026 12:04:14 -0600 Subject: [PATCH 351/360] move resizing outside of calc_volume_integral --- src/solvers/dgsem/calc_volume_integral.jl | 2 -- src/solvers/dgsem/special_volume_integrals.jl | 6 ++++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/solvers/dgsem/calc_volume_integral.jl b/src/solvers/dgsem/calc_volume_integral.jl index 3042052be0e..8ea523264d1 100644 --- a/src/solvers/dgsem/calc_volume_integral.jl +++ b/src/solvers/dgsem/calc_volume_integral.jl @@ -241,8 +241,6 @@ function calc_volume_integral!(du, u, mesh, (; alpha) = indicator_shock_capturing.cache # since `alpha` is defined in `indicator_shock_capturing`, we reuse it instead du_element_threaded = indicator_entropy_correction.cache.volume_integral_values_threaded - resize!(alpha, nelements(dg, cache)) - # Calculate DG-FV blending factors α a-priori for: u_{DG-FV} = u_DG * (1 - α) + u_FV * α alpha_shock_capturing = @trixi_timeit timer() "blending factors" indicator_shock_capturing(u, mesh, diff --git a/src/solvers/dgsem/special_volume_integrals.jl b/src/solvers/dgsem/special_volume_integrals.jl index 6c480892495..e4c21dbe9be 100644 --- a/src/solvers/dgsem/special_volume_integrals.jl +++ b/src/solvers/dgsem/special_volume_integrals.jl @@ -55,6 +55,9 @@ function create_cache(mesh, equations, return (; cache_default..., cache_stabilized...) end +# `resize_volume_integral_cache!` is called after mesh adaptation in `reinitialize_containers!`. +# We only need to resize `volume_integral.indicator.cache.alpha`, which stores the blending factors +# for visualization. function resize_volume_integral_cache!(cache, mesh, volume_integral::VolumeIntegralEntropyCorrection, new_size) @@ -79,6 +82,7 @@ function get_element_variables!(element_variables, u, mesh, equations, return nothing end +# `resize_volume_integral_cache!` is called after mesh adaptation in `reinitialize_containers!`. # For `VolumeIntegralEntropyCorrectionShockCapturingCombined`, we can reuse the `alpha` array from # `indicator_shock_capturing` and avoid resizing `indicator_entropy_correction.cache.alpha` function resize_volume_integral_cache!(cache, mesh, @@ -88,6 +92,8 @@ function resize_volume_integral_cache!(cache, mesh, resize_volume_integral_cache!(cache, mesh, volume_integral_default, new_size) resize_volume_integral_cache!(cache, mesh, volume_integral_stabilized, new_size) + resize!(volume_integral.indicator.indicator_shock_capturing.cache.alpha, new_size) + return nothing end end # @muladd From 3dd41c9cfbc942c3483d4838185a3dbdb611ae4a Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Mon, 23 Feb 2026 12:46:57 -0600 Subject: [PATCH 352/360] add dropped `basis` --- .../elixir_euler_peng_robinson_transcritical_mixing.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/structured_2d_dgsem/elixir_euler_peng_robinson_transcritical_mixing.jl b/examples/structured_2d_dgsem/elixir_euler_peng_robinson_transcritical_mixing.jl index c4be02fd526..de37773e27a 100644 --- a/examples/structured_2d_dgsem/elixir_euler_peng_robinson_transcritical_mixing.jl +++ b/examples/structured_2d_dgsem/elixir_euler_peng_robinson_transcritical_mixing.jl @@ -58,6 +58,7 @@ initial_condition = initial_condition_transcritical_mixing volume_flux = flux_terashima_etal surface_flux = FluxPlusDissipation(volume_flux, DissipationLocalLaxFriedrichs()) +basis = LobattoLegendreBasis(3) volume_integral_default = VolumeIntegralFluxDifferencing(volume_flux) volume_integral_entropy_stable = VolumeIntegralPureLGLFiniteVolume(surface_flux) volume_integral = VolumeIntegralAdaptive(IndicatorEntropyCorrection(equations, basis), From abd97298ef2f59bbeded52427ddd083421bc163e Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Mon, 23 Feb 2026 15:20:18 -0600 Subject: [PATCH 353/360] remove unnecessary resizing --- src/solvers/dgsem/special_volume_integrals.jl | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/solvers/dgsem/special_volume_integrals.jl b/src/solvers/dgsem/special_volume_integrals.jl index e4c21dbe9be..d410f5701b2 100644 --- a/src/solvers/dgsem/special_volume_integrals.jl +++ b/src/solvers/dgsem/special_volume_integrals.jl @@ -84,16 +84,15 @@ end # `resize_volume_integral_cache!` is called after mesh adaptation in `reinitialize_containers!`. # For `VolumeIntegralEntropyCorrectionShockCapturingCombined`, we can reuse the `alpha` array from -# `indicator_shock_capturing` and avoid resizing `indicator_entropy_correction.cache.alpha` +# `indicator_shock_capturing`, which is resized by the call to the shock capturing indicator. function resize_volume_integral_cache!(cache, mesh, volume_integral::VolumeIntegralEntropyCorrectionShockCapturingCombined, new_size) @unpack volume_integral_default, volume_integral_stabilized = volume_integral + resize_volume_integral_cache!(cache, mesh, volume_integral_default, new_size) resize_volume_integral_cache!(cache, mesh, volume_integral_stabilized, new_size) - resize!(volume_integral.indicator.indicator_shock_capturing.cache.alpha, new_size) - return nothing end end # @muladd From 89d2a3c12ea9bf7c0776e343cd9d5dd7f2cf46e9 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Mon, 23 Feb 2026 15:20:33 -0600 Subject: [PATCH 354/360] use only `alpha_shock_capturing` --- src/solvers/dgsem/calc_volume_integral.jl | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/solvers/dgsem/calc_volume_integral.jl b/src/solvers/dgsem/calc_volume_integral.jl index 8ea523264d1..502e31c9f36 100644 --- a/src/solvers/dgsem/calc_volume_integral.jl +++ b/src/solvers/dgsem/calc_volume_integral.jl @@ -238,10 +238,10 @@ function calc_volume_integral!(du, u, mesh, (; volume_integral_default, volume_integral_stabilized, indicator) = volume_integral (; indicator_entropy_correction, indicator_shock_capturing) = indicator (; scaling) = indicator_entropy_correction - (; alpha) = indicator_shock_capturing.cache # since `alpha` is defined in `indicator_shock_capturing`, we reuse it instead du_element_threaded = indicator_entropy_correction.cache.volume_integral_values_threaded # Calculate DG-FV blending factors α a-priori for: u_{DG-FV} = u_DG * (1 - α) + u_FV * α + # Note that we also reuse the `alpha_shock_capturing` array to store the indicator values for visualization. alpha_shock_capturing = @trixi_timeit timer() "blending factors" indicator_shock_capturing(u, mesh, equations, @@ -309,8 +309,9 @@ function calc_volume_integral!(du, u, mesh, ratio = regularized_ratio(-entropy_residual, entropy_dissipation) alpha_element = min(1, max(alpha_shock_capturing[element], scaling * ratio)) - # Save blending coefficient for visualization - alpha[element] = alpha_element + # Save blending coefficient for visualization. Note that we overwrite the data + # in `alpha_shock_capturing[element]`. + alpha_shock_capturing[element] = alpha_element # Blend the high order method back in @views du[.., element] .= alpha_element .* du[.., element] .+ From 72df5d94459b880ffafbc1421d6c821ab69be232 Mon Sep 17 00:00:00 2001 From: Daniel_Doehring Date: Mon, 23 Feb 2026 22:26:23 +0100 Subject: [PATCH 355/360] rm unnecessary specialization --- src/solvers/dgsem/special_volume_integrals.jl | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/src/solvers/dgsem/special_volume_integrals.jl b/src/solvers/dgsem/special_volume_integrals.jl index d410f5701b2..a4631766be8 100644 --- a/src/solvers/dgsem/special_volume_integrals.jl +++ b/src/solvers/dgsem/special_volume_integrals.jl @@ -81,18 +81,4 @@ function get_element_variables!(element_variables, u, mesh, equations, element_variables[:indicator_shock_capturing] = volume_integral.indicator_shock_capturing.cache.alpha return nothing end - -# `resize_volume_integral_cache!` is called after mesh adaptation in `reinitialize_containers!`. -# For `VolumeIntegralEntropyCorrectionShockCapturingCombined`, we can reuse the `alpha` array from -# `indicator_shock_capturing`, which is resized by the call to the shock capturing indicator. -function resize_volume_integral_cache!(cache, mesh, - volume_integral::VolumeIntegralEntropyCorrectionShockCapturingCombined, - new_size) - @unpack volume_integral_default, volume_integral_stabilized = volume_integral - - resize_volume_integral_cache!(cache, mesh, volume_integral_default, new_size) - resize_volume_integral_cache!(cache, mesh, volume_integral_stabilized, new_size) - - return nothing -end end # @muladd From bc542cc7ee12878de7c1c0f0ac9b50f374c05d43 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Mon, 23 Feb 2026 18:40:49 -0600 Subject: [PATCH 356/360] add one more test for coverage --- .../elixir_euler_nonideal_density_wave.jl | 6 +++-- test/test_tree_2d_euler.jl | 25 +++++++++++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/examples/tree_2d_dgsem/elixir_euler_nonideal_density_wave.jl b/examples/tree_2d_dgsem/elixir_euler_nonideal_density_wave.jl index 886c2e46de9..da07889d2f9 100644 --- a/examples/tree_2d_dgsem/elixir_euler_nonideal_density_wave.jl +++ b/examples/tree_2d_dgsem/elixir_euler_nonideal_density_wave.jl @@ -43,10 +43,12 @@ function Trixi.initial_condition_density_wave(x, t, end initial_condition = initial_condition_density_wave +polydeg = 3 +basis = LobattoLegendreBasis(polydeg) volume_flux = flux_terashima_etal volume_integral = VolumeIntegralFluxDifferencing(volume_flux) -solver = DGSEM(polydeg = 3, volume_integral = volume_integral, - surface_flux = flux_lax_friedrichs) +surface_integral = SurfaceIntegralWeakForm(flux_lax_friedrichs) +solver = DGSEM(basis, volume_integral, surface_integral) coordinates_min = (-1.0, -1.0) coordinates_max = (1.0, 1.0) diff --git a/test/test_tree_2d_euler.jl b/test/test_tree_2d_euler.jl index 46bc63b45c7..c8afa8c141a 100644 --- a/test/test_tree_2d_euler.jl +++ b/test/test_tree_2d_euler.jl @@ -177,6 +177,31 @@ end @test_allocations(Trixi.rhs!, semi, sol, 1000) end +@trixi_testset "elixir_euler_nonideal_density_wave.jl (with entropy correction)" begin + @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_nonideal_density_wave.jl"), + solver=DGSEM(basis, flux_lax_friedrichs, + VolumeIntegralAdaptive(IndicatorEntropyCorrection(equations, + basis), + VolumeIntegralWeakForm(), + VolumeIntegralPureLGLFiniteVolume(flux_lax_friedrichs))), + tspan=(0.0, 0.5), + l2=[ + 0.017952014817452303, + 0.003600447385007734, + 0.0036373597485249765, + 0.9083836546745024 + ], + linf=[ + 0.08048478879884935, + 0.020807943563967146, + 0.019544525627337717, + 4.043549641568546 + ]) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + @test_allocations(Trixi.rhs!, semi, sol, 1000) +end + @trixi_testset "elixir_euler_source_terms_nonperiodic.jl" begin @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_source_terms_nonperiodic.jl"), From ac74870c4cb41acad834b18b6dbc1ef05a1df107 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Mon, 23 Feb 2026 21:17:25 -0600 Subject: [PATCH 357/360] fix test --- examples/tree_2d_dgsem/elixir_euler_nonideal_density_wave.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/tree_2d_dgsem/elixir_euler_nonideal_density_wave.jl b/examples/tree_2d_dgsem/elixir_euler_nonideal_density_wave.jl index da07889d2f9..420eb758e4d 100644 --- a/examples/tree_2d_dgsem/elixir_euler_nonideal_density_wave.jl +++ b/examples/tree_2d_dgsem/elixir_euler_nonideal_density_wave.jl @@ -48,7 +48,7 @@ basis = LobattoLegendreBasis(polydeg) volume_flux = flux_terashima_etal volume_integral = VolumeIntegralFluxDifferencing(volume_flux) surface_integral = SurfaceIntegralWeakForm(flux_lax_friedrichs) -solver = DGSEM(basis, volume_integral, surface_integral) +solver = DGSEM(basis, surface_integral, volume_integral) coordinates_min = (-1.0, -1.0) coordinates_max = (1.0, 1.0) From b6551daa7e091e77a09767421f68d8eb1c8b32fa Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Mon, 23 Feb 2026 23:02:24 -0600 Subject: [PATCH 358/360] put surface_flux back into elixir --- examples/tree_2d_dgsem/elixir_euler_nonideal_density_wave.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/tree_2d_dgsem/elixir_euler_nonideal_density_wave.jl b/examples/tree_2d_dgsem/elixir_euler_nonideal_density_wave.jl index 420eb758e4d..54e2c62d28a 100644 --- a/examples/tree_2d_dgsem/elixir_euler_nonideal_density_wave.jl +++ b/examples/tree_2d_dgsem/elixir_euler_nonideal_density_wave.jl @@ -47,8 +47,8 @@ polydeg = 3 basis = LobattoLegendreBasis(polydeg) volume_flux = flux_terashima_etal volume_integral = VolumeIntegralFluxDifferencing(volume_flux) -surface_integral = SurfaceIntegralWeakForm(flux_lax_friedrichs) -solver = DGSEM(basis, surface_integral, volume_integral) +surface_flux = flux_lax_friedrichs +solver = DGSEM(basis, surface_flux, volume_integral) coordinates_min = (-1.0, -1.0) coordinates_max = (1.0, 1.0) From 17a5e1c9291a6f3fa85bcb85b963ea44ffcda343 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Tue, 24 Feb 2026 00:41:10 -0600 Subject: [PATCH 359/360] fix --- src/solvers/dgsem/special_volume_integrals.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/solvers/dgsem/special_volume_integrals.jl b/src/solvers/dgsem/special_volume_integrals.jl index a4631766be8..19ab8be7952 100644 --- a/src/solvers/dgsem/special_volume_integrals.jl +++ b/src/solvers/dgsem/special_volume_integrals.jl @@ -78,7 +78,7 @@ function get_element_variables!(element_variables, u, mesh, equations, volume_integral::VolumeIntegralEntropyCorrectionShockCapturingCombined, dg, cache) # here, we reuse `indicator_shock_capturing.cache.alpha` to store the indicator variable - element_variables[:indicator_shock_capturing] = volume_integral.indicator_shock_capturing.cache.alpha + element_variables[:indicator_shock_capturing] = volume_integral.indicator.indicator_shock_capturing.cache.alpha return nothing end end # @muladd From 950c4870bdc928aba342e24497c8183e025dce35 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Tue, 24 Feb 2026 00:41:26 -0600 Subject: [PATCH 360/360] use AMR, increase test coverage --- ...er_modified_sod_entropy_correction_amr.jl} | 16 ++++++++++---- test/test_tree_1d_euler.jl | 21 +++++++++++++++---- 2 files changed, 29 insertions(+), 8 deletions(-) rename examples/tree_1d_dgsem/{elixir_euler_modified_sod_entropy_correction.jl => elixir_euler_modified_sod_entropy_correction_amr.jl} (83%) diff --git a/examples/tree_1d_dgsem/elixir_euler_modified_sod_entropy_correction.jl b/examples/tree_1d_dgsem/elixir_euler_modified_sod_entropy_correction_amr.jl similarity index 83% rename from examples/tree_1d_dgsem/elixir_euler_modified_sod_entropy_correction.jl rename to examples/tree_1d_dgsem/elixir_euler_modified_sod_entropy_correction_amr.jl index 57746eff86f..009c0c8c75f 100644 --- a/examples/tree_1d_dgsem/elixir_euler_modified_sod_entropy_correction.jl +++ b/examples/tree_1d_dgsem/elixir_euler_modified_sod_entropy_correction_amr.jl @@ -9,8 +9,7 @@ function initial_condition_modified_sod(x, t, equations::CompressibleEulerEquati if x[1] < 0.3 return prim2cons(SVector(1, 0.75, 1), equations) else - # this version of modified sod uses a 100x density and pressure jump - return prim2cons(SVector(0.0125, 0.0, 0.01), equations) + return prim2cons(SVector(0.125, 0.0, 0.1), equations) end end @@ -39,7 +38,7 @@ solver = DGSEM(basis, surface_flux, volume_integral) coordinates_min = 0.0 coordinates_max = 1.0 mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 6, + initial_refinement_level = 3, n_cells_max = 30_000, periodicity = false) @@ -66,10 +65,19 @@ save_solution = SaveSolutionCallback(interval = 100, save_final_solution = true, solution_variables = cons2prim) +amr_indicator = IndicatorLöhner(semi, variable = first) +amr_controller = ControllerThreeLevel(semi, amr_indicator, + base_level = 3, + med_level = 4, med_threshold = 0.1, + max_level = 6, max_threshold = 0.15) +amr_interval = 50 +amr_callback = AMRCallback(semi, amr_controller, + interval = amr_interval) + ############################################################################### # run the simulation -callbacks = CallbackSet(summary_callback, +callbacks = CallbackSet(summary_callback, save_solution, amr_callback, analysis_callback, alive_callback) sol = solve(ode, SSPRK43(); abstol = 1e-6, reltol = 1e-4, diff --git a/test/test_tree_1d_euler.jl b/test/test_tree_1d_euler.jl index a89fb717f59..42889430bf0 100644 --- a/test/test_tree_1d_euler.jl +++ b/test/test_tree_1d_euler.jl @@ -696,12 +696,12 @@ end @test_allocations(Trixi.rhs!, semi, sol, 1000) end -@trixi_testset "elixir_euler_modified_sod_entropy_correction.jl" begin +@trixi_testset "elixir_euler_modified_sod_entropy_correction_amr.jl (IndicatorEntropyCorrectionShockCapturingCombined)" begin @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_euler_modified_sod_entropy_correction.jl"), + "elixir_euler_modified_sod_entropy_correction_amr.jl"), tspan=(0.0, 0.1), - l2=[0.18205439515402314, 0.30149291355224794, 0.5976365681857497], - linf=[0.7019546220790203, 0.8107371518443405, 1.9963507053838896]) + l2=[0.18464446565258658, 0.3140498549283543, 0.6496099923312244], + linf=[0.5635649557004835, 0.8086426785591834, 1.7109218921081835]) # Ensure that we do not have excessive memory allocations # (e.g., from type instabilities) @@ -714,6 +714,19 @@ end indicator) end +@trixi_testset "elixir_euler_modified_sod_entropy_correction_amr.jl (IndicatorEntropyCorrection)" begin + @test_trixi_include(joinpath(EXAMPLES_DIR, + "elixir_euler_modified_sod_entropy_correction_amr.jl"), + indicator=IndicatorEntropyCorrection(equations, basis), + tspan=(0.0, 0.1), + l2=[0.18410518515764235, 0.3151497970340809, 0.6485302765750537], + linf=[0.5561756505040076, 0.8355026358832652, 1.6896042720266948]) + + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + @test_allocations(Trixi.rhs!, semi, sol, 1000) +end + @trixi_testset "elixir_euler_nonideal_transcritical_wave.jl (Peng Robinson)" begin @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_nonideal_transcritical_wave.jl"),