1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
/// Generate a frequency grid for a given time array `t`, and optional minimum and maximum frequencies.
///
/// # Arguments
///
/// * `t` - Time array (slice of f64)
/// * `fmin` - Optional minimum frequency (f64)
/// * `fmax` - Optional maximum frequency (f64)
/// * `oversample` - Oversampling factor (usize), typically between 1 and 10
///
/// # Returns
///
/// * `Vec<f64>` - A vector of frequencies for the periodogram.
///
///
/// # Examples
///
/// ```
/// use rand::Rng;
/// use flare::period::freq_grid;
///
/// // randomize a list of 1000 data points over 6 years
/// let n_points = 1000;
/// let mut rng = rand::rng();
/// let mut t: Vec<f64> = Vec::with_capacity(n_points);
/// for _ in 0..n_points {
/// // generate a random time between 0 and 6*365 days
/// let random_time = rng.random_range(0.0..(6.0 * 365.0));
/// t.push(random_time);
/// }
/// // Sort the time array to ensure it's in ascending order
/// t.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
///
/// // Now generate the frequency grid, from 10 days to 5 minutes
/// let f_min = 1.0 / (10.0 * 24.0 * 60.0);
/// let f_max = 1.0 / (5.0 / 60.0);
/// let fgrid = freq_grid(&t, Some(f_min), Some(f_max), 3); // Generate frequency grid
/// assert!(!fgrid.is_empty(), "Frequency grid should not be empty");
/// assert!(fgrid.len() > 70000, "Frequency grid should have at least 7000 values for the given parameters");
/// ```
/// Calculate the FPW statistic (https://arxiv.org/abs/2502.00243) for a given time series data.
///
/// # Arguments
///
/// * `t` - Time array (slice of f64)
/// * `y` - Measurement array (slice of f64)
/// * `dy` - Measurement errors (slice of f64), must be the same length as `y`
/// * `freqs` - Frequency grid (slice of f64), typically generated by `freq_grid`
/// * `n_bins` - Number of bins to use for the FPW statistic (must be <= 20)
///
/// # Returns
///
/// * `Vec<f64>` - A vector of FPW statistics for each frequency in the provided frequency grid.
///
/// # Examples
///
/// ```
/// use rand::Rng;
/// use flare::period::{fpw, freq_grid};
///
/// // Simulate some time series data
/// let n_points = 1000;
/// let period = 0.25; // in days
/// let mut rng = rand::rng();
/// let mut t: Vec<f64> = Vec::with_capacity(n_points);
/// for _ in 0..n_points {
/// // generate a random time between 0 and 6*365 days
/// let random_time = rng.random_range(0.0..(6.0 * 365.0));
/// t.push(random_time);
/// }
/// // Sort the time array to ensure it's in ascending order
/// t.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
///
/// // Now generate the sinusoidal light curve with some noise
/// let y: Vec<f64> = t
/// .iter()
/// .map(|&time| {
/// // Simulate a sinusoidal light curve with some noise
/// let true_value = (2.0 * std::f64::consts::PI * time / period).sin();
/// let noise = rng.random_range(-0.1..0.1); // Add some noise
/// true_value + noise
/// })
/// .collect();
///
/// // Define frequency grid, with a min freq of 10 days
/// let f_min = 1.0 / (10.0 * 24.0 * 60.0);
/// let f_max = 1.0 / (5.0 / 60.0);
/// let freqs = freq_grid(&t, Some(f_min), Some(f_max), 3); // Generate frequency grid
///
/// // the FPW algorithm requires a number of bins, typically between 5 and 20
/// let n_bins = 10; // choose a number of bins between 5 and 20
///
/// // Calculate FPW statistic for the given time series data
/// let fpw_stats = fpw(&t, &y, &vec![0.1; n_points], &freqs, n_bins);
///
/// // Get the index of the best frequency and its corresponding statistic
/// let (best_freq, best_stat) = {
/// let max_index = fpw_stats
/// .iter()
/// .enumerate()
/// .filter(|&(_, &x)| !x.is_nan())
/// .max_by(|a, b| a.1.partial_cmp(b.1).unwrap())
/// .map(|(i, _)| i)
/// .unwrap_or(0);
///
/// (freqs[max_index], fpw_stats[max_index])
/// };
///
/// let period = 1.0 / best_freq * 24.0; // convert from frequency in days to a period in hours
///
/// println!("Best period: {:.2} hours, statistic: {:.2}", period, best_stat);
///
/// assert!((period - 6.0).abs() < 1e-4, "The best period should be close to the true period of 6 hours");
/// ```
/// Get the best frequency and its corresponding statistic from the FPW results.
///
/// # Arguments
///
/// * `freqs` - Frequency grid (slice of f64)
/// * `result` - FPW statistic results (slice of f64)
///
/// # Returns
///
/// * `(f64, f64)` - A tuple containing the best frequency and its corresponding statistic.
///
/// # Examples
///
/// ```
/// use flare::period::get_best_freq;
/// let freqs = vec![0.1, 0.2, 0.3, 0.4, 0.5]; // Example frequencies
/// let result = vec![0.5, 1.2, 0.8, 2.5, 1.0]; // Example FPW statistics
/// let (best_freq, best_stat) = get_best_freq(&freqs, &result);
/// assert_eq!(best_freq, 0.4);
/// assert_eq!(best_stat, 2.5);
/// ```
/// Get the best frequencies and their statistics, returning the top `n` frequencies based on the FPW statistic.
///
/// # Arguments
///
/// * `freqs` - Frequency grid (slice of f64)
/// * `result` - FPW statistic results (slice of f64)
/// * `n` - Number of top frequencies to return
///
/// # Returns
///
/// * `Vec<(f64, f64)>` - A vector of tuples containing the top `n` frequencies and their corresponding statistics.
///
/// # Examples
///
/// ```
/// use flare::period::get_best_freqs;
/// let freqs = vec![0.1, 0.2, 0.3, 0.4, 0.5]; // Example frequencies
/// let result = vec![0.5, 1.2, 0.8, 2.5, 1.0]; // Example FPW statistics
/// let best_freqs = get_best_freqs(&freqs, &result, 3);
/// assert_eq!(best_freqs.len(), 3);
/// assert_eq!(best_freqs[0], (0.4, 2.5));
/// assert_eq!(best_freqs[1], (0.2, 1.2));
/// assert_eq!(best_freqs[2], (0.5, 1.0));