#[macro_export]
macro_rules! finite_metric {
(
$(#[$attr:meta])*
$vis:vis
metric => $name:ident,
float => $T:ty,
subject => $I:ty,
dimensions => $($variant:ident($ty:ty)),+ $(,)?
) => {
$(#[$attr])*
#[allow(clippy::pub_enum_variant_fields)]
$vis enum $name {
$($variant($ty),)+
}
impl $crate::Scorable<$T, $I> for $name {
#[inline]
fn eval(&self, input: &$I) -> $crate::Witnessed<$T, $crate::Value01> {
match self {
$(Self::$variant(m) => m.eval(input)),+
}
}
#[inline]
fn name(&self) -> &str {
match self {
$(Self::$variant(m) => m.name()),+
}
}
}
};
(
$(#[$attr:meta])*
$vis:vis
metric => $name:ident<$T:ident, $I:ident>,
dimensions => $($variant:ident($ty:ty)),+ $(,)?
) => {
$(#[$attr])*
#[allow(clippy::pub_enum_variant_fields)]
$vis enum $name<$T: $crate::Float, $I> {
$($variant($ty)),+
}
impl<$T: $crate::Float, $I> $crate::Scorable<$T, $I> for $name<$T, $I> {
#[inline]
fn eval(&self, input: &$I) -> $crate::Witnessed<$T, $crate::Value01> {
match self {
$(Self::$variant(m) => m.eval(input)),+
}
}
#[inline]
fn name(&self) -> &str {
match self {
$(Self::$variant(m) => m.name()),+
}
}
}
};
}
use crate::breakdown::Breakdown;
use crate::dynamic::Scorable;
use crate::float::Float;
use crate::value::{GtZero, NormalizedContainer, NormalizedWeight, Value01};
use core::marker::PhantomData;
use witnessed::{WitnessExt, Witnessed};
pub struct FiniteMember<T: Float, E> {
pub weight: Witnessed<T, NormalizedWeight>,
pub metric: E,
}
impl<T: Float, E> FiniteMember<T, E> {
#[inline]
pub fn contribute(&self, value: Witnessed<T, Value01>) -> T {
value.into_inner() * self.weight.into_inner()
}
#[inline]
pub fn metric(&self) -> &E {
&self.metric
}
}
pub struct FiniteScoreSet<T: Float, I, E> {
members: Vec<FiniteMember<T, E>>,
_phantom: PhantomData<I>,
}
impl<T: Float, I, E: Scorable<T, I>> FiniteScoreSet<T, I, E> {
#[doc(hidden)]
pub fn normalize(entries: Vec<(T, E)>) -> Result<Self, &'static str> {
if entries.is_empty() {
return Err("FiniteScoreSet: 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<FiniteMember<T, E>> = entries
.into_iter()
.zip(normalized.drain(..))
.map(|((_, metric), nw)| {
let weight = nw
.witness()
.by(|v| NormalizedWeight::from_normalized_container(*v, &container))?;
Ok(FiniteMember { weight, metric })
})
.collect::<Result<Vec<_>, &'static str>>()?;
Ok(FiniteScoreSet {
members,
_phantom: PhantomData,
})
}
#[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) -> FiniteScoreStage<'_, T, I, E> {
FiniteScoreStage {
members: &self.members,
_phantom: PhantomData,
}
}
#[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 = &FiniteMember<T, E>> {
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() -> FiniteScoreSetBuilder<T, I, E> {
FiniteScoreSetBuilder {
entries: Vec::new(),
_phantom: PhantomData,
}
}
}
pub struct FiniteScoreStage<'a, T: Float, I, E> {
members: &'a [FiniteMember<T, E>],
_phantom: PhantomData<I>,
}
impl<'a, T: Float, I, E: Scorable<T, I>> FiniteScoreStage<'a, T, I, E> {
#[inline]
pub fn by<F, R>(self, f: F) -> R
where
F: FnOnce(&[FiniteMember<T, E>]) -> R,
{
f(self.members)
}
}
pub struct FiniteScoreSetBuilder<T: Float, I, E> {
entries: Vec<(T, E)>,
_phantom: PhantomData<I>,
}
impl<T: Float, I, E: Scorable<T, I>> FiniteScoreSetBuilder<T, I, E> {
#[inline]
pub fn push(mut self, weight: T, variant: E) -> Result<Self, &'static str> {
GtZero::witness(weight)?;
self.entries.push((weight, variant));
Ok(self)
}
#[inline]
pub fn build(self) -> Result<FiniteScoreSet<T, I, E>, &'static str> {
FiniteScoreSet::normalize(self.entries)
}
}
#[cfg(test)]
mod tests_for_finite;