///|
/// Frozen sorted calibration values. The underlying array is never returned.
pub struct Scores {
priv sorted : Array[Double]
}
///|
/// Accepts signed scores (needed by conformalized quantile regression).
pub fn Scores::new(values : Array[Double]) -> Scores raise ConformalError {
validate_values(values, "scores")
let sorted = values.copy()
sorted.sort()
{ sorted, }
}
///|
pub fn Scores::count(self : Scores) -> Int32 {
self.sorted.length()
}
///|
/// Defensive copy: mutating this result cannot alter a fitted calibrator.
pub fn Scores::values(self : Scores) -> Array[Double] {
self.sorted.copy()
}
///|
/// One-based finite-sample rank. n+1 deliberately denotes positive infinity.
pub fn conformal_rank(n : Int32, alpha : Double) -> Int32 raise ConformalError {
validate_alpha(alpha)
if n < 1 || n > 100000 {
raise Invalid("calibration size must be in 1..100000")
}
// Compare the discrete p-value grid directly, avoiding cancellation in 1-alpha.
let denominator = (n + 1).to_double()
let mut excluded = (denominator * alpha).floor().to_int()
while excluded > 0 && excluded.to_double() / denominator > alpha {
excluded = excluded - 1
}
while excluded < n && (excluded + 1).to_double() / denominator <= alpha {
excluded = excluded + 1
}
n + 1 - excluded
}
///|
pub fn Scores::threshold(
self : Scores,
alpha : Double,
) -> Double raise ConformalError {
let rank = conformal_rank(self.count(), alpha)
if rank > self.count() {
@double.infinity
} else {
self.sorted[rank - 1]
}
}
///|
/// Conservative deterministic upper-tail p-value; includes all ties and +1.
pub fn Scores::p_value(
self : Scores,
score : Double,
) -> Double raise ConformalError {
if !finite(score) {
raise Invalid("candidate score must be finite")
}
let mut low = 0
let mut high = self.count()
while low < high {
let mid = low + (high - low) / 2
if self.sorted[mid] < score {
low = mid + 1
} else {
high = mid
}
}
(self.count() - low + 1).to_double() / (self.count() + 1).to_double()
}