///|
pub(all) struct HealthCheck {
name : String
passed : Bool
metric : Double
threshold : Double
message : String
} derive(Debug, ToJson)
///|
pub fn health_check(
name : String,
metric : Double,
threshold : Double,
message : String,
) -> HealthCheck {
{ name, passed: metric <= threshold, metric, threshold, message }
}
///|
pub fn health_charge(
grid : Grid1D,
density : ArrayView[Double],
expected : Double,
tolerance : Double,
) -> HealthCheck {
health_check(
"charge",
(total_charge(grid, density) - expected).abs(),
tolerance,
"total charge error",
)
}
///|
pub fn health_energy(
initial : Double,
final_value : Double,
tolerance : Double,
) -> HealthCheck {
health_check(
"energy",
relative_change(initial, final_value),
tolerance,
"relative energy drift",
)
}
///|
pub fn health_residual(residual : Double, tolerance : Double) -> HealthCheck {
health_check("residual", residual, tolerance, "solver residual")
}
///|
pub fn health_particles(expected : Int, actual : Int) -> HealthCheck {
health_check(
"particles",
(expected - actual).abs().to_double(),
0.0,
"particle count",
)
}
///|
pub fn health_report(checks : ArrayView[HealthCheck]) -> Bool {
checks.all(fn(check) { check.passed })
}
///|
pub fn health_report_to_csv(checks : ArrayView[HealthCheck]) -> String {
let output = StringBuilder()
output.write_string("name,passed,metric,threshold,message\n")
for check in checks {
output.write_string(
"\{check.name},\{check.passed},\{check.metric},\{check.threshold},\{check.message}\n",
)
}
output.to_string()
}
///|
pub fn scenario_health(scenario : SimulationScenario) -> Array[HealthCheck] {
let diagnostics = scenario_trace(scenario)
if diagnostics.length() == 0 {
[]
} else {
let first = diagnostics[0]
let last = diagnostics[diagnostics.length() - 1]
[
health_particles(first.particles, last.particles),
health_energy(first.total_energy(), last.total_energy(), 1.0),
health_check("time", last.time, 1.0e9, "simulation time"),
]
}
}
///|
pub fn benchmark_health(record : BenchmarkRecord) -> Array[HealthCheck] {
[
health_check(
"charge",
record.charge_error,
1.0e-8,
"benchmark charge error",
),
health_check("energy", record.energy_drift, 1.0, "benchmark energy drift"),
health_particles(record.particles, record.final_particles),
]
}
///|
pub fn validation_health(report : ValidationReport) -> HealthCheck {
health_check(
"validation",
if report.valid {
0.0
} else {
1.0
},
0.0,
validation_message(report),
)
}