use witnessed::Witnessed;
use crate::value::{GtZero, 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, F = fn(&C) -> f32> {
pub name: &'static str,
measure: F,
map01: Map0132,
_phantom: core::marker::PhantomData<fn(&C)>,
}
impl<C, F: Fn(&C) -> f32> Metric32<C, F> {
#[inline]
pub fn eval(&self, ctx: &C) -> Result<Witnessed<f32, Value01>, &'static str> {
let raw = (self.measure)(ctx);
self.map01.apply(raw)
}
#[inline]
pub fn make_breakdown(&self, weight: f32, ctx: &C) -> Breakdown32 {
let raw = (self.measure)(ctx);
let score = self
.map01
.apply(raw)
.map(Witnessed::into_inner)
.unwrap_or(0.0);
Breakdown32 {
name: self.name,
raw,
score,
weight,
contribution: score * weight,
}
}
}
impl<C, F: Clone> Clone for Metric32<C, F> {
fn clone(&self) -> Self {
Self {
name: self.name,
measure: self.measure.clone(),
map01: self.map01.clone(),
_phantom: core::marker::PhantomData,
}
}
}
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, F>(self, measure: F) -> MeasuredStage32<C, F>
where
F: Fn(&C) -> f32,
{
MeasuredStage32::<C, F> {
name: self.name,
measure,
_phantom: core::marker::PhantomData,
}
}
}
pub struct MeasuredStage32<C, F = fn(&C) -> f32> {
name: &'static str,
measure: F,
_phantom: core::marker::PhantomData<fn(&C)>,
}
impl<C, F> MeasuredStage32<C, F> {
#[inline]
pub fn map01(self) -> Map01Stage32<C, F> {
Map01Stage32::<C, F> {
name: self.name,
measure: self.measure,
_phantom: core::marker::PhantomData,
}
}
}
pub struct Map01Stage32<C, F = fn(&C) -> f32> {
name: &'static str,
measure: F,
_phantom: core::marker::PhantomData<fn(&C)>,
}
impl<C, F> Map01Stage32<C, F> {
#[inline]
pub fn identity(self) -> Metric32<C, F> {
Metric32::<C, F> {
name: self.name,
measure: self.measure,
map01: Map0132::Identity,
_phantom: core::marker::PhantomData,
}
}
#[inline]
pub fn linear(self, max: f32) -> Metric32<C, F> {
Metric32::<C, F> {
name: self.name,
measure: self.measure,
map01: Map0132::Linear { max },
_phantom: core::marker::PhantomData,
}
}
#[inline]
pub fn inc_sigmoid(self, low: f32, high: f32) -> Metric32<C, F> {
Metric32::<C, F> {
name: self.name,
measure: self.measure,
map01: Map0132::IncSigmoid { low, high },
_phantom: core::marker::PhantomData,
}
}
#[inline]
pub fn dec_sigmoid(self, low: f32, high: f32) -> Metric32<C, F> {
Metric32::<C, F> {
name: self.name,
measure: self.measure,
map01: Map0132::DecSigmoid { low, high },
_phantom: core::marker::PhantomData,
}
}
#[inline]
pub fn cauchy(self, center: f32, half_left: f32, half_right: f32) -> Metric32<C, F> {
Metric32::<C, F> {
name: self.name,
measure: self.measure,
map01: Map0132::Cauchy {
center,
half_left,
half_right,
},
_phantom: core::marker::PhantomData,
}
}
#[inline]
pub fn by(self, map01: fn(f32) -> f32) -> Metric32<C, F> {
Metric32::<C, F> {
name: self.name,
measure: self.measure,
map01: Map0132::Custom(map01),
_phantom: core::marker::PhantomData,
}
}
}
#[derive(Clone, Debug)]
pub struct Breakdown32 {
pub name: &'static str,
pub raw: f32,
pub score: f32,
pub weight: f32,
pub contribution: f32,
}
pub trait ScoreSetTrait32<C> {
fn weighted_sum(&self, weights: &[f32], ctx: &C) -> f32;
fn collect_breakdown(&self, weights: &[f32], ctx: &C) -> alloc::vec::Vec<Breakdown32>;
}
pub struct Scored32<C, T: ScoreSetTrait32<C>> {
metrics: T,
weights: alloc::vec::Vec<f32>,
_phantom: core::marker::PhantomData<fn(&C)>,
}
impl<C, T: ScoreSetTrait32<C>> Scored32<C, T> {
#[inline]
pub fn new(metrics: T, raw_weights: &[f32]) -> Result<Self, &'static str> {
for &w in raw_weights {
let _ = GtZero::witness(w)?;
}
let sum: f32 = raw_weights.iter().sum();
let weights: alloc::vec::Vec<f32> = raw_weights.iter().map(|w| w / sum).collect();
Ok(Self {
metrics,
weights,
_phantom: core::marker::PhantomData,
})
}
#[inline]
pub fn score(&self, ctx: &C) -> f32 {
self.metrics.weighted_sum(&self.weights, ctx)
}
#[inline]
pub fn breakdown(&self, ctx: &C) -> alloc::vec::Vec<Breakdown32> {
self.metrics.collect_breakdown(&self.weights, ctx)
}
}
#[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;