///|
pub(all) struct InvariantCheck {
name : String
passed : Bool
observed : Double
tolerance : Double
} derive(Debug, ToJson)
///|
pub fn invariant(
name : String,
observed : Double,
tolerance : Double,
) -> InvariantCheck {
{ name, passed: observed <= tolerance, observed, tolerance }
}
///|
pub fn invariant_shape(
name : String,
actual : Int,
expected : Int,
) -> InvariantCheck {
invariant(name, (actual - expected).abs().to_double(), 0.0)
}
///|
pub fn invariant_close(
name : String,
actual : Double,
expected : Double,
tolerance : Double,
) -> InvariantCheck {
invariant(name, (actual - expected).abs(), tolerance)
}
///|
pub fn invariant_charge(
grid : Grid1D,
density : ArrayView[Double],
expected : Double,
tolerance : Double,
) -> InvariantCheck {
invariant("charge", (total_charge(grid, density) - expected).abs(), tolerance)
}
///|
pub fn invariant_zero_mean(
name : String,
values : ArrayView[Double],
tolerance : Double,
) -> InvariantCheck {
invariant(name, mean(values).abs(), tolerance)
}
///|
pub fn invariant_positive(
name : String,
values : ArrayView[Double],
) -> InvariantCheck {
invariant(name, (-min_value(values, 0.0)).max(0.0), 0.0)
}
///|
pub fn invariant_bounds(
name : String,
values : ArrayView[Double],
low : Double,
high : Double,
) -> InvariantCheck {
let lower_error = (low - min_value(values, low)).max(0.0)
let upper_error = (max_value(values, high) - high).max(0.0)
invariant(name, lower_error.max(upper_error), 0.0)
}
///|
pub fn invariant_all(checks : ArrayView[InvariantCheck]) -> Bool {
checks.all(fn(check) { check.passed })
}
///|
pub fn invariant_max_error(checks : ArrayView[InvariantCheck]) -> Double {
max_value(checks.map(fn(check) { check.observed }), 0.0)
}
///|
pub fn invariant_to_csv(checks : ArrayView[InvariantCheck]) -> String {
let output = StringBuilder()
output.write_string("name,passed,observed,tolerance\n")
for check in checks {
output.write_string(
"\{check.name},\{check.passed},\{check.observed},\{check.tolerance}\n",
)
}
output.to_string()
}
///|
pub fn pic_invariants(
state : PicState,
expected_charge : Double,
) -> Array[InvariantCheck] {
[
invariant_shape(
"field_shape",
state.field.electric.length(),
state.grid.cells,
),
invariant_charge(
state.grid,
state.field.charge_density,
expected_charge,
1.0e-8,
),
invariant_bounds(
"particle_position",
state.particles.map(fn(particle) { particle.x }),
0.0,
state.grid.length,
),
]
}
///|
pub fn vlasov_invariants(
state : VlasovState,
expected_mass : Double,
) -> Array[InvariantCheck] {
[
invariant_close(
"distribution_mass",
distribution_integral(state),
expected_mass,
1.0e-8,
),
invariant_shape(
"distribution_density_shape",
state.charge_density.length(),
state.config.grid.cells,
),
]
}
///|
pub fn field_invariants(field : Field1D) -> Array[InvariantCheck] {
[
invariant_shape("electric_shape", field.electric.length(), field.grid.cells),
invariant_shape(
"potential_shape",
field.potential.length(),
field.grid.cells,
),
invariant_zero_mean("electric_mean", field.electric, 1.0e-8),
invariant_zero_mean("potential_mean", field.potential, 1.0e-8),
]
}
///|
pub fn invariant_report(checks : ArrayView[InvariantCheck]) -> String {
"passed=\{invariant_all(checks)}\nmax_error=\{invariant_max_error(checks)}\n\{invariant_to_csv(checks)}"
}