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