///|
/// Model-selection and audit helpers for production consensus pipelines.
pub(all) struct ModelSelectionSummary {
candidates : Int
usable : Int
best_index : Int
best_score : Double
mean_ratio : Double
mean_iterations : Double
} derive(Debug, Eq)
///|
pub fn model_report_score(
report : ModelReport,
ratio_weight : Double,
iteration_weight : Double,
) -> Double {
report.ratio * ratio_weight.max(0.0) +
report.score -
report.iterations.to_double() * iteration_weight.max(0.0)
}
///|
pub fn model_report_rank(
reports : ArrayView[ModelReport],
minimum_ratio : Double,
minimum_inliers : Int,
) -> Array[Int] {
let result : Array[Int] = []
for i in 0.. Int {
let mut best = -1
let mut best_score = -1.0e300
for i in 0.. best_score {
best = i
best_score = score
}
}
}
best
}
///|
pub fn model_report_best(
reports : ArrayView[ModelReport],
minimum_ratio : Double,
minimum_inliers : Int,
) -> ModelReport? {
let index = model_report_best_index(reports, minimum_ratio, minimum_inliers)
if index < 0 {
None
} else {
Some(reports[index])
}
}
///|
pub fn model_selection_summary(
reports : ArrayView[ModelReport],
minimum_ratio : Double,
minimum_inliers : Int,
) -> ModelSelectionSummary {
let best = model_report_best_index(reports, minimum_ratio, minimum_inliers)
let mut usable = 0
let mut ratio = 0.0
let mut iterations = 0.0
for report in reports {
if report.is_usable(minimum_ratio, minimum_inliers) {
usable += 1
}
ratio += report.ratio
iterations += report.iterations.to_double()
}
{
candidates: reports.length(),
usable,
best_index: best,
best_score: if best < 0 {
0.0
} else {
model_report_score(reports[best], 1.0, 0.000001)
},
mean_ratio: if reports.length() == 0 {
0.0
} else {
ratio / reports.length().to_double()
},
mean_iterations: if reports.length() == 0 {
0.0
} else {
iterations / reports.length().to_double()
},
}
}
///|
pub fn model_report_filter_converged(
reports : ArrayView[ModelReport],
) -> Array[ModelReport] {
let result : Array[ModelReport] = []
for report in reports {
if report.converged {
result.push(report)
}
}
result
}
///|
pub fn model_report_filter_ratio(
reports : ArrayView[ModelReport],
minimum_ratio : Double,
) -> Array[ModelReport] {
let result : Array[ModelReport] = []
for report in reports {
if report.ratio >= minimum_ratio {
result.push(report)
}
}
result
}
///|
pub fn model_report_filter_inliers(
reports : ArrayView[ModelReport],
minimum_inliers : Int,
) -> Array[ModelReport] {
let result : Array[ModelReport] = []
for report in reports {
if report.inliers >= minimum_inliers {
result.push(report)
}
}
result
}
///|
pub fn model_report_sort_by_score(
reports : ArrayView[ModelReport],
) -> Array[ModelReport] {
let result = reports.to_owned()
result.sort_by(fn(left, right) {
model_report_score(right, 1.0, 0.000001).compare(
model_report_score(left, 1.0, 0.000001),
)
})
result
}
///|
pub fn model_report_sort_by_ratio(
reports : ArrayView[ModelReport],
) -> Array[ModelReport] {
let result = reports.to_owned()
result.sort_by(fn(left, right) { right.ratio.compare(left.ratio) })
result
}
///|
pub fn model_report_consensus_ratio(reports : ArrayView[ModelReport]) -> Double {
if reports.length() == 0 {
return 0.0
}
let mut total = 0.0
for report in reports {
total += report.ratio
}
total / reports.length().to_double()
}
///|
pub fn model_report_consensus_spread(
reports : ArrayView[ModelReport],
) -> Double {
if reports.length() == 0 {
return 0.0
}
let mean = model_report_consensus_ratio(reports)
let mut total = 0.0
for report in reports {
let delta = report.ratio - mean
total += delta * delta
}
(total / reports.length().to_double()).sqrt()
}
///|
pub fn model_report_stability(
reports : ArrayView[ModelReport],
maximum_spread : Double,
) -> Double {
if maximum_spread <= 0.0 {
return 0.0
}
(1.0 - model_report_consensus_spread(reports) / maximum_spread)
.max(0.0)
.min(1.0)
}
///|
pub fn model_report_merge(
left : ModelReport,
right : ModelReport,
) -> ModelReport {
make_model_report(
iterations=left.iterations.max(right.iterations),
inliers=left.inliers.max(right.inliers),
total=left.total.max(right.total),
score=left.score.max(right.score),
converged=left.converged || right.converged,
)
}
///|
pub fn model_report_merge_all(reports : ArrayView[ModelReport]) -> ModelReport {
if reports.length() == 0 {
return make_model_report(
iterations=0,
inliers=0,
total=0,
score=0.0,
converged=false,
)
}
let mut result = reports[0]
for report in reports[1:] {
result = model_report_merge(result, report)
}
result
}
///|
pub fn model_report_reliability(
report : ModelReport,
maximum_iterations : Int,
) -> Double {
let convergence = if report.converged { 1.0 } else { 0.0 }
let iteration = if maximum_iterations <= 0 {
0.0
} else {
(1.0 - report.iterations.to_double() / maximum_iterations.to_double()).max(
0.0,
)
}
convergence * report.ratio * (0.5 + 0.5 * iteration)
}
///|
pub fn model_report_is_degenerate(report : ModelReport) -> Bool {
report.total <= 0 ||
report.inliers <= 0 ||
report.ratio <= 0.0 ||
report.score.is_nan()
}
///|
pub fn model_report_quality_bucket(report : ModelReport) -> Int {
if model_report_is_degenerate(report) {
0
} else if report.ratio >= 0.8 && report.converged {
3
} else if report.ratio >= 0.5 {
2
} else {
1
}
}
///|
pub fn model_report_bucket_counts(
reports : ArrayView[ModelReport],
) -> Array[Int] {
let result = [0, 0, 0, 0]
for report in reports {
let bucket = model_report_quality_bucket(report)
result[bucket] += 1
}
result
}
///|
pub fn model_report_to_csv(report : ModelReport) -> String {
report.iterations.to_string() +
"," +
report.inliers.to_string() +
"," +
report.total.to_string() +
"," +
report.ratio.to_string() +
"," +
report.score.to_string() +
"," +
report.converged.to_string()
}
///|
pub fn model_reports_to_csv(reports : ArrayView[ModelReport]) -> String {
let mut result = "iterations,inliers,total,ratio,score,converged"
for report in reports {
result += "\n" + model_report_to_csv(report)
}
result
}
///|
pub fn model_report_expected_success(
report : ModelReport,
confidence : Double,
) -> Double {
let probability = report.ratio.max(0.0).min(1.0)
let mut miss = 1.0
for _ in 0.. Int {
(maximum_iterations.max(0) - report.iterations.max(0)).max(0)
}