use crate::math;
#[derive(Clone, Debug)]
pub(in crate::rcf) struct ScoreMode {
score_seen: fn(usize, usize) -> f64,
score_unseen: fn(usize, usize) -> f64,
damp: fn(usize, usize) -> f64,
normalizer: fn(f64, usize) -> f64,
}
fn score_seen(x: usize, y: usize) -> f64 {
1.0 / (x as f64 + math::log2(1.0 + y as f64))
}
fn score_unseen(x: usize, _y: usize) -> f64 {
1.0 / (x as f64 + 1.0)
}
fn normalizer(x: f64, y: usize) -> f64 {
x * math::log2(1.0 + y as f64)
}
fn damp(x: usize, y: usize) -> f64 {
if y == 0 {
return 1.0;
}
1.0 - (x as f64) / (2.0 * y as f64)
}
fn score_seen_displacement(_x: usize, y: usize) -> f64 {
1.0 / (1.0 + y as f64)
}
fn score_unseen_displacement(_x: usize, y: usize) -> f64 {
y as f64
}
fn displacement_normalizer(x: f64, y: usize) -> f64 {
x / (1.0 + y as f64)
}
fn identity(x: f64, _y: usize) -> f64 {
x
}
impl ScoreMode {
pub(in crate::rcf) fn standard() -> Self {
ScoreMode {
score_seen,
score_unseen,
damp,
normalizer,
}
}
pub(in crate::rcf) fn displacement() -> Self {
ScoreMode {
score_seen: score_seen_displacement,
score_unseen: score_unseen_displacement,
damp,
normalizer: displacement_normalizer,
}
}
#[allow(dead_code)]
pub(in crate::rcf) fn density() -> Self {
ScoreMode {
score_seen: score_unseen_displacement,
score_unseen: score_unseen_displacement,
damp,
normalizer: identity,
}
}
#[allow(dead_code)]
pub(in crate::rcf) fn custom(
score_seen: fn(usize, usize) -> f64,
score_unseen: fn(usize, usize) -> f64,
damp: fn(usize, usize) -> f64,
normalizer: fn(f64, usize) -> f64,
) -> Self {
ScoreMode {
score_seen,
score_unseen,
damp,
normalizer,
}
}
pub(in crate::rcf) fn score_seen(&self, depth: usize, mass: usize) -> f64 {
(self.score_seen)(depth, mass)
}
pub(in crate::rcf) fn score_unseen(&self, depth: usize, mass: usize) -> f64 {
(self.score_unseen)(depth, mass)
}
pub(in crate::rcf) fn damp(&self, mass: usize, tree_mass: usize) -> f64 {
(self.damp)(mass, tree_mass)
}
pub(in crate::rcf) fn normalize(&self, raw: f64, tree_mass: usize) -> f64 {
(self.normalizer)(raw, tree_mass)
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Attribution {
pub below: f64,
pub above: f64,
}
impl Attribution {
pub fn total(self) -> f64 {
self.below + self.above
}
pub fn scale(self, factor: f64) -> Self {
Attribution {
below: self.below * factor,
above: self.above * factor,
}
}
}
impl Default for Attribution {
fn default() -> Self {
Attribution {
below: 0.0,
above: 0.0,
}
}
}
impl core::ops::Add for Attribution {
type Output = Self;
fn add(self, rhs: Self) -> Self {
Attribution {
below: self.below + rhs.below,
above: self.above + rhs.above,
}
}
}
impl core::ops::AddAssign for Attribution {
fn add_assign(&mut self, rhs: Self) {
self.below += rhs.below;
self.above += rhs.above;
}
}
#[cfg(test)]
pub(in crate::rcf) fn attribution_total(attr: &[Attribution]) -> f64 {
attr.iter().map(|a| a.total()).sum()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn score_seen_decreases_with_depth_and_mass() {
let s00 = score_seen(0, 0);
let s01 = score_seen(0, 1);
let s10 = score_seen(1, 0);
let s11 = score_seen(1, 1);
assert!(s00 > s01 && s00 > s10 && s01 > s11 && s10 > s11);
}
#[test]
fn standard_score_unseen_decreases_with_depth() {
let s0 = score_unseen(0, 1);
let s1 = score_unseen(1, 1);
let s5 = score_unseen(5, 1);
assert!(s0 > s1 && s1 > s5);
}
#[test]
fn normalizer_scales_with_tree_mass() {
let s = normalizer(1.0, 256);
assert!(s > 1.0); }
#[cfg(feature = "std")]
mod proptest_tests {
use super::*;
use approx::abs_diff_eq;
use proptest::prelude::*;
proptest! {
#[test]
fn damp_in_unit_interval(
tree_mass in 1usize..=1000,
mass in 0usize..=1000,
) {
let capped = mass.min(tree_mass);
let d = damp(capped, tree_mass);
prop_assert!((0.0..=1.0).contains(&d), "damp({capped},{tree_mass})={d}");
}
#[test]
fn attribution_total_equals_sum(below in -1e6f64..1e6f64, above in -1e6f64..1e6f64) {
let a = Attribution { below, above };
prop_assert!(abs_diff_eq!(a.total(), below + above, epsilon = 1e-9));
}
#[test]
fn attribution_scale_distributes(
below in -1e3f64..1e3f64,
above in -1e3f64..1e3f64,
factor in -1e3f64..1e3f64,
) {
let a = Attribution { below, above };
let s = a.scale(factor);
prop_assert!(abs_diff_eq!(s.below, below * factor, epsilon = 1e-9));
prop_assert!(abs_diff_eq!(s.above, above * factor, epsilon = 1e-9));
}
#[test]
fn attribution_add_by_component(
b1 in -1e6f64..1e6f64,
a1 in -1e6f64..1e6f64,
b2 in -1e6f64..1e6f64,
a2 in -1e6f64..1e6f64,
) {
let x = Attribution { below: b1, above: a1 };
let y = Attribution { below: b2, above: a2 };
let sum = x + y;
prop_assert!(abs_diff_eq!(sum.below, b1 + b2, epsilon = 1e-9));
prop_assert!(abs_diff_eq!(sum.above, a1 + a2, epsilon = 1e-9));
}
}
}
}