/*
* ADWIN.java
* Copyright (C) 2008 UPC-Barcelona Tech, Catalonia
* @author Albert Bifet (abifet at cs dot waikato dot ac dot nz)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*
* ADWIN_Volatility.java
* Modified by: David Tse Jung Huang (The University of Auckland)
* As seen in: David Tse Jung Huang, Yun Sing Koh, Gillian Dobbie, and Albert Bifet.
* Drift detection using Stream Volatility. ECML/PKDD, pages 414-425, 2015.
*
* Usage: setInput(double intEntrada, double delta, double x, String mode)
* intEntrada - input value
* delta - delta confidence
* x - relative position [0.0, 1.0] to the next drift point with 0.0 being furthest away and 1.0 being right on
* mode - "SINE" for sine function mode and "SIGMOID" for sigmoid function mode
*/
public class ADWIN_Volatility
{
private class List
{
protected int count;
protected ListItem head;
protected ListItem tail;
public List()
{
// post: initializes the list to be empty.
clear();
addToHead();
}
/* Interface Store Methods */
public int size()
{
// post: returns the number of elements in the list.
return this.count;
}
public ListItem head()
{
// post: returns the number of elements in the list.
return this.head;
}
public ListItem tail()
{
// post: returns the number of elements in the list.
return this.tail;
}
public boolean isEmpty()
{
// post: returns the true iff store is empty.
return (this.size() == 0);
}
public void clear()
{
// post: clears the list so that it contains no elements.
this.head = null;
this.tail = null;
this.count = 0;
}
/* Interface List Methods */
public void addToHead()
{
// pre: anObject is non-null
// post: the object is added to the beginning of the list
this.head = new ListItem(this.head, null);
if (this.tail == null)
{
this.tail = this.head;
}
this.count++;
}
public void removeFromHead()
{
// pre: list is not empty
// post: removes and returns first object from the list
// ListItem temp;
// temp = this.head;
this.head = this.head.next();
if (this.head != null)
{
this.head.setPrevious(null);
} else
{
this.tail = null;
}
this.count--;
// temp=null;
return;
}
public void addToTail()
{
// pre: anObject is non-null
// post: the object is added at the end of the list
this.tail = new ListItem(null, this.tail);
if (this.head == null)
{
this.head = this.tail;
}
this.count++;
}
public void removeFromTail()
{
// pre: list is not empty
// post: the last object in the list is removed and returned
// ListItem temp;
// temp = this.tail;
this.tail = this.tail.previous();
if (this.tail == null)
{
this.head = null;
} else
{
this.tail.setNext(null);
}
this.count--;
// temp=null;
return;
}
}
private class ListItem
{
// protected Object data;
protected ListItem next;
protected ListItem previous;
protected int bucketSizeRow = 0;
protected int MAXBUCKETS = ADWIN_Volatility.MAXBUCKETS;
protected double bucketTotal[] = new double[MAXBUCKETS + 1];
protected double bucketVariance[] = new double[MAXBUCKETS + 1];
public ListItem()
{
// post: initializes the node to be a tail node
// containing the given value.
this(null, null);
}
public void clear()
{
bucketSizeRow = 0;
for (int k = 0; k <= MAXBUCKETS; k++)
{
clearBucket(k);
}
}
private void clearBucket(int k)
{
setTotal(0, k);
setVariance(0, k);
}
public ListItem(ListItem nextNode, ListItem previousNode)
{
// post: initializes the node to contain the given
// object and link to the given next node.
// this.data = element;
this.next = nextNode;
this.previous = previousNode;
if (nextNode != null)
{
nextNode.previous = this;
}
if (previousNode != null)
{
previousNode.next = this;
}
clear();
}
public void insertBucket(double Value, double Variance)
{
// insert a Bucket at the end
int k = bucketSizeRow;
bucketSizeRow++;
// Insert new bucket
setTotal(Value, k);
setVariance(Variance, k);
}
public void RemoveBucket()
{
// Removes the first Buvket
compressBucketsRow(1);
}
public void compressBucketsRow(int NumberItemsDeleted)
{
// Delete first elements
for (int k = NumberItemsDeleted; k <= MAXBUCKETS; k++)
{
bucketTotal[k - NumberItemsDeleted] = bucketTotal[k];
bucketVariance[k - NumberItemsDeleted] = bucketVariance[k];
}
for (int k = 1; k <= NumberItemsDeleted; k++)
{
clearBucket(MAXBUCKETS - k + 1);
}
bucketSizeRow -= NumberItemsDeleted;
// BucketNumber-=NumberItemsDeleted;
}
public ListItem previous()
{
// post: returns the previous node.
return this.previous;
}
public void setPrevious(ListItem previous)
{
// post: sets the previous node to be the given node
this.previous = previous;
}
public ListItem next()
{
// post: returns the next node.
return this.next;
}
public void setNext(ListItem next)
{
// post: sets the next node to be the given node
this.next = next;
}
public double Total(int k)
{
// post: returns the element in this node
return bucketTotal[k];
}
public double Variance(int k)
{
// post: returns the element in this node
return bucketVariance[k];
}
public void setTotal(double value, int k)
{
// post: sets the element in this node to the given
// object.
bucketTotal[k] = value;
}
public void setVariance(double value, int k)
{
// post: sets the element in this node to the given
// object.
bucketVariance[k] = value;
}
/*
* public ListItem(Object element, ListItem nextNode){ // post:
* initializes the node to contain the given // object and link to the
* given next node. this.data = element; this.next = nextNode; } public
* ListItem(Object element) { // post: initializes the node to be a tail
* node // containing the given value. this(element, null); }
*
*
* public Object value() { // post: returns the element in this node
* return this.data; } public void setValue(Object anObject) { // post:
* sets the element in this node to the given // object. this.data =
* anObject; }
*/
}
public static final double DELTA = .002; // .1;
private static final int mintMinimLongitudWindow = 10; // 10
private double mdbldelta = .002; // .1;
private int mintTime = 0;
private int mintClock = 32;
private double mdblWidth = 0; // Mean of Width = mdblWidth/Number of items
// BUCKET
public static final int MAXBUCKETS = 5;
private int lastBucketRow = 0;
private double TOTAL = 0;
private double VARIANCE = 0;
private int WIDTH = 0;
private int BucketNumber = 0;
private int Detect = 0;
private int numberDetections = 0;
private int DetectTwice = 0;
private boolean blnBucketDeleted = false;
private int BucketNumberMAX = 0;
private int mintMinWinLength = 5;
private List listRowBuckets;
//
private int checks = 0;
public int getChecks()
{
return checks;
}
private double pWeight = 0.0;
private int weightCheckCount = 0;
public double getWeight()
{
return pWeight;
}
public void setWeight(double value)
{
this.pWeight = value;
}
private double tension = 0.0;
public void setTension(double value)
{
this.tension = value;
}
//
public boolean getChange()
{
return blnBucketDeleted;
}
public void resetChange()
{
blnBucketDeleted = false;
}
public int getBucketsUsed()
{
return BucketNumberMAX;
}
public int getWidth()
{
return WIDTH;
}
public void setClock(int intClock)
{
mintClock = intClock;
}
public int getClock()
{
return mintClock;
}
public boolean getWarning()
{
return false;
}
public boolean getDetect()
{
return (Detect == mintTime);
}
public int getNumberDetections()
{
return numberDetections;
}
public double getTotal()
{
return TOTAL;
}
public double getEstimation()
{
return TOTAL / WIDTH;
}
public double getVariance()
{
return VARIANCE / WIDTH;
}
public double getWidthT()
{
return mdblWidth;
}
private void initBuckets()
{
// Init buckets
listRowBuckets = new List();
lastBucketRow = 0;
TOTAL = 0;
VARIANCE = 0;
WIDTH = 0;
BucketNumber = 0;
}
private void insertElement(double Value)
{
WIDTH++;
insertElementBucket(0, Value, listRowBuckets.head());
double incVariance = 0;
if (WIDTH > 1)
{
incVariance = (WIDTH - 1) * (Value - TOTAL / (WIDTH - 1))
* (Value - TOTAL / (WIDTH - 1)) / WIDTH;
}
VARIANCE += incVariance;
TOTAL += Value;
compressBuckets();
}
private void insertElementBucket(double Variance, double Value,
ListItem Node)
{
// Insert new bucket
Node.insertBucket(Value, Variance);
BucketNumber++;
if (BucketNumber > BucketNumberMAX)
{
BucketNumberMAX = BucketNumber;
}
}
private int bucketSize(int Row)
{
return (int) Math.pow(2, Row);
}
public int deleteElement()
{
// LIST
// Update statistics
ListItem Node;
Node = listRowBuckets.tail();
int n1 = bucketSize(lastBucketRow);
WIDTH -= n1;
TOTAL -= Node.Total(0);
double u1 = Node.Total(0) / n1;
double incVariance = Node.Variance(0) + n1 * WIDTH
* (u1 - TOTAL / WIDTH) * (u1 - TOTAL / WIDTH) / (n1 + WIDTH);
VARIANCE -= incVariance;
// Delete Bucket
Node.RemoveBucket();
BucketNumber--;
if (Node.bucketSizeRow == 0)
{
listRowBuckets.removeFromTail();
lastBucketRow--;
}
return n1;
}
public void compressBuckets()
{
// Traverse the list of buckets in increasing order
int n1, n2;
double u2, u1, incVariance;
ListItem cursor;
ListItem nextNode;
cursor = listRowBuckets.head();
int i = 0;
do
{
// Find the number of buckets in a row
int k = cursor.bucketSizeRow;
// If the row is full, merge buckets
if (k == MAXBUCKETS + 1)
{
nextNode = cursor.next();
if (nextNode == null)
{
listRowBuckets.addToTail();
nextNode = cursor.next();
lastBucketRow++;
}
n1 = bucketSize(i);
n2 = bucketSize(i);
u1 = cursor.Total(0) / n1;
u2 = cursor.Total(1) / n2;
incVariance = n1 * n2 * (u1 - u2) * (u1 - u2) / (n1 + n2);
nextNode.insertBucket(cursor.Total(0) + cursor.Total(1),
cursor.Variance(0) + cursor.Variance(1) + incVariance);
BucketNumber++;
cursor.compressBucketsRow(2);
if (nextNode.bucketSizeRow <= MAXBUCKETS)
{
break;
}
} else
{
break;
}
cursor = cursor.next();
i++;
} while (cursor != null);
}
public boolean setInput(double intEntrada)
{
return setInput(intEntrada, mdbldelta);
}
private double relPos;
public boolean setInput(double intEntrada, double delta, double x, String mode)
{
relPos = x;
relPos = x > 1.0 ? 1.0 : x;
relPos = x < 0.0 ? 0.0 : x;
setMode(mode);
return setInput(intEntrada, delta);
}
private int mode = 0;
public void setMode(String value)
{
if(value.equals("SINE"))
{
mode = 1;
}
else if(value.equals("SIGMOID"))
{
mode = 2;
}
}
public boolean setInput(double intEntrada, double delta)
{
//System.out.println("here");
boolean blnChange = false;
boolean blnExit = false;
ListItem cursor;
mintTime++;
// 1,2)Increment window in one element
insertElement(intEntrada);
blnBucketDeleted = false;
// 3)Reduce window
if (mintTime % mintClock == 0 && getWidth() > mintMinimLongitudWindow)
{
boolean blnReduceWidth = true; // Diference
while (blnReduceWidth) // Diference
{
blnReduceWidth = false; // Diference
blnExit = false;
int n0 = 0;
int n1 = WIDTH;
double u0 = 0;
double u1 = getTotal();
double v0 = 0;
double v1 = VARIANCE;
double n2 = 0;
double u2 = 0;
//
weightCheckCount = weightCheckCount < mintClock ? weightCheckCount + 1 : 0 ;
pWeight = weightCheckCount == 0 ? 0.0 : pWeight;
//
cursor = listRowBuckets.tail();
int i = lastBucketRow;
do
{
for (int k = 0; k <= (cursor.bucketSizeRow - 1); k++)
{
n2 = bucketSize(i);
u2 = cursor.Total(k);
if (n0 > 0)
{
v0 += cursor.Variance(k) + n0 * n2
* (u0 / n0 - u2 / n2) * (u0 / n0 - u2 / n2)
/ (n0 + n2);
}
if (n1 > 0)
{
v1 -= cursor.Variance(k) + n1 * n2
* (u1 / n1 - u2 / n2) * (u1 / n1 - u2 / n2)
/ (n1 + n2);
}
n0 += bucketSize(i);
n1 -= bucketSize(i);
u0 += cursor.Total(k);
u1 -= cursor.Total(k);
if (i == 0 && k == cursor.bucketSizeRow - 1)
{
blnExit = true;
break;
}
checks++;
double absvalue = u0 / n0 - (u1 / n1); // n1 mintMinWinLength + 1 && n0 > mintMinWinLength + 1)
&& // Diference NEGATIVE
blnCutexpression(n0, n1, u0, u1, v0, v1, absvalue, delta))
{
blnBucketDeleted = true;
Detect = mintTime;
if (Detect == 0)
{
Detect = mintTime;
// blnFirst=true;
// blnWarning=true;
} else if (DetectTwice == 0)
{
DetectTwice = mintTime;
// blnDetect=true;
}
blnReduceWidth = true; // Diference
blnChange = true;
if (getWidth() > 0)
{ // Reduce width of the window
// while (n0>0) // Diference NEGATIVE
n0 -= deleteElement();
blnExit = true;
break;
}
} // End if
}// Next k
cursor = cursor.previous();
i--;
} while (((!blnExit && cursor != null)));
}// End While // Diference
}// End if
mdblWidth += getWidth();
if (blnChange)
{
numberDetections++;
}
return blnChange;
}
private boolean blnCutexpression(int n0, int n1, double u0, double u1, double v0, double v1, double absvalue, double delta)
{
int n = getWidth();
double dd = Math.log(2 * Math.log(n) / delta); // -- ull perque el ln n
// va al numerador.
// Formula Gener 2008
double v = getVariance();
// System.out.println(v + " " + WIDTH);
double m = ((double) 1 / ((n0 - mintMinWinLength + 1)))
+ ((double) 1 / ((n1 - mintMinWinLength + 1)));
double epsilon = Math.sqrt(2 * m * v * dd) + (double) 2 / 3 * dd * m;
double x = relPos;
double y = 1-x;
if(mode == 1)
{
//SINE FUNCTION
double esinebar = epsilon * (1 + (tension * Math.sin(Math.PI * x)));
epsilon = esinebar;
}
else if(mode == 2)
{
//SIGMOID FUNCTION
double esigbar = (1 + (tension * (y / (.01 + Math.abs(y))))) * epsilon;
epsilon = esigbar;
}
return (Math.abs(absvalue) > epsilon);
}
public ADWIN_Volatility()
{
mdbldelta = DELTA;
initBuckets();
Detect = 0;
numberDetections = 0;
DetectTwice = 0;
}
public ADWIN_Volatility(double d)
{
mdbldelta = d;
initBuckets();
Detect = 0;
numberDetections = 0;
DetectTwice = 0;
}
public ADWIN_Volatility(int cl)
{
mdbldelta = DELTA;
initBuckets();
Detect = 0;
numberDetections = 0;
DetectTwice = 0;
mintClock = cl;
}
}