use alloc::vec::Vec;
use witnessed::{WitnessExt, Witnessed};
use crate::value::{GtZero, NormalizedContainer, NormalizedWeight, Value01};
pub type Score32 = f32;
#[derive(Clone, Debug)]
pub enum Map0132 {
Identity,
Linear {
max: Score32,
},
IncSigmoid {
low: Score32,
high: Score32,
},
DecSigmoid {
low: Score32,
high: Score32,
},
Cauchy {
center: Score32,
scale: Score32,
},
Custom(fn(Score32) -> Score32),
}
impl Map0132 {
#[inline]
pub fn apply(&self, raw: Score32) -> Result<Witnessed<Score32, Value01>, &'static str> {
let v = match self {
Self::Identity => raw.clamp(0.0, 1.0),
Self::Linear { max } => {
if *max <= 0.0 {
return Err("Map0132::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 mid = (low + high) / 2.0;
let steep = 10.0 / (high - low);
1.0 / (1.0 + libm::expf(-steep * (raw - mid)))
}
Self::DecSigmoid { low, high } => {
debug_assert!(high > low, "DecSigmoid: high must exceed low");
let mid = (low + high) / 2.0;
let steep = 10.0 / (high - low);
1.0 / (1.0 + libm::expf(steep * (raw - mid)))
}
Self::Cauchy { center, scale } => {
let z = (raw - center) / scale;
1.0 / (1.0 + z * z)
}
Self::Custom(f) => f(raw),
};
Value01::witness(v)
}
}
pub struct Metric32<C> {
pub name: &'static str,
measure: fn(&C) -> Score32,
map01: Map0132,
}
impl<C> Metric32<C> {
#[inline]
pub fn eval(&self, ctx: &C) -> Result<Witnessed<Score32, Value01>, &'static str> {
let raw = (self.measure)(ctx);
self.map01.apply(raw)
}
}
impl<C> Clone for Metric32<C> {
fn clone(&self) -> Self {
Self {
name: self.name,
measure: self.measure,
map01: self.map01.clone(),
}
}
}
pub struct MetricNamingStage32 {
name: &'static str,
}
impl MetricNamingStage32 {
#[inline]
pub fn measure(self) -> MeasureStage32 {
MeasureStage32 { name: self.name }
}
}
pub struct MeasureStage32 {
name: &'static str,
}
impl MeasureStage32 {
#[inline]
pub fn by<C>(self, measure: fn(&C) -> Score32) -> MeasuredStage32<C> {
MeasuredStage32 {
name: self.name,
measure,
}
}
}
pub struct MeasuredStage32<C> {
name: &'static str,
measure: fn(&C) -> Score32,
}
impl<C> MeasuredStage32<C> {
#[inline]
pub fn map01(self) -> Map01Stage32<C> {
Map01Stage32 {
name: self.name,
measure: self.measure,
}
}
}
pub struct Map01Stage32<C> {
name: &'static str,
measure: fn(&C) -> Score32,
}
impl<C> Map01Stage32<C> {
#[inline]
pub fn identity(self) -> Metric32<C> {
Metric32 {
name: self.name,
measure: self.measure,
map01: Map0132::Identity,
}
}
#[inline]
pub fn linear(self, max: Score32) -> Metric32<C> {
Metric32 {
name: self.name,
measure: self.measure,
map01: Map0132::Linear { max },
}
}
#[inline]
pub fn inc_sigmoid(self, low: Score32, high: Score32) -> Metric32<C> {
Metric32 {
name: self.name,
measure: self.measure,
map01: Map0132::IncSigmoid { low, high },
}
}
#[inline]
pub fn dec_sigmoid(self, low: Score32, high: Score32) -> Metric32<C> {
Metric32 {
name: self.name,
measure: self.measure,
map01: Map0132::DecSigmoid { low, high },
}
}
#[inline]
pub fn cauchy(self, center: Score32, scale: Score32) -> Metric32<C> {
Metric32 {
name: self.name,
measure: self.measure,
map01: Map0132::Cauchy { center, scale },
}
}
#[inline]
pub fn by(self, map01: fn(Score32) -> Score32) -> Metric32<C> {
Metric32 {
name: self.name,
measure: self.measure,
map01: Map0132::Custom(map01),
}
}
}
#[derive(Clone, Debug)]
pub struct Breakdown32 {
pub name: &'static str,
pub score: Score32,
pub weight: Score32,
pub contribution: Score32,
}
pub struct ScoreSet32<C> {
entries: Vec<(Score32, Metric32<C>)>,
}
impl<C> ScoreSet32<C> {
#[inline]
pub fn new() -> Self {
Self {
entries: Vec::new(),
}
}
#[inline]
pub fn push(mut self, weight: Score32, metric: Metric32<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) -> Score32, &'static str> {
let members = self.normalize()?;
Ok(move |ctx: &C| {
let mut total: Score32 = 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 = Breakdown32>, &'static str> {
let members = self.normalize()?;
Ok(members
.into_iter()
.map(|m| {
let score = m.metric.eval(ctx).map(|w| w.into_inner()).unwrap_or(0.0);
let weight = m.weight.into_inner();
Breakdown32 {
name: m.metric.name,
score,
weight,
contribution: score * weight,
}
})
.collect::<Vec<_>>())
}
fn normalize(self) -> Result<Vec<NormalizedMember32<C>>, &'static str> {
if self.entries.is_empty() {
return Err("ScoreSet32: must contain at least one metric");
}
let raw_weights: Vec<Score32> = self.entries.iter().map(|(w, _)| *w).collect();
let sum: Score32 = raw_weights.iter().sum();
let normalized_raw: Vec<Score32> = 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(NormalizedMember32 { weight, metric })
})
.collect();
members
}
}
impl<C> Default for ScoreSet32<C> {
#[inline]
fn default() -> Self {
Self::new()
}
}
struct NormalizedMember32<C> {
weight: Witnessed<Score32, NormalizedWeight>,
metric: Metric32<C>,
}
#[inline]
pub fn metric32(name: &'static str) -> MetricNamingStage32 {
MetricNamingStage32 { name }
}
#[cfg(test)]
mod tests_for_attack;
#[cfg(test)]
mod tests_for_metric;
#[cfg(test)]
mod tests_for_score_set;