altoslib: Fix gyro headings in CSV files
[fw/altos] / altoslib / AltosTimeSeries.java
1 /*
2  * Copyright © 2017 Keith Packard <keithp@keithp.com>
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation, either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * General Public License for more details.
13  */
14
15 package org.altusmetrum.altoslib_13;
16
17 import java.util.*;
18
19 public class AltosTimeSeries implements Iterable<AltosTimeValue>, Comparable<AltosTimeSeries> {
20         public String                   label;
21         public AltosUnits               units;
22         ArrayList<AltosTimeValue>       values;
23         boolean                         data_changed;
24
25         public int compareTo(AltosTimeSeries other) {
26                 return label.compareTo(other.label);
27         }
28
29         public void add(AltosTimeValue tv) {
30                 data_changed = true;
31                 values.add(tv);
32         }
33
34         public void erase_values() {
35                 data_changed = true;
36                 this.values = new ArrayList<AltosTimeValue>();
37         }
38
39         public void clear_changed() {
40                 data_changed = false;
41         }
42
43 //      public boolean changed() {
44 //              return data_changed;
45 //      }
46
47         public void add(double time, double value) {
48                 add(new AltosTimeValue(time, value));
49         }
50
51         public AltosTimeValue get(int i) {
52                 return values.get(i);
53         }
54
55         private double lerp(AltosTimeValue v0, AltosTimeValue v1, double t) {
56                 /* degenerate case */
57                 if (v0.time == v1.time)
58                         return (v0.value + v1.value) / 2;
59
60                 return (v0.value * (v1.time - t) + v1.value * (t - v0.time)) / (v1.time - v0.time);
61         }
62
63         private int after_index(double time) {
64                 int     lo = 0;
65                 int     hi = values.size() - 1;
66
67                 while (lo <= hi) {
68                         int mid = (lo + hi) / 2;
69
70                         if (values.get(mid).time < time)
71                                 lo = mid + 1;
72                         else
73                                 hi = mid - 1;
74                 }
75                 return lo;
76         }
77
78         /* Compute a value for an arbitrary time */
79         public double value(double time) {
80                 int after = after_index(time);
81                 double ret;
82
83                 if (after == 0)
84                         ret = values.get(0).value;
85                 else if (after == values.size())
86                         ret = values.get(after - 1).value;
87                 else {
88                         AltosTimeValue b = values.get(after-1);
89                         AltosTimeValue a = values.get(after);
90                         ret = lerp(b, a, time);
91                 }
92                 return ret;
93         }
94
95         /* Find the value just before an arbitrary time */
96         public double value_before(double time) {
97                 int after = after_index(time);
98
99                 if (after == 0)
100                         return values.get(0).value;
101                 return values.get(after-1).value;
102         }
103
104         /* Find the value just after an arbitrary time */
105         public double value_after(double time) {
106                 int after = after_index(time);
107
108                 if (after == values.size())
109                         return values.get(after-1).value;
110                 return values.get(after).value;
111         }
112
113         public double time_of(double value) {
114                 double  last = AltosLib.MISSING;
115                 for (AltosTimeValue v : values) {
116                         if (v.value >= value)
117                                 return v.time;
118                         last = v.time;
119                 }
120                 return last;
121         }
122
123         public int size() {
124                 return values.size();
125         }
126
127         public Iterator<AltosTimeValue> iterator() {
128                 return values.iterator();
129         }
130
131         public AltosTimeValue max() {
132                 AltosTimeValue max = null;
133                 for (AltosTimeValue tv : values)
134                         if (max == null || tv.value > max.value)
135                                 max = tv;
136                 return max;
137         }
138
139         public AltosTimeValue max(double start_time, double end_time) {
140                 AltosTimeValue max = null;
141                 for (AltosTimeValue tv : values) {
142                         if (start_time <= tv.time && tv.time <= end_time)
143                                 if (max == null || tv.value > max.value)
144                                         max = tv;
145                 }
146                 return max;
147         }
148
149         public AltosTimeValue min() {
150                 AltosTimeValue min = null;
151                 for (AltosTimeValue tv : values) {
152                         if (min == null || tv.value < min.value)
153                                 min = tv;
154                 }
155                 return min;
156         }
157
158         public AltosTimeValue min(double start_time, double end_time) {
159                 AltosTimeValue min = null;
160                 for (AltosTimeValue tv : values) {
161                         if (start_time <= tv.time && tv.time <= end_time)
162                                 if (min == null || tv.value < min.value)
163                                         min = tv;
164                 }
165                 return min;
166         }
167
168         public AltosTimeValue first() {
169                 if (values.size() > 0)
170                         return values.get(0);
171                 return null;
172         }
173
174         public AltosTimeValue last() {
175                 if (values.size() > 0)
176                         return values.get(values.size() - 1);
177                 return null;
178         }
179
180         public double average() {
181                 double total_value = 0;
182                 double total_time = 0;
183                 AltosTimeValue prev = null;
184                 for (AltosTimeValue tv : values) {
185                         if (prev != null) {
186                                 total_value += (tv.value + prev.value) / 2 * (tv.time - prev.time);
187                                 total_time += (tv.time - prev.time);
188                         }
189                         prev = tv;
190                 }
191                 if (total_time == 0)
192                         return AltosLib.MISSING;
193                 return total_value / total_time;
194         }
195
196         public double average(double start_time, double end_time) {
197                 double total_value = 0;
198                 double total_time = 0;
199                 AltosTimeValue prev = null;
200                 for (AltosTimeValue tv : values) {
201                         if (start_time <= tv.time && tv.time <= end_time) {
202                                 if (prev != null) {
203                                         total_value += (tv.value + prev.value) / 2 * (tv.time - start_time);
204                                         total_time += (tv.time - start_time);
205                                 }
206                                 start_time = tv.time;
207                         }
208                         prev = tv;
209                 }
210                 if (total_time == 0)
211                         return AltosLib.MISSING;
212                 return total_value / total_time;
213         }
214
215         public AltosTimeSeries integrate(AltosTimeSeries integral) {
216                 double  value = 0.0;
217                 double  pvalue = 0.0;
218                 double  time = 0.0;
219                 boolean start = true;
220
221                 for (AltosTimeValue v : values) {
222                         if (start) {
223                                 value = 0.0;
224                                 start = false;
225                         } else {
226                                 value += (pvalue + v.value) / 2.0 * (v.time - time);
227                         }
228                         pvalue = v.value;
229                         time = v.time;
230                         integral.add(time, value);
231
232                 }
233                 return integral;
234         }
235
236         public AltosTimeSeries differentiate(AltosTimeSeries diff) {
237                 double value = 0.0;
238                 double time = 0.0;
239                 boolean start = true;
240
241                 for (AltosTimeValue v: values) {
242                         if (start) {
243                                 value = v.value;
244                                 time = v.time;
245                                 start = false;
246                         } else {
247                                 double  dx = v.time - time;
248                                 double  dy = v.value - value;
249
250                                 if (dx != 0)
251                                         diff.add(time, dy/dx);
252
253                                 time = v.time;
254                                 value = v.value;
255                         }
256                 }
257                 return diff;
258         }
259
260         private int find_left(int i, double dt) {
261                 int j;
262                 double t = values.get(i).time - dt;
263                 for (j = i; j >= 0; j--)        {
264                         if (values.get(j).time < t)
265                                 break;
266                 }
267                 return j + 1;
268
269         }
270
271         private int find_right(int i, double dt) {
272                 int j;
273                 double t = values.get(i).time + dt;
274                 for (j = i; j < values.size(); j++) {
275                         if (values.get(j).time > t)
276                                 break;
277                 }
278                 return j - 1;
279
280         }
281
282         private static double i0(double x) {
283                 double  ds = 1, d = 0, s = 0;
284
285                 do {
286                         d += 2;
287                         ds = ds * (x * x) / (d * d);
288                         s += ds;
289                 } while (ds - 0.2e-8 * s > 0);
290                 return s;
291         }
292
293         private static double kaiser(double n, double m, double beta) {
294                 double alpha = m / 2;
295                 double t = (n - alpha) / alpha;
296
297                 if (t > 1 || t < -1)
298                         t = 1;
299                 double k = i0 (beta * Math.sqrt (1 - t*t)) / i0(beta);
300                 return k;
301         }
302
303         private double filter_coeff(double dist, double width) {
304                 return kaiser(dist + width/2.0, width, 2 * Math.PI);
305         }
306
307         public AltosTimeSeries filter(AltosTimeSeries f, double width) {
308
309                 double  half_width = width/2;
310                 int half_point = values.size() / 2;
311                 for (int i = 0; i < values.size(); i++) {
312                         double  center_time = values.get(i).time;
313                         double  left_time = center_time - half_width;
314                         double  right_time = center_time + half_width;
315                         double  total_coeff = 0.0;
316                         double  total_value = 0.0;
317
318                         int     left = find_left(i, half_width);
319                         int     right = find_right(i, half_width);
320
321                         for (int j = left; j <= right; j++) {
322                                 double  j_time = values.get(j).time;
323
324                                 if (left_time <= j_time && j_time <= right_time) {
325                                         double  j_left = j == left ? left_time : values.get(j-1).time;
326                                         double  j_right = j == right ? right_time : values.get(j+1).time;
327                                         double  interval = (j_right - j_left) / 2.0;
328                                         double  coeff = filter_coeff(j_time - center_time, width) * interval;
329                                         double  value = values.get(j).value;
330                                         double  partial = value * coeff;
331
332                                         total_coeff += coeff;
333                                         total_value += partial;
334                                 }
335                         }
336                         if (total_coeff != 0.0)
337                                 f.add(center_time, total_value / total_coeff);
338                 }
339                 return f;
340         }
341
342         public AltosTimeSeries(String label, AltosUnits units) {
343                 this.label = label;
344                 this.units = units;
345                 this.values = new ArrayList<AltosTimeValue>();
346         }
347 }