use crate::float::Float;
use crate::value::Value01;
use witnessed::Witnessed;
pub trait Scorable<T: Float, I> {
fn eval(&self, input: &I) -> Witnessed<T, Value01>;
fn name(&self) -> &str;
}
impl<T, I, Raw, M, F> Scorable<T, I> for crate::Metric<T, I, Raw, M, F>
where
T: Float,
M: Fn(&I) -> Raw,
F: Fn(&Raw, &I) -> Witnessed<T, Value01>,
{
#[inline]
fn eval(&self, input: &I) -> Witnessed<T, Value01> {
crate::Metric::eval(self, input)
}
#[inline]
fn name(&self) -> &str {
crate::Metric::name(self)
}
}
impl<T: Float, I> Scorable<T, I> for Box<dyn Scorable<T, I>> {
#[inline]
fn eval(&self, input: &I) -> Witnessed<T, Value01> {
(**self).eval(input)
}
#[inline]
fn name(&self) -> &str {
(**self).name()
}
}
use crate::breakdown::Breakdown;
use crate::value::{GtZero, NormalizedContainer, NormalizedWeight};
use witnessed::WitnessExt;
pub struct DynamicMember<T: Float, I> {
pub weight: Witnessed<T, NormalizedWeight>,
pub metric: Box<dyn Scorable<T, I>>,
}
impl<T: Float, I> DynamicMember<T, I> {
#[inline]
pub fn contribute(&self, value: Witnessed<T, Value01>) -> T {
value.into_inner() * self.weight.into_inner()
}
#[inline]
pub fn metric(&self) -> &dyn Scorable<T, I> {
&*self.metric
}
}
pub struct DynamicScoreSet<T: Float, I> {
members: Vec<DynamicMember<T, I>>,
}
impl<T: Float, I> DynamicScoreSet<T, I> {
#[doc(hidden)]
pub fn normalize(entries: Vec<(T, Box<dyn Scorable<T, I>>)>) -> Result<Self, &'static str> {
if entries.is_empty() {
return Err("DynamicScoreSet: must have at least one member");
}
for (w, _) in &entries {
GtZero::witness(*w)?;
}
let sum: T = entries.iter().fold(T::zero(), |acc, (w, _)| acc + *w);
let mut normalized: Vec<T> = entries.iter().map(|(w, _)| *w / sum).collect();
let mut sorted = normalized.clone();
sorted.sort_by(|a, b| a.partial_cmp(b).unwrap_or(core::cmp::Ordering::Equal));
let container = NormalizedContainer::witness(sorted)?;
let members: Vec<DynamicMember<T, I>> = entries
.into_iter()
.zip(normalized.drain(..))
.map(|((_, metric), nw)| {
let weight = nw
.witness()
.by(|v| NormalizedWeight::from_normalized_container(*v, &container))?;
Ok(DynamicMember { weight, metric })
})
.collect::<Result<Vec<_>, &'static str>>()?;
Ok(DynamicScoreSet { members })
}
#[inline]
pub fn sum(&self, input: &I) -> T {
self.members
.iter()
.fold(T::zero(), |acc, m| acc + m.contribute(m.metric.eval(input)))
}
#[inline]
pub fn score(&self) -> DynamicScoreStage<'_, T, I> {
DynamicScoreStage {
members: &self.members,
}
}
#[inline]
pub fn len(&self) -> usize {
self.members.len()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.members.is_empty()
}
#[inline]
pub fn iter(&self) -> impl Iterator<Item = &DynamicMember<T, I>> {
self.members.iter()
}
#[inline]
pub fn breakdown(&self, input: &I) -> Vec<Breakdown<'_, T>> {
self.members
.iter()
.map(|m| {
let score_witness = m.metric.eval(input);
let score_val: T = *score_witness;
Breakdown {
name: m.metric.name(),
score: score_val,
weight: m.weight.into_inner(),
contribution: m.contribute(score_witness),
}
})
.collect()
}
#[inline]
pub fn builder() -> DynamicScoreSetBuilder<T, I> {
DynamicScoreSetBuilder {
entries: Vec::new(),
}
}
}
pub struct DynamicScoreStage<'a, T: Float, I> {
members: &'a [DynamicMember<T, I>],
}
impl<'a, T: Float, I> DynamicScoreStage<'a, T, I> {
#[inline]
pub fn by<F, R>(self, f: F) -> R
where
F: FnOnce(&[DynamicMember<T, I>]) -> R,
{
f(self.members)
}
}
pub struct DynamicScoreSetBuilder<T: Float, I> {
entries: Vec<(T, Box<dyn Scorable<T, I>>)>,
}
impl<T: Float, I> DynamicScoreSetBuilder<T, I> {
#[inline]
pub fn push(
mut self,
weight: T,
metric: Box<dyn Scorable<T, I>>,
) -> Result<Self, &'static str> {
GtZero::witness(weight)?;
self.entries.push((weight, metric));
Ok(self)
}
#[inline]
pub fn build(self) -> Result<DynamicScoreSet<T, I>, &'static str> {
DynamicScoreSet::normalize(self.entries)
}
}
#[cfg(test)]
mod tests_for_dynamic;