001    /*
002     * Copyright (C) 2008-2010 by Holger Arndt
003     *
004     * This file is part of the Universal Java Matrix Package (UJMP).
005     * See the NOTICE file distributed with this work for additional
006     * information regarding copyright ownership and licensing.
007     *
008     * UJMP is free software; you can redistribute it and/or modify
009     * it under the terms of the GNU Lesser General Public License as
010     * published by the Free Software Foundation; either version 2
011     * of the License, or (at your option) any later version.
012     *
013     * UJMP is distributed in the hope that it will be useful,
014     * but WITHOUT ANY WARRANTY; without even the implied warranty of
015     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
016     * GNU Lesser General Public License for more details.
017     *
018     * You should have received a copy of the GNU Lesser General Public
019     * License along with UJMP; if not, write to the
020     * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
021     * Boston, MA  02110-1301  USA
022     */
023    
024    package org.ujmp.core.timeseries;
025    
026    import java.util.ArrayList;
027    import java.util.HashMap;
028    import java.util.Iterator;
029    import java.util.List;
030    import java.util.Map;
031    import java.util.SortedMap;
032    import java.util.TreeMap;
033    
034    import org.ujmp.core.Matrix;
035    import org.ujmp.core.collections.SortedListSet;
036    import org.ujmp.core.doublematrix.stub.AbstractDenseDoubleMatrix2D;
037    import org.ujmp.core.exceptions.MatrixException;
038    
039    public class TimeSeriesMatrix extends AbstractDenseDoubleMatrix2D {
040            private static final long serialVersionUID = 4326920011599023858L;
041    
042            public enum Interpolation {
043                    NONE, STEPS, LINEAR
044            };
045    
046            private Interpolation defaultInterpolation = Interpolation.NONE;
047    
048            private final Map<Integer, Interpolation> seriesInterpolations = new HashMap<Integer, Interpolation>();
049    
050            private final List<SortedMap<Long, Double>> series = new ArrayList<SortedMap<Long, Double>>();
051    
052            private final SortedListSet<Long> timestampsListSet = new SortedListSet<Long>();
053    
054            private long[] size = new long[2];
055    
056            public void addEvent(long timestamp, Matrix value) {
057                    if (value.getRowCount() != 1) {
058                            throw new MatrixException("matrix cannot have more than one row");
059                    }
060                    for (int id = 0; id < value.getColumnCount(); id++) {
061                            addEvent(timestamp, id + 1, value.getAsDouble(0, id));
062                    }
063            }
064    
065            public Interpolation getInterpolation(int seriesId) {
066                    Interpolation i = seriesInterpolations.get(seriesId);
067                    if (i == null) {
068                            return defaultInterpolation;
069                    } else {
070                            return i;
071                    }
072            }
073    
074            public Interpolation getDefaultInterpolation() {
075                    return defaultInterpolation;
076            }
077    
078            public void setDefaultInterpolation(Interpolation defaultInterpolation) {
079                    this.defaultInterpolation = defaultInterpolation;
080            }
081    
082            /**
083             * Adds the events of a new Matrix to the time series. The first column of
084             * the matrix must contain the timestamps.
085             * 
086             * @param events
087             *            matrix with events to add
088             */
089            public void addEvents(Matrix events) {
090                    int seriesCount = getSeriesCount();
091                    for (int r = 0; r < events.getRowCount(); r++) {
092                            long timestamp = events.getAsLong(r, 0);
093                            for (int c = 1; c < events.getColumnCount(); c++) {
094                                    double value = events.getAsDouble(r, c);
095                                    addEvent(timestamp, seriesCount + c - 1, value);
096                            }
097                    }
098            }
099    
100            public void addEvent(long timestamp, int column, double value) {
101                    int seriesId = column - 1;
102                    while (series.size() <= seriesId) {
103                            series.add(new TreeMap<Long, Double>());
104                    }
105                    SortedMap<Long, Double> map = series.get(seriesId);
106                    map.put(timestamp, value);
107                    timestampsListSet.add(timestamp);
108            }
109    
110            public int getEventCount() {
111                    return timestampsListSet.size();
112            }
113    
114            public int getSeriesCount() {
115                    return series.size();
116            }
117    
118            public List<Long> getTimestamps() {
119                    return timestampsListSet;
120            }
121    
122            public long[] getSize() {
123                    size[ROW] = getEventCount();
124                    size[COLUMN] = getSeriesCount() + 1;
125                    return size;
126            }
127    
128            public long getRowCount() {
129                    return getEventCount();
130            }
131    
132            public long getColumnCount() {
133                    return getSeriesCount() + 1;
134            }
135    
136            public double getDouble(long row, long column) {
137                    return getDouble((int) row, (int) column);
138            }
139    
140            public double getDouble(int row, int column) {
141    
142                    if (row < 0 || column >= getColumnCount() || row >= getRowCount()) {
143                            return Double.NaN;
144                    }
145    
146                    int seriesId = column - 1;
147                    long timestamp = timestampsListSet.get(row);
148                    if (column == 0) {
149                            return timestamp;
150                    } else {
151                            SortedMap<Long, Double> map = series.get(seriesId);
152                            switch (getInterpolation(seriesId)) {
153                            case NONE:
154                                    Double v = map.get(timestamp);
155                                    if (v == null) {
156                                            return Double.NaN;
157                                    } else {
158                                            return v;
159                                    }
160    
161                            case STEPS:
162                                    Iterator<Long> it = map.keySet().iterator();
163                                    double value = 0.0;
164                                    while (it.hasNext()) {
165                                            long t = it.next();
166                                            if (t > timestamp) {
167                                                    break;
168                                            } else {
169                                                    value = map.get(t);
170                                            }
171                                    }
172                                    return value;
173    
174                            default:
175                                    throw new MatrixException("Interpolation method not (yet) supported: "
176                                                    + getInterpolation(seriesId));
177                            }
178                    }
179            }
180    
181            public void setDouble(double value, long row, long column) {
182                    throw new MatrixException("please use addEvent() for making changes");
183            }
184    
185            public void setDouble(double value, int row, int column) {
186                    throw new MatrixException("please use addEvent() for making changes");
187            }
188    
189            public void setInterpolation(int column, Interpolation interpolation) {
190                    int seriesId = column - 1;
191                    seriesInterpolations.put(seriesId, interpolation);
192            }
193    
194            public long getRowForTime(long time) {
195                    long rows = getRowCount();
196                    for (long r = 0; r < rows; r++) {
197                            long t = getAsLong(r, 0);
198                            if (t == time) {
199                                    return r;
200                            }
201                    }
202                    return -1;
203            }
204    
205            public double getAsDoubleForTime(long time, long column) {
206                    long row = getRowForTime(time);
207                    if (row < 0 || column >= getColumnCount()) {
208                            return Double.NaN;
209                    }
210                    return getAsDouble(row, column);
211            }
212    
213            public long getTimestamp(long row) {
214                    if (row < 0 | row >= timestampsListSet.size()) {
215                            return 0;
216                    } else {
217                            return timestampsListSet.get((int) row);
218                    }
219            }
220    
221            public long getMinTimestamp() {
222                    if (timestampsListSet.isEmpty()) {
223                            return 0;
224                    } else {
225                            return timestampsListSet.first();
226                    }
227            }
228    
229            public long getMaxTimestamp() {
230                    if (timestampsListSet.isEmpty()) {
231                            return 0;
232                    } else {
233                            return timestampsListSet.last();
234                    }
235            }
236    
237    }