Skip to content

Commit dcea60d

Browse files
committed
Component norm for MC periodogram
1 parent 4f8d081 commit dcea60d

File tree

4 files changed

+106
-43
lines changed

4 files changed

+106
-43
lines changed

src/features/_periodogram_peaks.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,13 +50,13 @@ impl PeriodogramPeaks {
5050
.flat_map(|i| {
5151
vec![
5252
format!(
53-
"period of the {}{} highest peak of periodogram",
53+
"period of the {}{} highest peak",
5454
i + 1,
5555
number_ending(i + 1),
5656
),
5757
format!(
5858
"Spectral density to spectral density standard deviation ratio of \
59-
the {}{} highest peak of periodogram",
59+
the {}{} highest peak",
6060
i + 1,
6161
number_ending(i + 1)
6262
),

src/features/periodogram.rs

Lines changed: 30 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,10 @@ where
4545
max_freq_factor: f32,
4646
nyquist: NyquistFreq,
4747
pub(crate) feature_extractor: FeatureExtractor<T, F>,
48+
// In can be re-defined in MultiColorPeriodogram
49+
pub(crate) name_prefix: String,
50+
// In can be re-defined in MultiColorPeriodogram
51+
pub(crate) description_suffix: String,
4852
periodogram_algorithm: PeriodogramPower<T>,
4953
properties: Box<EvaluatorProperties>,
5054
}
@@ -100,13 +104,13 @@ where
100104
feature
101105
.get_names()
102106
.iter()
103-
.map(|name| "periodogram_".to_owned() + name),
107+
.map(|name| format!("{}_{}", self.name_prefix, name)),
104108
);
105109
self.properties.descriptions.extend(
106110
feature
107111
.get_descriptions()
108112
.into_iter()
109-
.map(|desc| format!("{} of periodogram", desc)),
113+
.map(|desc| format!("{} {}", desc, self.description_suffix)),
110114
);
111115
self.feature_extractor.add_feature(feature);
112116
self
@@ -149,41 +153,44 @@ where
149153
{
150154
/// New [Periodogram] that finds given number of peaks
151155
pub fn new(peaks: usize) -> Self {
152-
let peaks = PeriodogramPeaks::new(peaks);
153-
let peak_names = peaks
154-
.get_names()
155-
.into_iter()
156-
.map(ToOwned::to_owned)
157-
.collect();
158-
let peak_descriptions = peaks
159-
.get_descriptions()
160-
.into_iter()
161-
.map(ToOwned::to_owned)
162-
.collect();
163-
let peaks_size_hint = peaks.size_hint();
164-
let peaks_min_ts_length = peaks.min_ts_length();
156+
Self::with_name_description(
157+
peaks,
158+
"periodogram",
159+
"of periodogram (interpreting frequency as time, power as magnitude)",
160+
)
161+
}
162+
163+
pub(crate) fn with_name_description(
164+
peaks: usize,
165+
name_prefix: impl ToString,
166+
description_suffix: impl ToString,
167+
) -> Self {
165168
let info = EvaluatorInfo {
166-
size: peaks_size_hint,
167-
min_ts_length: usize::max(peaks_min_ts_length, 2),
169+
size: 0,
170+
min_ts_length: 2,
168171
t_required: true,
169172
m_required: true,
170173
w_required: false,
171174
sorting_required: true,
172175
variability_required: false,
173176
};
174-
Self {
177+
let mut slf = Self {
175178
properties: EvaluatorProperties {
176179
info,
177-
names: peak_names,
178-
descriptions: peak_descriptions,
180+
names: vec![],
181+
descriptions: vec![],
179182
}
180183
.into(),
181184
resolution: Self::default_resolution(),
185+
name_prefix: name_prefix.to_string(),
186+
description_suffix: description_suffix.to_string(),
182187
max_freq_factor: Self::default_max_freq_factor(),
183188
nyquist: AverageNyquistFreq.into(),
184-
feature_extractor: FeatureExtractor::new(vec![peaks.into()]),
189+
feature_extractor: FeatureExtractor::new(vec![]),
185190
periodogram_algorithm: PeriodogramPowerFft::new().into(),
186-
}
191+
};
192+
slf.add_feature(PeriodogramPeaks::new(peaks).into());
193+
slf
187194
}
188195
}
189196

@@ -286,7 +293,7 @@ where
286293
nyquist,
287294
feature_extractor,
288295
periodogram_algorithm,
289-
properties: _,
296+
..
290297
} = f;
291298

292299
let mut features = feature_extractor.into_vec();

src/multicolor/features/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,4 @@ mod color_of_minimum;
88
pub use color_of_minimum::ColorOfMinimum;
99

1010
mod multi_color_periodogram;
11-
pub use multi_color_periodogram::MultiColorPeriodogram;
11+
pub use multi_color_periodogram::{MultiColorPeriodogram, MultiColorPeriodogramNormalisation};

src/multicolor/features/multi_color_periodogram.rs

Lines changed: 73 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,20 @@ use crate::periodogram::{self, NyquistFreq, PeriodogramPower};
1313
use ndarray::Array1;
1414
use std::fmt::Debug;
1515

16+
/// Normalisation of periodogram across passbands
17+
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
18+
pub enum MultiColorPeriodogramNormalisation {
19+
/// Weight individual periodograms by the number of observations in each passband.
20+
/// Useful if no weight is given to observations
21+
Count,
22+
/// Weight individual periodograms by $\chi^2 = \sum \left(\frac{m_i - \bar{m}}{\delta_i}\right)^2$
23+
///
24+
/// Be aware that if no weight are given to observations
25+
/// (i.e. via [TimeSeries::new_without_weight]) unity weights are assumed and this is NOT
26+
/// equivalent to [::Count], but weighting by magnitude variance.
27+
Chi2,
28+
}
29+
1630
/// Multi-passband periodogram
1731
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
1832
#[serde(
@@ -25,14 +39,26 @@ where
2539
{
2640
// We use it to not reimplement some internals
2741
monochrome: Periodogram<T, F>,
28-
properties: Box<EvaluatorProperties>,
42+
normalization: MultiColorPeriodogramNormalisation,
2943
}
3044

3145
impl<T, F> MultiColorPeriodogram<T, F>
3246
where
3347
T: Float,
34-
F: FeatureEvaluator<T>,
48+
F: FeatureEvaluator<T> + From<PeriodogramPeaks>,
3549
{
50+
pub fn new(peaks: usize, normalization: MultiColorPeriodogramNormalisation) -> Self {
51+
let monochrome = Periodogram::with_name_description(
52+
peaks,
53+
"multicolor_periodogram",
54+
"of multi-color periodogram (interpreting frequency as time, power as magnitude)",
55+
);
56+
Self {
57+
monochrome,
58+
normalization,
59+
}
60+
}
61+
3662
#[inline]
3763
pub fn default_peaks() -> usize {
3864
PeriodogramPeaks::default_peaks()
@@ -103,19 +129,53 @@ where
103129
'a: 'mcts,
104130
P: PassbandTrait,
105131
{
106-
let unnormed_power = mcts
107-
.mapping_mut()
132+
let ts_weights = {
133+
let mut a: Array1<_> = match self.normalization {
134+
MultiColorPeriodogramNormalisation::Count => {
135+
mcts.mapping_mut().values().map(|ts| ts.lenf()).collect()
136+
}
137+
MultiColorPeriodogramNormalisation::Chi2 => mcts
138+
.mapping_mut()
139+
.values_mut()
140+
.map(|ts| ts.get_m_chi2())
141+
.collect(),
142+
};
143+
let norm = a.sum();
144+
if norm.is_zero() {
145+
match self.normalization {
146+
MultiColorPeriodogramNormalisation::Count => {
147+
return Err(MultiColorEvaluatorError::all_time_series_short(
148+
mcts.mapping_mut(),
149+
self.min_ts_length(),
150+
));
151+
}
152+
MultiColorPeriodogramNormalisation::Chi2 => {
153+
return Err(MultiColorEvaluatorError::AllTimeSeriesAreFlat);
154+
}
155+
}
156+
}
157+
a /= norm;
158+
a
159+
};
160+
mcts.mapping_mut()
108161
.values_mut()
109-
.filter(|ts| self.monochrome.check_ts_length(ts).is_ok())
110-
.map(|ts| p.power(ts) * ts.lenf())
111-
.reduce(|acc, x| acc + x)
162+
.zip(ts_weights.iter())
163+
.filter(|(ts, _ts_weight)| self.monochrome.check_ts_length(ts).is_ok())
164+
.map(|(ts, &ts_weight)| {
165+
let mut power = p.power(ts);
166+
power *= ts_weight;
167+
power
168+
})
169+
.reduce(|mut acc, power| {
170+
acc += &power;
171+
acc
172+
})
112173
.ok_or_else(|| {
113174
MultiColorEvaluatorError::all_time_series_short(
114175
mcts.mapping_mut(),
115-
self.monochrome.min_ts_length(),
176+
self.min_ts_length(),
116177
)
117-
})?;
118-
Ok(unnormed_power / mcts.total_lenf())
178+
})
119179
}
120180

121181
pub fn power<'slf, 'a, 'mcts, P>(
@@ -174,7 +234,7 @@ where
174234
<F as TryInto<PeriodogramPeaks>>::Error: Debug,
175235
{
176236
fn get_info(&self) -> &EvaluatorInfo {
177-
&self.properties.info
237+
self.monochrome.get_info()
178238
}
179239
}
180240

@@ -185,15 +245,11 @@ where
185245
<F as TryInto<PeriodogramPeaks>>::Error: Debug,
186246
{
187247
fn get_names(&self) -> Vec<&str> {
188-
self.properties.names.iter().map(String::as_str).collect()
248+
self.monochrome.get_names()
189249
}
190250

191251
fn get_descriptions(&self) -> Vec<&str> {
192-
self.properties
193-
.descriptions
194-
.iter()
195-
.map(String::as_str)
196-
.collect()
252+
self.monochrome.get_descriptions()
197253
}
198254
}
199255

0 commit comments

Comments
 (0)