use fieldx::fxstruct;
use crate::test::simulation::scriptwriter::math::bisect;
#[fxstruct(sync, new(off), default(off), builder(post_build), get(copy))]
pub struct CustomerModel {
initial_customers: f64,
market_capacity: f64,
inflection_point: f64,
#[fieldx(lock, set)]
growth_rate: f64,
#[fieldx(default(0.0001))]
tolerance: f64,
#[fieldx(private, set, builder(off))]
v: f64,
#[fieldx(private, set, builder(off))]
q: f64,
}
impl CustomerModel {
pub fn new(initial_customers: f64) -> Self {
Self::builder().initial_customers(initial_customers).build().unwrap()
}
fn post_build(mut self) -> Self {
self.set_v(self.calc_v());
self.set_q(self.calc_q());
self
}
fn calc_v(&self) -> f64 {
let v0 = 0.0;
let v1 = 10.0;
let expected = self.inflection_point / self.market_capacity;
#[inline(always)]
fn coeff(v: f64) -> f64 {
(v / (1.0 + v)).powf(1.0 / v)
}
bisect(v0, v1, expected, self.tolerance, coeff).expect("failed to bisect Richards model asymmetry parameter v")
}
fn calc_q(&self) -> f64 {
(self.market_capacity / self.initial_customers).powf(self.v()) - 1.0
}
pub fn adjust_growth_rate(&self, expected_customers: f64, day: i32) {
let gr0 = 0.0;
let gr1 = 1.0;
self.set_growth_rate(gr1);
self.set_growth_rate(
bisect(gr0, gr1, expected_customers, self.tolerance, |gr| {
self.set_growth_rate(gr);
self.expected_customers(day)
})
.unwrap_or_else(|err| {
panic!("failed to bisect growth rate for expected customers {expected_customers} on day {day}: {err}")
}),
);
}
pub fn expected_customers(&self, t: i32) -> f64 {
let t = t as f64;
self.market_capacity / (1.0 + self.q() * (-self.growth_rate() * t).exp()).powf(1.0 / self.v)
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_v_param() {
let richards = CustomerModel::builder()
.initial_customers(1.)
.market_capacity(1_000_000.)
.inflection_point(200_000.)
.tolerance(0.0001)
.growth_rate(0.05)
.build()
.unwrap();
let v = richards.v();
assert_eq!((v * 10000.0).round(), 6058.);
richards.adjust_growth_rate(500_000., 365);
assert_eq!((richards.growth_rate() * 10000.0).round(), 0247.);
}
}