use alloc::vec::Vec;
use witnessed::{WitnessExt, Witnessed};
use crate::value::{GtZero, NormalizedContainer, NormalizedWeight, Value01};
#[derive(Clone, Debug)]
pub enum Map0132 {
Identity,
Linear {
max: f32,
},
IncSigmoid {
low: f32,
high: f32,
},
DecSigmoid {
low: f32,
high: f32,
},
Cauchy {
center: f32,
half_left: f32,
half_right: f32,
},
Custom(fn(f32) -> f32),
}
impl Map0132 {
#[inline]
pub fn apply(&self, raw: f32) -> Result<Witnessed<f32, 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 two = 2.0_f32;
let eps = 10.0 * f32::EPSILON;
let x0 = (low + high) / two;
let k = two * libm::logf(1.0 / eps - 1.0) / (high - low);
1.0 / (1.0 + libm::expf(-k * (raw - x0)))
}
Self::DecSigmoid { low, high } => {
debug_assert!(high > low, "DecSigmoid: high must exceed low");
let two = 2.0_f32;
let eps = 10.0 * f32::EPSILON;
let x0 = (low + high) / two;
let k = two * libm::logf(1.0 / eps - 1.0) / (high - low);
1.0 / (1.0 + libm::expf(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 Metric32<C> {
pub name: &'static str,
measure: fn(&C) -> f32,
map01: Map0132,
}
impl<C> Metric32<C> {
#[inline]
pub fn eval(&self, ctx: &C) -> Result<Witnessed<f32, 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) -> f32) -> MeasuredStage32<C> {
MeasuredStage32 {
name: self.name,
measure,
}
}
}
pub struct MeasuredStage32<C> {
name: &'static str,
measure: fn(&C) -> f32,
}
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) -> f32,
}
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: f32) -> Metric32<C> {
Metric32 {
name: self.name,
measure: self.measure,
map01: Map0132::Linear { max },
}
}
#[inline]
pub fn inc_sigmoid(self, low: f32, high: f32) -> Metric32<C> {
Metric32 {
name: self.name,
measure: self.measure,
map01: Map0132::IncSigmoid { low, high },
}
}
#[inline]
pub fn dec_sigmoid(self, low: f32, high: f32) -> Metric32<C> {
Metric32 {
name: self.name,
measure: self.measure,
map01: Map0132::DecSigmoid { low, high },
}
}
#[inline]
pub fn cauchy(self, center: f32, half_left: f32, half_right: f32) -> Metric32<C> {
Metric32 {
name: self.name,
measure: self.measure,
map01: Map0132::Cauchy {
center,
half_left,
half_right,
},
}
}
#[inline]
pub fn by(self, map01: fn(f32) -> f32) -> Metric32<C> {
Metric32 {
name: self.name,
measure: self.measure,
map01: Map0132::Custom(map01),
}
}
}
#[derive(Clone, Debug)]
pub struct Breakdown32 {
pub name: &'static str,
pub raw: f32,
pub score: f32,
pub weight: f32,
pub contribution: f32,
}
pub struct ScoreSet32<C> {
entries: Vec<(f32, Metric32<C>)>,
}
impl<C> ScoreSet32<C> {
#[inline]
pub fn new() -> Self {
Self {
entries: Vec::new(),
}
}
#[inline]
pub fn push(mut self, weight: f32, 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) -> f32, &'static str> {
let members = self.normalize()?;
Ok(move |ctx: &C| {
let mut total: f32 = 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 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();
Breakdown32 {
name: m.metric.name,
raw,
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<f32> = self.entries.iter().map(|(w, _)| *w).collect();
let sum: f32 = raw_weights.iter().sum();
let normalized_raw: Vec<f32> = 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<f32, 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;