2 * Copyright © 2017 Keith Packard <keithp@keithp.com>
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.
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.
15 package org.altusmetrum.altoslib_14;
19 public class AltosTimeSeries implements Iterable<AltosTimeValue>, Comparable<AltosTimeSeries> {
21 public AltosUnits units;
22 ArrayList<AltosTimeValue> values;
26 public int compareTo(AltosTimeSeries other) {
27 return label.compareTo(other.label);
30 public void add(AltosTimeValue tv) {
31 if (tv.time >= min_time) {
37 public void erase_values() {
39 this.values = new ArrayList<AltosTimeValue>();
42 public void clear_changed() {
46 // public boolean changed() {
47 // return data_changed;
50 public void add(double time, double value) {
51 add(new AltosTimeValue(time, value));
54 public AltosTimeValue get(int i) {
58 private double lerp(AltosTimeValue v0, AltosTimeValue v1, double t) {
60 if (v0.time == v1.time)
61 return (v0.value + v1.value) / 2;
63 return (v0.value * (v1.time - t) + v1.value * (t - v0.time)) / (v1.time - v0.time);
66 private int after_index(double time) {
68 int hi = values.size() - 1;
71 int mid = (lo + hi) / 2;
73 if (values.get(mid).time < time)
81 /* Compute a value for an arbitrary time */
82 public double value(double time) {
83 int after = after_index(time);
87 ret = values.get(0).value;
88 else if (after == values.size())
89 ret = values.get(after - 1).value;
91 AltosTimeValue b = values.get(after-1);
92 AltosTimeValue a = values.get(after);
93 ret = lerp(b, a, time);
98 /* Find the value just before an arbitrary time */
99 public double value_before(double time) {
100 int after = after_index(time);
103 return values.get(0).value;
104 return values.get(after-1).value;
107 /* Find the value just after an arbitrary time */
108 public double value_after(double time) {
109 int after = after_index(time);
111 if (after == values.size())
112 return values.get(after-1).value;
113 return values.get(after).value;
116 public double time_of(double value) {
117 double last = AltosLib.MISSING;
118 for (AltosTimeValue v : values) {
119 if (v.value >= value)
127 return values.size();
130 public Iterator<AltosTimeValue> iterator() {
131 return values.iterator();
134 public AltosTimeValue max() {
135 AltosTimeValue max = null;
136 for (AltosTimeValue tv : values)
137 if (max == null || tv.value > max.value)
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)
152 public AltosTimeValue min() {
153 AltosTimeValue min = null;
154 for (AltosTimeValue tv : values) {
155 if (min == null || tv.value < min.value)
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)
171 public AltosTimeValue first() {
172 if (values.size() > 0)
173 return values.get(0);
177 public AltosTimeValue last() {
178 if (values.size() > 0)
179 return values.get(values.size() - 1);
183 public double average() {
184 double total_value = 0;
185 double total_time = 0;
186 AltosTimeValue prev = null;
187 for (AltosTimeValue tv : values) {
189 total_value += (tv.value + prev.value) / 2 * (tv.time - prev.time);
190 total_time += (tv.time - prev.time);
195 return AltosLib.MISSING;
196 return total_value / total_time;
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) {
206 total_value += (tv.value + prev.value) / 2 * (tv.time - start_time);
207 total_time += (tv.time - start_time);
209 start_time = tv.time;
214 return AltosLib.MISSING;
215 return total_value / total_time;
218 public AltosTimeSeries integrate(AltosTimeSeries integral) {
222 boolean start = true;
224 for (AltosTimeValue v : values) {
229 value += (pvalue + v.value) / 2.0 * (v.time - time);
233 integral.add(time, value);
239 public AltosTimeSeries differentiate(AltosTimeSeries diff) {
242 boolean start = true;
244 for (AltosTimeValue v: values) {
250 double dx = v.time - time;
251 double dy = v.value - value;
254 diff.add(time, dy/dx);
263 private int find_left(int i, double dt) {
265 double t = values.get(i).time - dt;
266 for (j = i; j >= 0; j--) {
267 if (values.get(j).time < t)
274 private int find_right(int i, double dt) {
276 double t = values.get(i).time + dt;
277 for (j = i; j < values.size(); j++) {
278 if (values.get(j).time > t)
285 private static double i0(double x) {
286 double ds = 1, d = 0, s = 0;
290 ds = ds * (x * x) / (d * d);
292 } while (ds - 0.2e-8 * s > 0);
296 private static double kaiser(double n, double m, double beta) {
297 double alpha = m / 2;
298 double t = (n - alpha) / alpha;
302 double k = i0 (beta * Math.sqrt (1 - t*t)) / i0(beta);
306 private double filter_coeff(double dist, double width) {
307 return kaiser(dist + width/2.0, width, 2 * Math.PI);
310 public AltosTimeSeries filter(AltosTimeSeries f, double width) {
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;
321 int left = find_left(i, half_width);
322 int right = find_right(i, half_width);
324 for (int j = left; j <= right; j++) {
325 double j_time = values.get(j).time;
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;
335 total_coeff += coeff;
336 total_value += partial;
339 if (total_coeff != 0.0)
340 f.add(center_time, total_value / total_coeff);
345 public AltosTimeSeries clip(AltosTimeSeries clip, double min, double max) {
346 for (AltosTimeValue v: values) {
347 double value = v.value;
348 if (value < min) value = min;
349 if (value > max) value = max;
350 clip.add(v.time, value);
355 public AltosTimeSeries(String label, AltosUnits units) {
358 this.values = new ArrayList<AltosTimeValue>();