///|
/// One completed resolution of an analytic periodic entropy-wave study.
#derive(Debug)
pub(all) struct ConvergenceRow {
cells : Int32
dx : Double
error : ErrorNorm
observed_order : Double?
steps : Int32
conservation_residual : Conserved
}
///|
/// Run a resolution study against analytic cell averages, not midpoint samples.
/// Resolutions must strictly increase. An order is reported only for positive
/// successive errors; a zero error cannot yield a meaningful logarithmic ratio.
/// Periodicity is required so the reference remains valid after a full transit.
pub fn Gas::entropy_convergence(
self : Gas,
left : Double,
right : Double,
resolutions : Array[Int32],
mean_density : Double,
amplitude : Double,
velocity : Double,
pressure : Double,
end_time : Double,
config : SolverConfig,
) -> Array[ConvergenceRow] raise GasError {
if resolutions.length() < 2 || resolutions.length() > 12 {
raise Invalid("convergence study requires 2..12 resolutions")
}
if config.boundaries != Periodic {
raise Invalid("entropy study needs periodic boundaries")
}
if !finite(end_time) || end_time <= 0.0 {
raise Invalid("study time must be positive and finite")
}
let mut previous_cells = 0
// Validate the complete request before spending time on the first simulation.
for n in resolutions {
if n <= previous_cells {
raise Invalid("resolutions must strictly increase")
}
ignore(Mesh::new(left, right, n))
previous_cells = n
}
let phase = -2.0 * @math.PI * velocity * end_time / (right - left)
if !finite(phase) {
raise Invalid("analytic phase overflow")
}
let output = []
let mut previous_error = 0.0
let mut previous_dx = 0.0
for n in resolutions {
let mesh = Mesh::new(left, right, n)
let initial = self.entropy_wave(
mesh, mean_density, amplitude, velocity, pressure, 0.0,
)
let reference = self.entropy_wave(
mesh, mean_density, amplitude, velocity, pressure, phase,
)
let exact = reference.map(u => self.primitive(u))
let simulation = Simulation::new(self, mesh, initial).run_until(
end_time, config,
)
let error = self.density_error(mesh, simulation.cells, exact)
let observed_order = if previous_error > 0.0 && error.l1 > 0.0 {
let order = @math.ln(previous_error / error.l1) /
@math.ln(previous_dx / mesh.dx)
if finite(order) {
Some(order)
} else {
None
}
} else {
None
}
output.push({
cells: n,
dx: mesh.dx,
error,
observed_order,
steps: simulation.steps,
conservation_residual: simulation.conservation_residual(),
})
previous_error = error.l1
previous_dx = mesh.dx
}
output
}
///|
pub extend ConvergenceRow with @debug.Debug::{to_repr}