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