///|
/// One residual/mass observation.
pub(all) struct ConvergenceSample {
step : Int
residual : Double
mass : Double
} derive(Debug)
///|
/// Bounded convergence history for iterative solvers.
pub(all) struct ConvergenceHistory {
capacity : Int
samples : Array[ConvergenceSample]
} derive(Debug)
///|
/// Allocate a convergence history.
pub fn ConvergenceHistory::new(capacity? : Int = 128) -> ConvergenceHistory {
{ capacity: capacity.max(1), samples: Array::new() }
}
///|
/// Append an observation and discard the oldest one after reaching capacity.
pub fn ConvergenceHistory::push(
self : ConvergenceHistory,
step~ : Int,
residual~ : Double,
mass~ : Double,
) -> Unit {
self.samples.push({ step, residual, mass })
if self.samples.length() > self.capacity {
ignore(self.samples.remove(0))
}
}
///|
/// Number of observations stored.
pub fn ConvergenceHistory::length(self : ConvergenceHistory) -> Int {
self.samples.length()
}
///|
/// Read a sample safely.
pub fn ConvergenceHistory::get(
self : ConvergenceHistory,
index : Int,
) -> ConvergenceSample? {
self.samples.get(index)
}
///|
/// Ratio between the latest and earliest residual.
pub fn ConvergenceHistory::residual_ratio(self : ConvergenceHistory) -> Double {
if self.samples.length() < 2 {
1.0
} else {
let first = self.samples[0].residual
if first == 0.0 {
0.0
} else {
self.samples[self.samples.length() - 1].residual / first
}
}
}
///|
/// True when the most recent residual does not increase.
pub fn ConvergenceHistory::is_converging(self : ConvergenceHistory) -> Bool {
if self.samples.length() < 2 {
false
} else {
let n = self.samples.length()
self.samples[n - 1].residual <= self.samples[n - 2].residual
}
}
///|
/// Return the latest residual, or zero for an empty history.
pub fn ConvergenceHistory::latest_residual(self : ConvergenceHistory) -> Double {
if self.samples.length() == 0 {
0.0
} else {
self.samples[self.samples.length() - 1].residual
}
}
///|
/// Export a convergence history as CSV.
pub fn ConvergenceHistory::to_csv(self : ConvergenceHistory) -> String {
let builder = StringBuilder(size_hint=32 + self.samples.length() * 24)
builder.write_string("step,residual,mass\n")
for sample in self.samples {
builder.write_string("\{sample.step},\{sample.residual},\{sample.mass}\n")
}
builder.to_string()
}