/* * Licensed to Elasticsearch under one or more contributor * license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright * ownership. Elasticsearch licenses this file to you under * the Apache License, Version 2.0 (the "License"); you may * not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.elasticsearch.search.aggregations.pipeline.movavg.models; import org.elasticsearch.common.Nullable; import org.elasticsearch.common.io.stream.NamedWriteable; import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.xcontent.ToXContent; import java.io.IOException; import java.text.ParseException; import java.util.Arrays; import java.util.Collection; import java.util.Map; public abstract class MovAvgModel implements NamedWriteable, ToXContent { /** * Should this model be fit to the data via a cost minimizing algorithm by default? */ public boolean minimizeByDefault() { return false; } /** * Returns if the model can be cost minimized. Not all models have parameters * which can be tuned / optimized. */ public abstract boolean canBeMinimized(); /** * Generates a "neighboring" model, where one of the tunable parameters has been * randomly mutated within the allowed range. Used for minimization */ public abstract MovAvgModel neighboringModel(); /** * Checks to see this model can produce a new value, without actually running the algo. * This can be used for models that have certain preconditions that need to be met in order * to short-circuit execution * * @param valuesAvailable Number of values in the current window of values * @return Returns `true` if calling next() will produce a value, `false` otherwise */ public boolean hasValue(int valuesAvailable) { // Default implementation can always provide a next() value return valuesAvailable > 0; } /** * Returns the next value in the series, according to the underlying smoothing model * * @param values Collection of numerics to movingAvg, usually windowed * @param <T> Type of numeric * @return Returns a double, since most smoothing methods operate on floating points */ public abstract <T extends Number> double next(Collection<T> values); /** * Predicts the next `n` values in the series. * * @param values Collection of numerics to movingAvg, usually windowed * @param numPredictions Number of newly generated predictions to return * @param <T> Type of numeric * @return Returns an array of doubles, since most smoothing methods operate on floating points */ public <T extends Number> double[] predict(Collection<T> values, int numPredictions) { assert(numPredictions >= 1); // If there are no values, we can't do anything. Return an array of NaNs. if (values.isEmpty()) { return emptyPredictions(numPredictions); } return doPredict(values, numPredictions); } /** * Calls to the model-specific implementation which actually generates the predictions * * @param values Collection of numerics to movingAvg, usually windowed * @param numPredictions Number of newly generated predictions to return * @param <T> Type of numeric * @return Returns an array of doubles, since most smoothing methods operate on floating points */ protected abstract <T extends Number> double[] doPredict(Collection<T> values, int numPredictions); /** * Returns an empty set of predictions, filled with NaNs * @param numPredictions Number of empty predictions to generate */ protected double[] emptyPredictions(int numPredictions) { double[] predictions = new double[numPredictions]; Arrays.fill(predictions, Double.NaN); return predictions; } /** * Write the model to the output stream * * @param out Output stream */ @Override public abstract void writeTo(StreamOutput out) throws IOException; /** * Clone the model, returning an exact copy */ @Override public abstract MovAvgModel clone(); @Override public abstract int hashCode(); @Override public abstract boolean equals(Object obj); /** * Abstract class which also provides some concrete parsing functionality. */ public abstract static class AbstractModelParser { /** * Parse a settings hash that is specific to this model * * @param settings Map of settings, extracted from the request * @param pipelineName Name of the parent pipeline agg * @param windowSize Size of the window for this moving avg * @return A fully built moving average model */ public abstract MovAvgModel parse(@Nullable Map<String, Object> settings, String pipelineName, int windowSize) throws ParseException; /** * Extracts a 0-1 inclusive double from the settings map, otherwise throws an exception * * @param settings Map of settings provided to this model * @param name Name of parameter we are attempting to extract * @param defaultValue Default value to be used if value does not exist in map * @return Double value extracted from settings map */ protected double parseDoubleParam(@Nullable Map<String, Object> settings, String name, double defaultValue) throws ParseException { if (settings == null) { return defaultValue; } Object value = settings.get(name); if (value == null) { return defaultValue; } else if (value instanceof Number) { double v = ((Number) value).doubleValue(); if (v >= 0 && v <= 1) { settings.remove(name); return v; } throw new ParseException("Parameter [" + name + "] must be between 0-1 inclusive. Provided" + "value was [" + v + "]", 0); } throw new ParseException("Parameter [" + name + "] must be a double, type `" + value.getClass().getSimpleName() + "` provided instead", 0); } /** * Extracts an integer from the settings map, otherwise throws an exception * * @param settings Map of settings provided to this model * @param name Name of parameter we are attempting to extract * @param defaultValue Default value to be used if value does not exist in map * @return Integer value extracted from settings map */ protected int parseIntegerParam(@Nullable Map<String, Object> settings, String name, int defaultValue) throws ParseException { if (settings == null) { return defaultValue; } Object value = settings.get(name); if (value == null) { return defaultValue; } else if (value instanceof Number) { settings.remove(name); return ((Number) value).intValue(); } throw new ParseException("Parameter [" + name + "] must be an integer, type `" + value.getClass().getSimpleName() + "` provided instead", 0); } /** * Extracts a boolean from the settings map, otherwise throws an exception * * @param settings Map of settings provided to this model * @param name Name of parameter we are attempting to extract * @param defaultValue Default value to be used if value does not exist in map * @return Boolean value extracted from settings map */ protected boolean parseBoolParam(@Nullable Map<String, Object> settings, String name, boolean defaultValue) throws ParseException { if (settings == null) { return defaultValue; } Object value = settings.get(name); if (value == null) { return defaultValue; } else if (value instanceof Boolean) { settings.remove(name); return (Boolean)value; } throw new ParseException("Parameter [" + name + "] must be a boolean, type `" + value.getClass().getSimpleName() + "` provided instead", 0); } protected void checkUnrecognizedParams(@Nullable Map<String, Object> settings) throws ParseException { if (settings != null && settings.size() > 0) { throw new ParseException("Unrecognized parameter(s): [" + settings.keySet() + "]", 0); } } } }