package net.seninp.grammarviz.logic;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Observable;
import net.seninp.gi.logic.GrammarRuleRecord;
import net.seninp.gi.logic.RuleInterval;
import net.seninp.grammarviz.GrammarVizAnomaly;
import net.seninp.grammarviz.anomaly.RRAImplementation;
import net.seninp.grammarviz.model.GrammarVizMessage;
import net.seninp.jmotif.sax.SAXProcessor;
import net.seninp.jmotif.sax.discord.DiscordRecord;
import net.seninp.jmotif.sax.discord.DiscordRecords;
import net.seninp.util.StackTrace;
/**
* Implements a runnable for the proposed in EDBT15 anomaly discovery technique.
*
* @author psenin
*
*/
public class GrammarVizAnomalyFinder extends Observable implements Runnable {
/** The chart data handler. */
private GrammarVizChartData chartData;
/**
* Constructor.
*
* @param motifChartData The chartdata object -- i.e., info about the input and parameters.
*/
public GrammarVizAnomalyFinder(GrammarVizChartData motifChartData) {
super();
this.chartData = motifChartData;
}
@Override
public void run() {
// save the start timestamp
Date start = new Date();
// [2] extract all the intervals
//
log("walking through the grammar rules...");
ArrayList<RuleInterval> intervals = new ArrayList<RuleInterval>(
this.chartData.getGrammarRules().size() * 6);
try {
for (GrammarRuleRecord r : this.chartData.getGrammarRules()) {
if (0 == r.ruleNumber()) {
continue;
}
for (RuleInterval ri : getRulePositionsByRuleNum(r.ruleNumber())) {
RuleInterval i;
i = (RuleInterval) ri.clone();
i.setCoverage(r.getRuleIntervals().size()); // not a coverage used here but a rule
// frequency, will override later
i.setId(r.ruleNumber());
intervals.add(i);
}
}
}
catch (CloneNotSupportedException e) {
e.printStackTrace();
log("Exception thrown: " + e.toString());
return;
}
// [2] populate all intervals with their coverage
//
log("computing the rule coverage...");
int[] coverageArray = new int[this.chartData.originalTimeSeries.length];
for (RuleInterval interval : intervals) {
int startPos = interval.getStart();
int endPos = interval.getEnd();
for (int j = startPos; j < endPos; j++) {
coverageArray[j] = coverageArray[j] + 1;
}
}
// [3] check if somewhere there is a ZERO coverage!
//
log("looking for uncovered regions...");
for (int i = 0; i < coverageArray.length; i++) {
if (0 == coverageArray[i]) {
int j = i;
while ((j < coverageArray.length - 1) && (0 == coverageArray[j])) {
j++;
}
if (Math.abs(i - j) > 1) {
intervals.add(new RuleInterval(0, i, j, 0.0d));
}
i = j;
}
}
List<RuleInterval> zeros = GrammarVizAnomaly.getZeroIntervals(coverageArray);
if (zeros.size() > 0) {
log("found " + zeros.size() + " intervals not covered by rules: " + intervalsToString(zeros));
intervals.addAll(zeros);
}
else {
log("the whole timeseries is covered by rule intervals ...");
}
// resulting discords collection
this.chartData.discords = new DiscordRecords();
// visit registry
// visit registry
HashSet<Integer> registry = new HashSet<Integer>(5 * intervals.get(0).getLength() * 2);
// we conduct the search until the number of discords is less than desired
//
while (this.chartData.discords.getSize() < 5) {
start = new Date();
DiscordRecord bestDiscord;
try {
bestDiscord = RRAImplementation.findBestDiscordForIntervals(
this.chartData.originalTimeSeries, intervals, registry,
this.chartData.getZNormThreshold());
Date end = new Date();
// if the discord is null we getting out of the search
if (Integer.MIN_VALUE == bestDiscord.getNNDistance()
|| Integer.MIN_VALUE == bestDiscord.getPosition()) {
log("breaking the discords search loop, discords found: "
+ this.chartData.discords.getSize() + " last seen discord: "
+ bestDiscord.toString());
break;
}
log("found discord: position " + bestDiscord.getPosition() + ", length "
+ bestDiscord.getLength() + ", NN distance " + bestDiscord.getNNDistance()
+ ", elapsed time: " + SAXProcessor.timeToString(start.getTime(), end.getTime()) + ", "
+ bestDiscord.getInfo());
// collect the result
//
this.chartData.discords.add(bestDiscord);
// mark the discord discovered
//
int markStart = bestDiscord.getPosition() - bestDiscord.getLength();
int markEnd = bestDiscord.getPosition() + bestDiscord.getLength();
if (markStart < 0) {
markStart = 0;
}
if (markEnd > this.chartData.originalTimeSeries.length) {
markEnd = this.chartData.originalTimeSeries.length;
}
for (int i = markStart; i < markEnd; i++) {
registry.add(i);
}
}
catch (Exception e) {
log(StackTrace.toString(e));
e.printStackTrace();
}
}
// end of discords code
//
Date end = new Date();
log("discords found in " + SAXProcessor.timeToString(start.getTime(), end.getTime()));
}
private void log(String message) {
this.setChanged();
notifyObservers(
new GrammarVizMessage(GrammarVizMessage.STATUS_MESSAGE, "Grammarviz3: " + message));
}
/**
* Recovers start and stop coordinates ofRule's subsequences.
*
* @param ruleIdx The rule index.
* @return The array of all intervals corresponding to this rule.
*/
private ArrayList<RuleInterval> getRulePositionsByRuleNum(Integer ruleIdx) {
return this.chartData.getGrammarRules().get(ruleIdx).getRuleIntervals();
}
/**
* Makes a zeroed interval to appear nicely in output.
*
* @param zeros the list of zeros.
* @return the intervals list as a string.
*/
private String intervalsToString(List<RuleInterval> zeros) {
StringBuilder sb = new StringBuilder();
for (RuleInterval i : zeros) {
sb.append(i.toString()).append(",");
}
return sb.toString();
}
}