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