light_curve_feature/
extractor.rs1use 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 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 fn get_names(&self) -> Vec<&str> {
104 self.features.iter().flat_map(|x| x.get_names()).collect()
105 }
106
107 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 Token::Struct {
205 len: 1,
206 name: "FeatureExtractor",
207 },
208 Token::String("features"),
210 Token::Seq { len: Some(0) },
211 Token::SeqEnd,
212 Token::StructEnd,
214 ],
215 )
216 }
217}