use alloc::vec::Vec;
use witnessed::{WitnessExt, Witnessed};
use crate::value::{GtZero, NormalizedContainer, NormalizedWeight, Value01};
#[derive(Clone, Debug)]
pub enum Map0164 {
Identity,
Linear {
max: f64,
},
IncSigmoid {
low: f64,
high: f64,
},
DecSigmoid {
low: f64,
high: f64,
},
Cauchy {
center: f64,
half_left: f64,
half_right: f64,
},
Custom(fn(f64) -> f64),
}
impl Map0164 {
#[inline]
pub fn apply(&self, raw: f64) -> Result<Witnessed<f64, Value01>, &'static str> {
let v = match self {
Self::Identity => raw.clamp(0.0, 1.0),
Self::Linear { max } => {
if *max <= 0.0 {
return Err("Map0164::Linear: max must be positive");
}
(raw / max).clamp(0.0, 1.0)
}
Self::IncSigmoid { low, high } => {
debug_assert!(high > low, "IncSigmoid: high must exceed low");
let two = 2.0_f64;
let eps = 10.0 * f64::EPSILON;
let x0 = (low + high) / two;
let k = two * libm::log(1.0 / eps - 1.0) / (high - low);
1.0 / (1.0 + libm::exp(-k * (raw - x0)))
}
Self::DecSigmoid { low, high } => {
debug_assert!(high > low, "DecSigmoid: high must exceed low");
let two = 2.0_f64;
let eps = 10.0 * f64::EPSILON;
let x0 = (low + high) / two;
let k = two * libm::log(1.0 / eps - 1.0) / (high - low);
1.0 / (1.0 + libm::exp(k * (raw - x0)))
}
Self::Cauchy {
center,
half_left,
half_right,
} => {
let h = if raw < *center {
*half_left
} else {
*half_right
};
let z = (raw - center) / h;
1.0 / (1.0 + z * z)
}
Self::Custom(f) => f(raw),
};
Value01::witness(v)
}
}
pub struct Metric64<C> {
pub name: &'static str,
measure: fn(&C) -> f64,
map01: Map0164,
}
impl<C> Metric64<C> {
#[inline]
pub fn eval(&self, ctx: &C) -> Result<Witnessed<f64, Value01>, &'static str> {
let raw = (self.measure)(ctx);
self.map01.apply(raw)
}
}
impl<C> Clone for Metric64<C> {
fn clone(&self) -> Self {
Self {
name: self.name,
measure: self.measure,
map01: self.map01.clone(),
}
}
}
pub struct MetricNamingStage64 {
name: &'static str,
}
impl MetricNamingStage64 {
#[inline]
pub fn measure(self) -> MeasureStage64 {
MeasureStage64 { name: self.name }
}
}
pub struct MeasureStage64 {
name: &'static str,
}
impl MeasureStage64 {
#[inline]
pub fn by<C>(self, measure: fn(&C) -> f64) -> MeasuredStage64<C> {
MeasuredStage64 {
name: self.name,
measure,
}
}
}
pub struct MeasuredStage64<C> {
name: &'static str,
measure: fn(&C) -> f64,
}
impl<C> MeasuredStage64<C> {
#[inline]
pub fn map01(self) -> Map01Stage64<C> {
Map01Stage64 {
name: self.name,
measure: self.measure,
}
}
}
pub struct Map01Stage64<C> {
name: &'static str,
measure: fn(&C) -> f64,
}
impl<C> Map01Stage64<C> {
#[inline]
pub fn identity(self) -> Metric64<C> {
Metric64 {
name: self.name,
measure: self.measure,
map01: Map0164::Identity,
}
}
#[inline]
pub fn linear(self, max: f64) -> Metric64<C> {
Metric64 {
name: self.name,
measure: self.measure,
map01: Map0164::Linear { max },
}
}
#[inline]
pub fn inc_sigmoid(self, low: f64, high: f64) -> Metric64<C> {
Metric64 {
name: self.name,
measure: self.measure,
map01: Map0164::IncSigmoid { low, high },
}
}
#[inline]
pub fn dec_sigmoid(self, low: f64, high: f64) -> Metric64<C> {
Metric64 {
name: self.name,
measure: self.measure,
map01: Map0164::DecSigmoid { low, high },
}
}
#[inline]
pub fn cauchy(self, center: f64, half_left: f64, half_right: f64) -> Metric64<C> {
Metric64 {
name: self.name,
measure: self.measure,
map01: Map0164::Cauchy {
center,
half_left,
half_right,
},
}
}
#[inline]
pub fn by(self, map01: fn(f64) -> f64) -> Metric64<C> {
Metric64 {
name: self.name,
measure: self.measure,
map01: Map0164::Custom(map01),
}
}
}
#[derive(Clone, Debug)]
pub struct Breakdown64 {
pub name: &'static str,
pub raw: f64,
pub score: f64,
pub weight: f64,
pub contribution: f64,
}
pub struct ScoreSet64<C> {
entries: Vec<(f64, Metric64<C>)>,
}
impl<C> ScoreSet64<C> {
#[inline]
pub fn new() -> Self {
Self {
entries: Vec::new(),
}
}
#[inline]
pub fn push(mut self, weight: f64, metric: Metric64<C>) -> Result<Self, &'static str> {
let _validated = GtZero::witness(weight)?;
self.entries.push((weight, metric));
Ok(self)
}
pub fn sum(self) -> Result<impl Fn(&C) -> f64, &'static str> {
let members = self.normalize()?;
Ok(move |ctx: &C| {
let mut total: f64 = 0.0;
for m in &members {
if let Ok(score) = m.metric.eval(ctx) {
total += score.into_inner() * m.weight.into_inner();
}
}
total
})
}
pub fn breakdown(self, ctx: &C) -> Result<impl IntoIterator<Item = Breakdown64>, &'static str> {
let members = self.normalize()?;
Ok(members
.into_iter()
.map(|m| {
let raw = (m.metric.measure)(ctx);
let score = m
.metric
.map01
.apply(raw)
.map(|w| w.into_inner())
.unwrap_or(0.0);
let weight = m.weight.into_inner();
Breakdown64 {
name: m.metric.name,
raw,
score,
weight,
contribution: score * weight,
}
})
.collect::<Vec<_>>())
}
fn normalize(self) -> Result<Vec<NormalizedMember64<C>>, &'static str> {
if self.entries.is_empty() {
return Err("ScoreSet64: must contain at least one metric");
}
let raw_weights: Vec<f64> = self.entries.iter().map(|(w, _)| *w).collect();
let sum: f64 = raw_weights.iter().sum();
let normalized_raw: Vec<f64> = raw_weights.iter().map(|w| w / sum).collect();
let mut sorted = normalized_raw.clone();
sorted.sort_by(|a, b| a.partial_cmp(b).unwrap_or(core::cmp::Ordering::Equal));
let container = NormalizedContainer::witness(sorted)?;
let members: Result<Vec<_>, _> = self
.entries
.into_iter()
.zip(normalized_raw.iter())
.map(|((_raw_weight, metric), &nw)| {
let weight = nw
.witness()
.by(|v| NormalizedWeight::from_normalized_container(*v, &container))?;
Ok(NormalizedMember64 { weight, metric })
})
.collect();
members
}
}
impl<C> Default for ScoreSet64<C> {
#[inline]
fn default() -> Self {
Self::new()
}
}
struct NormalizedMember64<C> {
weight: Witnessed<f64, NormalizedWeight>,
metric: Metric64<C>,
}
#[inline]
pub fn metric64(name: &'static str) -> MetricNamingStage64 {
MetricNamingStage64 { name }
}
#[cfg(test)]
mod tests_for_attack;
#[cfg(test)]
mod tests_for_metric;
#[cfg(test)]
mod tests_for_score_set;