///|
/// Margin is positive on the permitted side. Offset-factor equality is rejected.
pub(all) struct ConstraintDiagnostic {
code : String
value : Double
limit : Double
margin : Double
passed : Bool
location : Location
}
///|
pub(all) struct Diagnostics {
pressure : ConstraintDiagnostic
offset_factor : ConstraintDiagnostic
}
///|
/// Analysis and Program must come from the same calculation.
pub fn diagnose(
p : Program,
a : Analysis,
pressure_limit : Double,
) -> Result[Diagnostics, String] {
if !finite(pressure_limit) ||
pressure_limit <= 0.0 ||
pressure_limit >= @math.PI / 2.0 {
return Err("pressure limit must be in (0, pi/2) radians")
}
let pressure_location = match
locate_sample(p, a.points, a.pressure_sample_index) {
Ok(l) => l
Err(e) => return Err(e)
}
let factor_location = match
locate_sample(p, a.points, a.offset_factor_sample_index) {
Ok(l) => l
Err(e) => return Err(e)
}
Ok({
pressure: {
code: "pressure_limit",
value: a.max_pressure,
limit: pressure_limit,
margin: pressure_limit - a.max_pressure,
passed: a.max_pressure <= pressure_limit,
location: pressure_location,
},
offset_factor: {
code: "offset_factor_limit",
value: a.min_offset_factor,
limit: Offset_factor_min,
margin: a.min_offset_factor - Offset_factor_min,
passed: a.min_offset_factor > Offset_factor_min,
location: factor_location,
},
})
}
///|
pub(all) struct SamplingLevel {
nominal_resolution : Int32
sample_count : Int32
pressure : ConstraintDiagnostic
offset_factor : ConstraintDiagnostic
crossings : CrossingCheck
state : String
}
///|
/// Last two levels' absolute metric changes; no convergence verdict or error bound.
pub(all) struct SamplingComparison {
levels : Array[SamplingLevel]
pressure_change : Double
offset_factor_change : Double
state_consistent : Bool
}
///|
fn comparison_resolutions(resolution : Int32) -> Array[Int32] {
if resolution <= Json_resolution_max / 4 {
[resolution, resolution * 2, resolution * 4]
} else {
[resolution / 4, resolution / 2, resolution]
}
}
///|
/// Reuses the current result only at its exact nominal resolution.
fn compare_sampling_cached(
p : Program,
g : Geometry,
resolutions : Array[Int32],
pressure_limit : Double,
omega : Double,
cached : Analysis?,
) -> Result[SamplingComparison, String] {
if resolutions.length() < 2 || resolutions.length() > Comparison_levels_max {
return Err(
"sampling comparison requires 2..\{Comparison_levels_max} levels",
)
}
let mut previous = 0
for resolution in resolutions {
if resolution < Resolution_min ||
resolution > Library_resolution_max ||
resolution <= previous {
return Err(
"comparison resolutions must be strictly increasing and within library limits",
)
}
previous = resolution
}
let levels : Array[SamplingLevel] = []
for resolution in resolutions {
let a = match cached {
Some(a) if a.nominal_resolution == resolution => a
_ =>
match analyze(p, g, resolution, pressure_limit, omega) {
Ok(a) => a
Err(e) => return Err(e)
}
}
let d = match diagnose(p, a, pressure_limit) {
Ok(d) => d
Err(e) => return Err(e)
}
levels.push({
nominal_resolution: resolution,
sample_count: a.points.length(),
pressure: d.pressure,
offset_factor: d.offset_factor,
crossings: a.crossings,
state: a.state(),
})
}
let last = levels[levels.length() - 1]
let before = levels[levels.length() - 2]
Ok({
levels,
pressure_change: (last.pressure.value - before.pressure.value).abs(),
offset_factor_change: (last.offset_factor.value - before.offset_factor.value).abs(),
state_consistent: levels.all(fn(level) { level.state == last.state }),
})
}
///|
/// Full independent sampled checks at each level, not continuous certification.
pub fn compare_sampling(
p : Program,
g : Geometry,
resolutions : Array[Int32],
pressure_limit : Double,
omega : Double,
) -> Result[SamplingComparison, String] {
compare_sampling_cached(p, g, resolutions, pressure_limit, omega, None)
}