1use std::collections::HashMap;
2use std::marker::PhantomData;
3use std::{hash::Hash, ops::RangeInclusive};
4
5use fixed_map::key::Key as FixedKey;
6use slotmap::{new_key_type, SlotMap};
7
8use crate::linspace::Linspace;
9use crate::math::interp;
10use crate::terms::Terms;
11
12new_key_type! {
13    pub struct VariableKey;
15}
16
17pub struct Variable<I>(pub(crate) VariableKey, PhantomData<I>);
18
19impl<I> Clone for Variable<I> {
20    fn clone(&self) -> Self {
21        Variable(self.0, PhantomData)
22    }
23}
24
25impl<I> Copy for Variable<I> {}
26
27#[derive(Default)]
28pub struct Variables<T>(pub(crate) SlotMap<VariableKey, VariableContraints<T>>);
29
30impl<T: Eq + Hash> Variables<T> {
31    pub fn new() -> Self {
32        Self(SlotMap::with_key())
33    }
34
35    pub fn add<I: Into<T> + FixedKey + 'static>(
36        &mut self,
37        universe_range: RangeInclusive<f64>,
38        terms: Terms<I>,
39    ) -> Variable<I> {
40        let start_term_coords = terms.0.iter().map(|(k, v)| (k.into(), *v));
41        let key = self.0.insert(VariableContraints::new(
42            universe_range,
43            start_term_coords,
44            terms.0.len(),
45        ));
46
47        Variable(key, PhantomData)
48    }
49}
50
51pub(crate) struct VariableContraints<T> {
52    pub(crate) universe: Vec<f64>,
53    pub(crate) min_u: f64,
54    pub(crate) max_u: f64,
55    pub(crate) terms: HashMap<T, Vec<f64>>,
56}
57
58impl<T: Eq + Hash> VariableContraints<T> {
59    fn new<'t>(
60        universe_range: RangeInclusive<f64>,
61        start_term_coords: impl IntoIterator<Item = (T, &'t [(f64, f64)])>,
62        n_terms: usize,
63    ) -> Self {
64        let step = 0.1;
66        let min_u = *universe_range.start();
67        let max_u = *universe_range.end();
68        let num = ((max_u - min_u) / step).floor() as usize + 1;
71        let universe = Linspace::new(min_u, max_u, num).collect();
72        let mut this = Self {
73            universe,
74            min_u,
75            max_u,
76            terms: HashMap::with_capacity(n_terms),
77        };
78
79        if false {
81            unimplemented!();
82        } else {
84            for (term, membership) in start_term_coords {
85                let xp = membership.iter().map(|(xp, _)| *xp);
86                this.add_points_to_universe(xp);
87                this.terms
88                    .insert(term, interp(this.universe.iter().copied(), membership.iter().copied()));
89            }
90        }
91
92        this
93    }
94
95    pub(crate) fn add_points_to_universe(&mut self, points: impl IntoIterator<Item = f64>) {
97        let iter = points.into_iter().map(|p| p.clamp(self.min_u, self.max_u));
99        let mut universe: Vec<_> = self.universe.iter().copied().chain(iter).collect();
100
101        universe.sort_unstable_by(|a, b| a.partial_cmp(b).expect("not to find unsortable floats"));
102        universe.dedup();
103
104        for term_values in self.terms.values_mut() {
106            let new_values = interp(
107                universe.iter().copied(),
108                self.universe.iter().copied().zip(term_values.iter().copied()),
109            );
110
111            *term_values = new_values;
112        }
113
114        self.universe = universe;
116    }
117
118    #[allow(clippy::let_and_return)]
119    pub(crate) fn get_modified_membership(&self, term: &T, _modifiers: &[()]) -> &[f64] {
120        let membership = &self.terms[term];
121
122        membership
125    }
126}