/* * To change this template, choose Tools | Templates * and open the template in the editor. */ package statistics.simulators; import java.util.*; import java.io.*; /** * * @author u0318701 */ public class ShapeletModel extends Model { public enum ShapeType {TRIANGLE,HEADSHOULDERS,SINE, STEP, SPIKE}; protected ArrayList<Shape> shapes; private static int DEFAULTNUMSHAPELETS=1; private static int DEFAULTSERIESLENGTH=500; private static int DEFAULTSHAPELETLENGTH=29; private static int DEFAULTMAXSTART=70; protected int numShapelets; protected int seriesLength; protected int shapeletLength; protected int maxStart; //Default Constructor, max start should be at least 29 less than the length // of the series if using the default shapelet length of 29 public ShapeletModel() { this(new double[]{DEFAULTNUMSHAPELETS,DEFAULTSERIESLENGTH,DEFAULTSHAPELETLENGTH,DEFAULTMAXSTART}); } public final void setDefaults(){ seriesLength=DEFAULTSERIESLENGTH; numShapelets=DEFAULTNUMSHAPELETS; shapeletLength=DEFAULTSHAPELETLENGTH; maxStart=DEFAULTMAXSTART; } public ShapeletModel(double[] param) { setDefaults(); //PARAMETER LIST: seriesLength, numShapelets, shapeletLength, maxStart //Using the fall through for switching, I should be shot! if(param!=null){ switch(param.length){ default: case 4: maxStart=(int)param[3]; case 3: shapeletLength=(int)param[2]; case 2: numShapelets=(int)param[1]; case 1: seriesLength=(int)param[0]; } } shapes=new ArrayList<>(); // Shapes are randomised for type and location; the other characteristics, such as length // must be changed in the inner class. for(int i=0;i<numShapelets;i++) { Shape sh = new Shape(); boolean valid = sh.randomiseShape(maxStart,shapes); //This checks that there is sufficient space to fit non-overlapping shapes; // If this is not the case, the max start value is increased by //default shapelet length. Be aware that max start may exceed your original // value if too many shapes are used. if(!valid) { // System.out.println("Insufficient space, increasing max start"); maxStart = maxStart+shapeletLength; sh.randomiseShape(maxStart,shapes); } shapes.add(sh); } } //This constructor is used for data of a given length public ShapeletModel(int s) { this(new double[]{(double)s}); } //This constructor is used for data of a given length in a two class problem //where the shape distinguishing the first class is known public ShapeletModel(int seriesLength,Shape shape) { setDefaults(); shapes=new ArrayList<Shape>(); maxStart = seriesLength-shapeletLength; for(int i=0;i<numShapelets;i++) { Shape sh = new Shape(); boolean valid = sh.randomiseShape(maxStart,shapes); //This checks that the shape is not of the same type as the //first shape. while(sh.type==shape.type) valid = sh.randomiseShape(maxStart, shapes); if(!valid){ // System.out.println("Insufficient space, increasing max start"); maxStart = maxStart+shapeletLength; sh.randomiseShape(maxStart,shapes); } shapes.add(sh); } } // This constructor accepts an ArrayList of shapes for the shapelet model, // rather than determining the shapes randomly. public ShapeletModel(ArrayList<Shape> s) { shapes=new ArrayList<Shape>(s); } /*Generate a single data //Assumes a model independent of previous observations. As //such will not be relevant for ARMA or HMM models, which just return -1. * Should probably remove. */ @Override public double generate(double x) { double value=error.simulate(); //System.out.println("Value"+value); //Slightly inefficient for non overlapping shapes, but worth it for clarity and generality for(Shape s:shapes) { //System.out.println("***********Fired"); value+=s.generate((int)x); } return value; } //This will generate the next sequence after currently stored t value @Override public double generate() { //System.out.println("t"+t); double value=generate(t); t++; return value; } /** * Subclasses must implement this, how they take them out of the array is their business. * @param p */ @Override public void setParameters(double[] p) { } // The implementation of the reset method should be adjusted appropriately. // Currently uses the randomiseLocation() method to implement a random // location after reset. public void reset() { t=0; for(int i=0;i<shapes.size();i++) { shapes.get(i).randomiseLocation(maxStart, shapes); } } public ShapeType getShapeType() { return shapes.get(0).type; } public Shape getShape() { ShapeType shp = shapes.get(0).type; Shape shape = new Shape(shp,1,1,1,1); return shape; } // The toString() method has not been changed. @Override public String toString(){ return "nos shapes = "+shapes.size()+" of type: "+shapes.get(0); } // Inner class determining the shape inserted into the shapelet model public static class Shape{ // Type: head and shoulders, spike, step, triangle, or sine wave. private ShapeType type; //Length of shape private int length; //Position of shape on axis determined by base (lowest point) and amp(litude). private double base; private double amp; //The position in the series at which the shape begins. private int location; private static int DEFAULTLENGTH=29; private static int DEFAULTBASE=-2; private static int DEFAULTAMP=4; private static int DEFAULTLOCATION=0; //Default constructor, call randomise shape to get a random instance // The default length is 29, the shape extends from -2 to +2, is of // type head and shoulders, and is located at index 0. private Shape() { this(ShapeType.HEADSHOULDERS,DEFAULTLENGTH,DEFAULTBASE,DEFAULTAMP,DEFAULTLOCATION); } //Set length only, default for the others private Shape(int length){ this(ShapeType.HEADSHOULDERS,length,DEFAULTBASE,DEFAULTAMP,DEFAULTLOCATION); } // This constructor produces a completely specified shape private Shape(ShapeType t,int l, double b, double a, int loc){ type=t; length=l; base=b; amp=a; location=loc; } //Checks the location against the value t, and outputs part of the shape // if appropriate. private double generate(int t){ if(t<location || t>location+length-1) { return 0; } int offset=t-location; double value=0; switch(type){ case TRIANGLE: if(offset<=length/2) { if(offset==0) { value=base; } else { value=((offset/(double)(length/2))*(amp))+base; } } else { if(offset+1==length) { value=base; } else { value=((length-offset-1)/(double)(length/2)*(amp))+base; } } break; case HEADSHOULDERS: if(offset<length/3) { value = ((amp/2)*Math.sin(((2*Math.PI)/((length/3-1)*2))*offset))+base; } else { if(offset+1>=(2*length)/3) { if(length%3>0&&offset>=(length/3)*3) { value = base; } else { value = ((amp/2)*Math.sin(((2*Math.PI)/((length/3-1)*2))*(offset+1-(2*length)/3)))+base; } } else { value = ((amp)*Math.sin(((2*Math.PI)/((length/3-1)*2))*(offset-length/3)))+base; } } break; case SINE: value=amp*Math.sin(((2*Math.PI)/(length-1))*offset)/2; break; case STEP: if(offset<length/2) { value=base; } else { value=base+amp; } break; case SPIKE: if(offset<=length/4) { if(offset==0) { value=0; } else { value=offset/(double)(length/4)*(-amp/2); } } if(offset>length/4 && offset<=length/2) { if(offset == length/2) { value=0; } else { value=(-amp/2)+((length/4-offset-1)/(double)(length/4)*(-amp/2)); } } if(offset>length/2&&offset<=length/4*3) { value=(offset-length/2)/(double)(length/4)*(amp/2); } if(offset>length/4*3) { if(offset+1==length) { value=0; } else { value=(length-offset-1)/(double)(length/4)*(amp/2); } } break; } return value; } private void setLocation(int newLoc) { this.location=newLoc; } private int getLocation() { return location; } private void setType(ShapeType newType) { this.type=newType; } // Randomises the starting location of a shape. Returns false when // there is insufficient space to fit all shapes within the value // of maxStart. This is resolved in the constructor. private boolean randomiseLocation(int maxStart, ArrayList<Shape> shapes) { if(shapes.size()*(length*2-1)>maxStart) { return false; } Random ran = new Random(); boolean validStart = false; int start =-1; while(!validStart) { start = (int)(maxStart*ran.nextDouble()); int end = start+length; validStart = true; for(int i=0;i<shapes.size();i++) { int locStart = shapes.get(i).getLocation(); int locEnd = locStart+length; if((start>=locStart&&start<=locEnd)||(end>=locStart&&end<=locEnd)) { validStart=false; break; } } } setLocation(start); return true; } @Override public String toString() { String shp = ""+this.type; return shp; } //gives a shape a random type and start position private boolean randomiseShape(int maxStart, ArrayList<Shape> shapes) { Random ran = new Random(); boolean valid = randomiseLocation(maxStart, shapes); if(!valid) { return false; } ShapeType [] types = ShapeType.values(); int ranType = (int)(types.length*ran.nextDouble()); setType(types[ranType]); return true; } } //Test harness public static void main (String[] args) throws IOException { ShapeletModel shape = new ShapeletModel(); for(int i=0;i<200;i++) { System.out.println(shape.generate()); } shape.reset(); System.out.println(-10); for(int i=0;i<200;i++) { System.out.println(shape.generate()); } } }