package org.rrd4j.data; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; import java.util.List; import java.util.TimeZone; import org.rrd4j.core.Util; import com.tomgibara.crinch.hashing.PerfectStringHash; class RpnCalculator { private enum Token_Symbol { TKN_VAR("") { @Override void do_method(RpnCalculator c, State s) { c.push(s.token.values[s.slot]); s.token_rpi = s.rpi; } }, TKN_NUM("") { @Override void do_method(RpnCalculator c, State s) { c.push(s.token.number); } }, // Arithmetics TKN_PLUS("+") { @Override void do_method(RpnCalculator c, State s) { c.push(c.pop() + c.pop()); } }, TKN_ADDNAN("ADDNAN") { @Override void do_method(RpnCalculator c, State s) { double x1 = c.pop(); double x2 = c.pop(); c.push(Double.isNaN(x1) ? x2 : (Double.isNaN(x2) ? x1 : x1 + x2)); } }, TKN_MINUS("-") { @Override void do_method(RpnCalculator c, State s) { double x2 = c.pop(); double x1 = c.pop(); c.push(x1 - x2); } }, TKN_MULT("*") { @Override void do_method(RpnCalculator c, State s) { c.push(c.pop() * c.pop()); } }, TKN_DIV("/") { @Override void do_method(RpnCalculator c, State s) { double x2 = c.pop(); double x1 = c.pop(); c.push(x1 / x2); } }, TKN_MOD("%") { @Override void do_method(RpnCalculator c, State s) { double x2 = c.pop(); double x1 = c.pop(); c.push(x1 % x2); } }, TKN_SIN("SIN") { @Override void do_method(RpnCalculator c, State s) { c.push(Math.sin(c.pop())); } }, TKN_COS("COS") { @Override void do_method(RpnCalculator c, State s) { c.push(Math.cos(c.pop())); } }, TKN_LOG("LOG") { @Override void do_method(RpnCalculator c, State s) { c.push(Math.log(c.pop())); } }, TKN_EXP("EXP") { @Override void do_method(RpnCalculator c, State s) { c.push(Math.exp(c.pop())); } }, TKN_SQRT("SQRT") { @Override void do_method(RpnCalculator c, State s) { c.push(Math.sqrt(c.pop())); } }, TKN_ATAN("ATAN") { @Override void do_method(RpnCalculator c, State s) { c.push(Math.atan(c.pop())); } }, TKN_ATAN2("ATAN2") { @Override void do_method(RpnCalculator c, State s) { double x2 = c.pop(); double x1 = c.pop(); c.push(Math.atan2(x1, x2)); } }, TKN_FLOOR("FLOOR") { @Override void do_method(RpnCalculator c, State s) { c.push(Math.floor(c.pop())); } }, TKN_CEIL("CEIL") { @Override void do_method(RpnCalculator c, State s) { c.push(Math.ceil(c.pop())); } }, TKN_DEG2RAD("DEG2RAD") { @Override void do_method(RpnCalculator c, State s) { c.push(Math.toRadians(c.pop())); } }, TKN_RAD2DEG("RAD2DEG") { @Override void do_method(RpnCalculator c, State s) { c.push(Math.toDegrees(c.pop())); } }, TKN_ROUND("ROUND") { @Override void do_method(RpnCalculator c, State s) { c.push(Math.round(c.pop())); } }, TKN_POW("POW") { @Override void do_method(RpnCalculator c, State s) { double x2 = c.pop(); double x1 = c.pop(); c.push(Math.pow(x1, x2)); } }, TKN_ABS("ABS") { @Override void do_method(RpnCalculator c, State s) { c.push(Math.abs(c.pop())); } }, TKN_RANDOM("RANDOM") { @Override void do_method(RpnCalculator c, State s) { c.push(Math.random()); } }, TKN_RND("RND") { @Override void do_method(RpnCalculator c, State s) { c.push(Math.floor(c.pop() * Math.random())); } }, // Boolean operators TKN_UN("UN") { @Override void do_method(RpnCalculator c, State s) { c.push(Double.isNaN(c.pop()) ? 1 : 0); } }, TKN_ISINF("ISINF") { @Override void do_method(RpnCalculator c, State s) { c.push(Double.isInfinite(c.pop()) ? 1 : 0); } }, TKN_LT("LT") { @Override void do_method(RpnCalculator c, State s) { double x2 = c.pop(); double x1 = c.pop(); c.push(x1 < x2 ? 1 : 0); } }, TKN_LE("LE") { @Override void do_method(RpnCalculator c, State s) { double x2 = c.pop(); double x1 = c.pop(); c.push(x1 <= x2 ? 1 : 0); } }, TKN_GT("GT") { @Override void do_method(RpnCalculator c, State s) { double x2 = c.pop(); double x1 = c.pop(); c.push(x1 > x2 ? 1 : 0); } }, TKN_GE("GE") { @Override void do_method(RpnCalculator c, State s) { double x2 = c.pop(); double x1 = c.pop(); c.push(x1 >= x2 ? 1 : 0); } }, TKN_EQ("EQ") { @Override void do_method(RpnCalculator c, State s) { double x2 = c.pop(); double x1 = c.pop(); c.push(x1 == x2 ? 1 : 0); } }, TKN_NE("NE") { @Override void do_method(RpnCalculator c, State s) { double x2 = c.pop(); double x1 = c.pop(); c.push(x1 != x2 ? 1 : 0); } }, TKN_IF("IF") { @Override void do_method(RpnCalculator c, State s) { double x3 = c.pop(); double x2 = c.pop(); double x1 = c.pop(); c.push(x1 != 0 ? x2 : x3); } }, // Comparing values TKN_MIN("MIN") { @Override void do_method(RpnCalculator c, State s) { c.push(Math.min(c.pop(), c.pop())); } }, TKN_MAX("MAX") { @Override void do_method(RpnCalculator c, State s) { c.push(Math.max(c.pop(), c.pop())); } }, TKN_MINNAN("MINNAN") { @Override void do_method(RpnCalculator c, State s) { double x1 = c.pop(); double x2 = c.pop(); c.push(Double.isNaN(x1) ? x2 : (Double.isNaN(x2) ? x1 : Math.min(x1, x2))); } }, TKN_MAXNAN("MAXNAN") { @Override void do_method(RpnCalculator c, State s) { double x1 = c.pop(); double x2 = c.pop(); c.push(Double.isNaN(x1) ? x2 : (Double.isNaN(x2) ? x1 : Math.max(x1, x2))); } }, TKN_LIMIT("LIMIT") { @Override void do_method(RpnCalculator c, State s) { double x3 = c.pop(); double x2 = c.pop(); double x1 = c.pop(); c.push(x1 < x2 || x1 > x3 ? Double.NaN : x1); } }, // Processing the stack directly TKN_DUP("DUP") { @Override void do_method(RpnCalculator c, State s) { c.push(c.peek()); } }, TKN_EXC("EXC") { @Override void do_method(RpnCalculator c, State s) { double x2 = c.pop(); double x1 = c.pop(); c.push(x2); c.push(x1); } }, TKN_POP("POP") { @Override void do_method(RpnCalculator c, State s) { c.pop(); } }, // Special values TKN_UNKN("UNKN") { @Override void do_method(RpnCalculator c, State s) { c.push(Double.NaN); } }, TKN_PI("PI") { @Override void do_method(RpnCalculator c, State s) { c.push(Math.PI); } }, TKN_E("E") { @Override void do_method(RpnCalculator c, State s) { c.push(Math.E); } }, TKN_INF("INF") { @Override void do_method(RpnCalculator c, State s) { c.push(Double.POSITIVE_INFINITY); } }, TKN_NEGINF("NEGINF") { @Override void do_method(RpnCalculator c, State s) { c.push(Double.NEGATIVE_INFINITY); } }, // Logical operator TKN_AND("AND") { @Override void do_method(RpnCalculator c, State s) { double x2 = c.pop(); double x1 = c.pop(); c.push((x1 != 0 && x2 != 0) ? 1 : 0); } }, TKN_OR("OR") { @Override void do_method(RpnCalculator c, State s) { double x2 = c.pop(); double x1 = c.pop(); c.push((x1 != 0 || x2 != 0) ? 1 : 0); } }, TKN_XOR("XOR") { @Override void do_method(RpnCalculator c, State s) { double x2 = c.pop(); double x1 = c.pop(); c.push(((x1 != 0 && x2 == 0) || (x1 == 0 && x2 != 0)) ? 1 : 0); } }, TKN_PREV("PREV") { @Override void do_method(RpnCalculator c, State s) { c.push((s.slot == 0) ? Double.NaN : s.token.values[s.slot - 1]); } }, //Time and date operator TKN_STEP("STEP") { @Override void do_method(RpnCalculator c, State s) { c.push(c.timeStep); } }, TKN_NOW("NOW") { @Override void do_method(RpnCalculator c, State s) { c.push(Util.getTime()); } }, TKN_TIME("TIME") { @Override void do_method(RpnCalculator c, State s) { c.push(c.timestamps[s.slot]); } }, TKN_LTIME("LTIME") { @Override void do_method(RpnCalculator c, State s) { TimeZone tz = s.getTimeZone(); c.push(c.timestamps[s.slot] + (long) (tz.getOffset(c.timestamps[s.slot]) / 1000L)); } }, TKN_YEAR("YEAR") { @Override void do_method(RpnCalculator c, State s) { c.push(c.getCalendarField(c.pop(), Calendar.YEAR)); } }, TKN_MONTH("MONTH") { @Override void do_method(RpnCalculator c, State s) { c.push(c.getCalendarField(c.pop(), Calendar.MONTH) + 1); } }, TKN_DATE("DATE") { @Override void do_method(RpnCalculator c, State s) { c.push(c.getCalendarField(c.pop(), Calendar.DAY_OF_MONTH)); } }, TKN_HOUR("HOUR") { @Override void do_method(RpnCalculator c, State s) { c.push(c.getCalendarField(c.pop(), Calendar.HOUR_OF_DAY)); } }, TKN_MINUTE("MINUTE") { @Override void do_method(RpnCalculator c, State s) { c.push(c.getCalendarField(c.pop(), Calendar.MINUTE)); } }, TKN_SECOND("SECOND") { @Override void do_method(RpnCalculator c, State s) { c.push(c.getCalendarField(c.pop(), Calendar.SECOND)); } }, TKN_WEEK("WEEK") { @Override void do_method(RpnCalculator c, State s) { c.push(c.getCalendarField(c.pop(), Calendar.WEEK_OF_YEAR)); } }, TKN_SIGN("SIGN") { @Override void do_method(RpnCalculator c, State s) { double x1 = c.pop(); c.push(Double.isNaN(x1) ? Double.NaN : x1 > 0 ? +1 : x1 < 0 ? -1 : 0); } }, TKN_SORT("SORT") { @Override void do_method(RpnCalculator c, State s) { int n = (int) c.pop(); double[] array = new double[n]; for(int i = 0; i < n; i++) { array[i] = c.pop(); } Arrays.sort(array); for (int i = 0; i < n; i++) { c.push(array[i]); } } }, TKN_REV("REV") { @Override void do_method(RpnCalculator c, State s) { int n = (int) c.pop(); double[] array = new double[n]; for(int i = 0; i < n; i++) { array[i] = c.pop(); } for (int i = 0; i < n; i++) { c.push(array[i]); } } }, TKN_AVG("AVG"){ @Override void do_method(RpnCalculator c, State s) { int count = 0; int n = (int) c.pop(); double sum = 0.0; while (n > 0) { double x1 = c.pop(); n--; if (Double.isNaN(x1)) { continue; } sum += x1; count++; } if (count > 0) { c.push(sum / count); } else { c.push(Double.NaN); } } }, TKN_COUNT("COUNT") { @Override void do_method(RpnCalculator c, State s) { c.push(s.slot+1); } }, TKN_TREND("TREND") { @Override void do_method(RpnCalculator c, State s) { int dur = (int) c.pop(); c.pop(); /* * OK, so to match the output from rrdtool, we have to go *forward* 2 timeperiods. * So at t[59] we use the average of t[1]..t[61] * */ if ((s.slot+1) < Math.ceil(dur / c.timeStep)) { c.push(Double.NaN); } else { double[] vals = c.dataProcessor.getValues(c.tokens[s.token_rpi].variable); boolean ignorenan = s.token.id == TKN_TRENDNAN; double accum = 0.0; int count = 0; int start = (int) (Math.ceil(dur / c.timeStep)); int row = 2; while ((s.slot + row) > vals.length) { row --; } for(; start > 0; start--) { double val = vals[s.slot + row - start]; if (ignorenan || !Double.isNaN(val)) { accum = Util.sum(accum, val); ++count; } } c.push((count == 0) ? Double.NaN : (accum / count)); } } }, TKN_TRENDNAN("TRENDNAN") { @Override void do_method(RpnCalculator c, State s) { TKN_TREND.do_method(c, s); } }, TKN_PREDICT("PREDICT") { @Override void do_method(RpnCalculator c, State s) { c.pop(); // Clear the value of our variable /* the local averaging window (similar to trend, but better here, as we get better statistics thru numbers)*/ int locstepsize = (int) c.pop(); /* the number of shifts and range-checking*/ int num_shifts = (int) c.pop(); double[] multipliers; // handle negative shifts special if (num_shifts < 0) { multipliers = new double[1]; multipliers[0] = c.pop(); } else { multipliers = new double[num_shifts]; for(int i = 0; i < num_shifts; i++) { multipliers[i] = c.pop(); } } /* the real calculation */ double val = Double.NaN; /* the info on the datasource */ double[] vals = c.dataProcessor.getValues(c.tokens[s.rpi-1].variable); int locstep = (int) Math.ceil((float) locstepsize / (float) c.timeStep); /* the sums */ double sum = 0; double sum2 = 0; int count = 0; /* now loop for each position */ int doshifts = Math.abs(num_shifts); for (int loop = 0; loop < doshifts; loop++) { /* calculate shift step */ int shiftstep; if (num_shifts < 0) { shiftstep = loop * (int) multipliers[0]; } else { shiftstep = (int) multipliers[loop]; } if (shiftstep < 0) { throw new RuntimeException("negative shift step not allowed: " + shiftstep); } shiftstep = (int) Math.ceil((float) shiftstep / (float) c.timeStep); /* loop all local shifts */ for (int i = 0; i <= locstep; i++) { int offset = shiftstep + i; if ((offset >= 0) && (offset < s.slot)) { /* get the value */ val = vals[s.slot - offset]; /* and handle the non NAN case only*/ if (!Double.isNaN(val)) { sum = Util.sum(sum, val); sum2 = Util.sum(sum2, val * val); count++; } } } } /* do the final calculations */ val = Double.NaN; if (s.token.id == TKN_PREDICT) { /* the average */ if (count > 0) { val = sum / (double) count; } } else { if (count > 1) { /* the sigma case */ val = count * sum2 - sum * sum; if (val < 0) { val = Double.NaN; } else { val = Math.sqrt(val / ((float) count * ((float) count - 1.0))); } } } c.push(val); } }, TKN_PREDICTSIGMA("PREDICTSIGMA") { @Override void do_method(RpnCalculator c, State s) { TKN_PREDICT.do_method(c, s); } }; public final String token_string; Token_Symbol(String token_string) { this.token_string = token_string; } abstract void do_method(RpnCalculator c, State s); } private static final Token_Symbol[] symbols; private static final PerfectStringHash perfect; static { List<String> tokenStrings = new ArrayList<String>(Token_Symbol.values().length); for(Token_Symbol s: Token_Symbol.values()) { if(! s.token_string.isEmpty()) { tokenStrings.add(s.token_string); } } String[] array = tokenStrings.toArray(new String[tokenStrings.size()]); perfect = new PerfectStringHash(array); symbols = new Token_Symbol[tokenStrings.size()]; for(Token_Symbol s: Token_Symbol.values()) { int hash = perfect.hashAsInt(s.token_string); if(hash >= 0) { symbols[hash] = s; } } } private final String rpnExpression; private final String sourceName; private final DataProcessor dataProcessor; private final Token[] tokens; private final RpnStack stack = new RpnStack(); private final double[] calculatedValues; private final long[] timestamps; private final double timeStep; private final List<String> sourcesNames; RpnCalculator(String rpnExpression, String sourceName, DataProcessor dataProcessor) { this.rpnExpression = rpnExpression; this.sourceName = sourceName; this.dataProcessor = dataProcessor; this.timestamps = dataProcessor.getTimestamps(); this.timeStep = this.timestamps[1] - this.timestamps[0]; this.calculatedValues = new double[this.timestamps.length]; this.sourcesNames = Arrays.asList(dataProcessor.getSourceNames()); String[] tokensString = rpnExpression.split(" *, *"); tokens = new Token[tokensString.length]; for (int i = 0; i < tokensString.length; i++) { tokens[i] = createToken(tokensString[i].trim()); } } private Token createToken(String parsedText) { Token token; int hash = perfect.hashAsInt(parsedText); if (hash >= 0 ){ token = new Token(symbols[hash]); } else if (parsedText.equals("PREV")) { token = new Token(Token_Symbol.TKN_PREV, sourceName, calculatedValues); } else if (parsedText.startsWith("PREV(") && parsedText.endsWith(")")) { String variable = parsedText.substring(5, parsedText.length() - 1); token = new Token(Token_Symbol.TKN_PREV, variable, dataProcessor.getValues(variable)); } else if (Util.isDouble(parsedText)) { token = new Token(Token_Symbol.TKN_NUM, Util.parseDouble(parsedText)); } else if (sourcesNames.contains(parsedText)){ token = new Token(Token_Symbol.TKN_VAR, parsedText, dataProcessor.getValues(parsedText)); } else { throw new IllegalArgumentException("Unexpected RPN token encountered: " + parsedText); } return token; } double[] calculateValues() { State s = new State(); for (int slot = 0; slot < timestamps.length; slot++) { resetStack(); s.rpi = 0; s.token_rpi = -1; for (Token token: tokens) { s.token = token; s.slot = slot; token.id.do_method(this, s); s.rpi++; } calculatedValues[slot] = pop(); // check if stack is empty only on the first try if (slot == 0 && !isStackEmpty()) { throw new IllegalArgumentException("Stack not empty at the end of calculation. " + "Probably bad RPN expression [" + rpnExpression + "]"); } } return calculatedValues; } private double getCalendarField(double timestamp, int field) { Calendar calendar = Util.getCalendar((long) (timestamp)); return calendar.get(field); } private void push(final double x) { stack.push(x); } private double pop() { return stack.pop(); } private double peek() { return stack.peek(); } private void resetStack() { stack.reset(); } private boolean isStackEmpty() { return stack.isEmpty(); } private static final class RpnStack { private static final int MAX_STACK_SIZE = 1000; private double[] stack = new double[MAX_STACK_SIZE]; private int pos = 0; void push(double x) { if (pos >= MAX_STACK_SIZE) { throw new IllegalArgumentException("PUSH failed, RPN stack full [" + MAX_STACK_SIZE + "]"); } stack[pos++] = x; } double pop() { if (pos <= 0) { throw new IllegalArgumentException("POP failed, RPN stack is empty"); } return stack[--pos]; } double peek() { if (pos <= 0) { throw new IllegalArgumentException("PEEK failed, RPN stack is empty"); } return stack[pos - 1]; } void reset() { pos = 0; } boolean isEmpty() { return pos <= 0; } } private final class State { public int token_rpi; int rpi; Token token; int slot; TimeZone getTimeZone() { return RpnCalculator.this.dataProcessor.getTimeZone(); } } private static final class Token { final Token_Symbol id; final double number; final String variable; final double[] values; Token(Token_Symbol id) { this.id = id; this.values = null; this.variable = ""; this.number = Double.NaN; } Token(Token_Symbol id, String variable, double[] values) { this.id = id; this.variable = variable; this.values = values; this.number = Double.NaN; } Token(Token_Symbol id, double number) { this.id = id; this.values = null; this.variable = ""; this.number = number; } } }