///|
pub(all) struct QualityMetric {
name : String
value : Double
tolerance : Double
passed : Bool
} derive(Debug, ToJson)
///|
pub fn metric(
name : String,
value : Double,
tolerance : Double,
) -> QualityMetric {
{ name, value, tolerance, passed: value <= tolerance }
}
///|
pub fn metric_l2(
name : String,
actual : ArrayView[Double],
expected : ArrayView[Double],
tolerance : Double,
) -> QualityMetric {
metric(name, field_l2_error(actual, expected), tolerance)
}
///|
pub fn metric_max_abs(
name : String,
values : ArrayView[Double],
tolerance : Double,
) -> QualityMetric {
metric(name, max_abs_value(values), tolerance)
}
///|
pub fn metric_relative(
name : String,
actual : Double,
expected : Double,
tolerance : Double,
) -> QualityMetric {
metric(name, relative_error(expected, actual), tolerance)
}
///|
pub fn metric_count(
name : String,
actual : Int,
expected : Int,
) -> QualityMetric {
metric(name, (actual - expected).abs().to_double(), 0.0)
}
///|
pub fn all_metrics_pass(metrics : ArrayView[QualityMetric]) -> Bool {
metrics.all(fn(item) { item.passed })
}
///|
pub fn maximum_metric(metrics : ArrayView[QualityMetric]) -> Double {
max_value(metrics.map(fn(item) { item.value }), 0.0)
}
///|
pub fn metric_names(metrics : ArrayView[QualityMetric]) -> Array[String] {
metrics.map(fn(item) { item.name })
}
///|
pub fn metrics_to_csv(metrics : ArrayView[QualityMetric]) -> String {
let output = StringBuilder()
output.write_string("name,value,tolerance,passed\n")
for item in metrics {
output.write_string(
"\{item.name},\{item.value},\{item.tolerance},\{item.passed}\n",
)
}
output.to_string()
}
///|
pub fn field_quality_metrics(
grid : Grid1D,
field : Field1D,
) -> Array[QualityMetric] {
[
metric_count("electric_length", field.electric.length(), grid.cells),
metric_count("potential_length", field.potential.length(), grid.cells),
metric("electric_mean", mean(field.electric).abs(), 1.0e-8),
metric("potential_mean", mean(field.potential).abs(), 1.0e-8),
]
}
///|
pub fn particle_quality_metrics(
grid : Grid1D,
particles : ArrayView[Particle],
) -> Array[QualityMetric] {
[
metric(
"position_range",
if boundary_valid(grid, particles) {
0.0
} else {
1.0
},
0.0,
),
metric("weight_sum", total_particle_charge(particles).abs(), 1.0e100),
metric("speed_range", sample_span(particle_speeds(particles)), 1.0e100),
]
}
///|
pub fn scenario_quality_metrics(
scenario : SimulationScenario,
) -> Array[QualityMetric] {
let state = scenario_run(scenario)
let metrics = particle_quality_metrics(scenario.grid, state.particles)
let field_metrics = field_quality_metrics(scenario.grid, state.field)
let output = metrics.copy()
for item in field_metrics {
output.push(item)
}
output
}
///|
pub fn quality_report(metrics : ArrayView[QualityMetric]) -> String {
let output = StringBuilder()
output.write_string("passed=\{all_metrics_pass(metrics)}\n")
output.write_string(metrics_to_csv(metrics))
output.to_string()
}
///|
pub fn compare_metric_sets(
first : ArrayView[QualityMetric],
second : ArrayView[QualityMetric],
) -> Double {
let count = first.length().min(second.length())
let mut total = 0.0
for i in 0..