blob: c19f1217d2c532b86afd72ba114c4304bbbb189f [file] [log] [blame]
/**
*
*/
package org.eclipse.stem.util.analysis;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.stem.analysis.impl.ReferenceScenarioDataMapImpl.ReferenceScenarioDataInstance;
/*******************************************************************************
* Copyright (c) 2007, 2008 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
/**
* This class is designed to take a time series with multiple epidemic waves. It collapses the data into
* a single "averaged" cycle aligning the peaks of each epidemic using a smoothing and peak finding algorithm
*/
public class CycleCompressor {
org.eclipse.stem.analysis.impl.ReferenceScenarioDataMapImpl.ReferenceScenarioDataInstance data;
/**
* this phase shift maximizes the correlation between the two vectors
*/
static final int WINDOW = 365;
/**
* by default we will try to find max correlation for a shift of +/- 30 days
* if the vectors are shorter than this we reduce the window
*/
static final int INITIAL_PHASE_SHIFT = 30;
/**
* the peak positions
*/
public static List<Integer> peakPositions = new ArrayList<Integer>();
static List<Double> compressedCycles = new ArrayList<Double>();
protected static final int[] DAY_OF_CHANNUKA = { 347, 702, 1086, 1430, 1794, 2179,
2533, 2916, 3271, 3625, 4008 };
/**
* This class is designed to take a ReferenceScenarioDataInstance(s) and to reconstruct a single "averaged" or
* typical epidemic wave from a time series containing many such waves.
* Note that the peak finder algorithm operates on smoothed data but once the peaks are located
* the integration and normalization is done with the raw data.
* @param dataInstance
*
*/
public CycleCompressor(ReferenceScenarioDataInstance dataInstance) {
data = dataInstance;
String key = "Incidence"; // this only works for incidence data right now
peakPositions = this.getDelayAfterChannuka(data, key );
//peakPositions = this.getPeakPositions(data, key );
for (int i = 0; i < peakPositions.size(); i ++) {
int peak = peakPositions.get(i).intValue();
System.out.print(""+peak+",");
}
Activator.logInformation("-1");
compressedCycles = this.compressData(data, peakPositions, key);
}
/**
* Finds index of peaks, returns as list
* @param instance
* @param key
* @parameter key (must be I, E, or incidence - they have peaks)
* @return list of peak positions
*/
public List<Integer> getPeakPositions(ReferenceScenarioDataInstance instance, String key) {
List<Integer> peaks = new ArrayList<Integer>();
List<Double> vector = instance.getData().get(key);
List<Double> smoothedOnce = smooth3(vector);
List<Double> smoothed = smooth3(smoothedOnce);
int istart = 0;
int istop = INITIAL_PHASE_SHIFT;
int peakPos = findPeak(smoothed,istart,istop);
if(peakPos!=istart) peaks.add(Integer.valueOf(peakPos));
int maxStartIndex = smoothed.size() - WINDOW/2;
while(peakPos < maxStartIndex ) {
istart = peakPos+(WINDOW/2);
istop = istart + WINDOW;
peakPos= findPeak(smoothed,istart,istop);
peaks.add(Integer.valueOf(peakPos));
}
return peaks;
}
/**
* Finds index of peaks, returns as list
* @param instance
* @param key
* @parameter key (must be I, E, or incidence - they have peaks)
* @return list of peak positions
*/
public List<Integer> getDelayAfterChannuka(ReferenceScenarioDataInstance instance, String key) {
List<Integer> peaks = new ArrayList<Integer>();
List<Double> vector = instance.getData().get(key);
List<Double> smoothedOnce = smooth3(vector);
List<Double> smoothed = smooth3(smoothedOnce);
for (int i = 0; i < DAY_OF_CHANNUKA.length; i ++) {
int istart = DAY_OF_CHANNUKA[i]-30;
int istop = istart + 90;
//int peakPos = findFirstPeak(smoothed,istart,istop);
int peakPos = findPeak(smoothed,istart,istop);
peaks.add(Integer.valueOf(peakPos));
}
return peaks;
}
/**
* Created and "averaged" cycle from several periods of data
* @param instance
* @param peaks
* @param key
* @return the compressed data
*/
public List<Double> compressData(ReferenceScenarioDataInstance instance, List<Integer> peaks, String key) {
List<Double> data = instance.getData().get(key);
double[] sum = new double[WINDOW];
double[] denom = new double[WINDOW];
//1. init
for(int i = 0; i < WINDOW; i ++) {
sum[i] = 0.0;
denom[i] = 0.0;
}
//2. integrate all cycles
for (int iPeak = 0; iPeak < peaks.size(); iPeak ++) {
int nextPeak = peaks.get(iPeak).intValue();
int istart = nextPeak-WINDOW/2;
int index = istart;
for(int i = 0; i < WINDOW; i ++) {
if((index >=0)&&(index < data.size())) {
double val = data.get(index).doubleValue();
sum[i] += val;
denom[i] += 1.0;
}// if in bounds
index ++;
}// all data in window
}// all peaks
//3. normalize the result
List<Double> compressed = new ArrayList<Double>();
for(int i = 0; i < WINDOW; i ++) {
if(denom[i] <= 0.0) denom[i] = 1.0; // always
double val = sum[i] / denom[i];
compressed.add(i,new Double(val));
}// all data in window
return compressed;
}
/**
* @param dataList
* @param istart
* @param istop
* @return index of peak position within window
*/
public static int findPeak(List<Double> dataList, int istart, int istop) {
int peakIndex = istart;
double peakVal = 0.0;
for (int i = istart; i < istop; i ++) {
if(i < dataList.size() ) {
double val = dataList.get(i).doubleValue();
if(val>peakVal) {
peakVal = val;
peakIndex = i;
}// if peak
}// if not end of data
}// for all data
//Activator.logInformation("found peak at "+peakIndex+" val="+peakVal);
return peakIndex;
}// findPeak
/**
* @param dataList
* @param istart
* @param istop
* @return index of peak position within window
*/
public static int findFirstPeak(List<Double> dataList, int istart, int istop) {
int peakIndex = istart;
double peakVal = 0.0;
for (int i = istart; i < istop; i ++) {
if(i < dataList.size() ) {
double val = dataList.get(i).doubleValue();
if(val>=peakVal) {
peakVal = val;
peakIndex = i;
}// if peak
else {
return peakIndex;
}
}// if not end of data
}// for all data
//Activator.logInformation("found peak at "+peakIndex+" val="+peakVal);
return peakIndex;
}// findPeak
/**
* do a three point smooth
* @param dataList
* @return the smoothed data
*/
public static List<Double> smooth3(List<Double> dataList) {
List<Double> smooth = new ArrayList<Double>();
double v0 = dataList.get(0).doubleValue();
double v1 = dataList.get(1).doubleValue();
// add the first endpoint
smooth.add( new Double( (v0+v1)/2.0) );
// add the others
for (int i = 1; i < dataList.size()-1; i ++) {
double d1 = dataList.get(i-1).doubleValue();
double d2 = dataList.get(i).doubleValue();
double d3 = dataList.get(i+1).doubleValue();
smooth.add(i, new Double( (d1+d2+d3)/3.0) );
}
int iLast = dataList.size() -1;
// add the last end point
v0 = dataList.get(iLast).doubleValue();
v1 = dataList.get(iLast-1).doubleValue();
smooth.add( new Double( (v0+v1)/2.0) );
return smooth;
}// smooth
/**
* get the result
* @return cycle compressed data
*/
public double[] getCompressedData() {
double[] values = new double[compressedCycles.size()];
for (int i = 0; i < compressedCycles.size(); i ++) {
values[i] = compressedCycles.get(i).doubleValue();
}
return values;
}
}