light_curve_feature/
extractor.rs

1use crate::error::EvaluatorError;
2use crate::evaluator::*;
3use crate::feature::Feature;
4use crate::float_trait::Float;
5use crate::time_series::TimeSeries;
6
7use std::marker::PhantomData;
8
9macro_const! {
10    const DOC: &str = r#"
11Bulk feature extractor
12
13- Depends on: as reuired by feature evaluators
14- Minimum number of observations: as required by feature evaluators
15- Number of features: total for all feature evaluators
16"#;
17}
18
19#[doc = DOC!()]
20#[derive(Clone, Debug, Serialize, Deserialize)]
21#[serde(
22    into = "FeatureExtractorParameters<F>",
23    from = "FeatureExtractorParameters<F>",
24    bound = "T: Float, F: FeatureEvaluator<T>"
25)]
26pub struct FeatureExtractor<T, F> {
27    features: Vec<F>,
28    info: Box<EvaluatorInfo>,
29    phantom: PhantomData<T>,
30}
31
32impl<T, F> FeatureExtractor<T, F>
33where
34    T: Float,
35    F: FeatureEvaluator<T>,
36{
37    pub fn new(features: Vec<F>) -> Self {
38        let info = EvaluatorInfo {
39            size: features.iter().map(|x| x.size_hint()).sum(),
40            min_ts_length: features
41                .iter()
42                .map(|x| x.min_ts_length())
43                .max()
44                .unwrap_or(0),
45            t_required: features.iter().any(|x| x.is_t_required()),
46            m_required: features.iter().any(|x| x.is_m_required()),
47            w_required: features.iter().any(|x| x.is_w_required()),
48            sorting_required: features.iter().any(|x| x.is_sorting_required()),
49        }
50        .into();
51        Self {
52            info,
53            features,
54            phantom: PhantomData,
55        }
56    }
57
58    pub fn get_features(&self) -> &Vec<F> {
59        &self.features
60    }
61
62    pub fn into_vec(self) -> Vec<F> {
63        self.features
64    }
65
66    pub fn add_feature(&mut self, feature: F) {
67        self.features.push(feature);
68    }
69}
70
71impl<T> FeatureExtractor<T, Feature<T>>
72where
73    T: Float,
74{
75    /// Specialized version of [FeatureExtractor::new] for [Feature]
76    pub fn from_features(features: Vec<Feature<T>>) -> Self {
77        Self::new(features)
78    }
79}
80
81impl<T, F> FeatureExtractor<T, F> {
82    pub const fn doc() -> &'static str {
83        DOC
84    }
85}
86
87impl<T, F> EvaluatorInfoTrait for FeatureExtractor<T, F>
88where
89    T: Float,
90    F: FeatureEvaluator<T>,
91{
92    fn get_info(&self) -> &EvaluatorInfo {
93        &self.info
94    }
95}
96
97impl<T, F> FeatureNamesDescriptionsTrait for FeatureExtractor<T, F>
98where
99    T: Float,
100    F: FeatureEvaluator<T>,
101{
102    /// Get feature names
103    fn get_names(&self) -> Vec<&str> {
104        self.features.iter().flat_map(|x| x.get_names()).collect()
105    }
106
107    /// Get feature descriptions
108    fn get_descriptions(&self) -> Vec<&str> {
109        self.features
110            .iter()
111            .flat_map(|x| x.get_descriptions())
112            .collect()
113    }
114}
115
116impl<T, F> FeatureEvaluator<T> for FeatureExtractor<T, F>
117where
118    T: Float,
119    F: FeatureEvaluator<T>,
120{
121    fn eval(&self, ts: &mut TimeSeries<T>) -> Result<Vec<T>, EvaluatorError> {
122        let mut vec = Vec::with_capacity(self.size_hint());
123        for x in &self.features {
124            vec.extend(x.eval(ts)?);
125        }
126        Ok(vec)
127    }
128
129    fn eval_or_fill(&self, ts: &mut TimeSeries<T>, fill_value: T) -> Vec<T> {
130        self.features
131            .iter()
132            .flat_map(|x| x.eval_or_fill(ts, fill_value))
133            .collect()
134    }
135}
136
137#[cfg(test)]
138impl<T, F> Default for FeatureExtractor<T, F>
139where
140    T: Float,
141    F: FeatureEvaluator<T>,
142{
143    fn default() -> Self {
144        Self::new(vec![])
145    }
146}
147
148#[derive(Serialize, Deserialize, JsonSchema)]
149#[serde(rename = "FeatureExtractor")]
150struct FeatureExtractorParameters<F> {
151    features: Vec<F>,
152}
153
154impl<T, F> From<FeatureExtractor<T, F>> for FeatureExtractorParameters<F> {
155    fn from(f: FeatureExtractor<T, F>) -> Self {
156        Self {
157            features: f.features,
158        }
159    }
160}
161
162impl<T, F> From<FeatureExtractorParameters<F>> for FeatureExtractor<T, F>
163where
164    T: Float,
165    F: FeatureEvaluator<T>,
166{
167    fn from(p: FeatureExtractorParameters<F>) -> Self {
168        Self::new(p.features)
169    }
170}
171
172impl<T, F> JsonSchema for FeatureExtractor<T, F>
173where
174    F: JsonSchema,
175{
176    json_schema!(FeatureExtractorParameters<F>, true);
177}
178
179#[cfg(test)]
180mod tests {
181    use super::*;
182    use crate::Feature;
183    use crate::tests::*;
184
185    use serde_test::{Token, assert_ser_tokens};
186
187    serialization_name_test!(FeatureExtractor<f64, Feature<f64>>);
188
189    serde_json_test!(
190        feature_extractor_ser_json_de,
191        FeatureExtractor<f64, Feature<f64>>,
192        FeatureExtractor::new(vec![crate::Amplitude{}.into(), crate::BeyondNStd::new(2.0).into()]),
193    );
194
195    check_doc_static_method!(feature_extractor_doc_static_method, FeatureExtractor<f64, Feature<f64>>);
196
197    #[test]
198    fn serialization_empty() {
199        let fe: FeatureExtractor<f64, Feature<_>> = FeatureExtractor::new(vec![]);
200        assert_ser_tokens(
201            &fe,
202            &[
203                //
204                Token::Struct {
205                    len: 1,
206                    name: "FeatureExtractor",
207                },
208                //
209                Token::String("features"),
210                Token::Seq { len: Some(0) },
211                Token::SeqEnd,
212                //
213                Token::StructEnd,
214            ],
215        )
216    }
217}