summaryrefslogtreecommitdiff
path: root/core/src/main/java/org/elasticsearch/search/aggregations/pipeline/movavg/models/MovAvgModel.java
blob: 4bfac9d44cbf7ad64f8c2375311affabc88ce29e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
/*
 * 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.ParseFieldMatcher;
import org.elasticsearch.common.io.stream.StreamOutput;

import java.io.IOException;
import java.text.ParseException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Map;

public abstract class MovAvgModel {

    /**
     * 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
     */
    public abstract void writeTo(StreamOutput out) throws IOException;

    /**
     * Clone the model, returning an exact copy
     */
    public abstract MovAvgModel clone();

    /**
     * Abstract class which also provides some concrete parsing functionality.
     */
    public abstract static class AbstractModelParser {

        /**
         * Returns the name of the model
         *
         * @return The model's name
         */
        public abstract String getName();

        /**
         * 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
         * @param parseFieldMatcher  Matcher for field names
         * @return                   A fully built moving average model
         */
        public abstract MovAvgModel parse(@Nullable Map<String, Object> settings, String pipelineName,
                                          int windowSize, ParseFieldMatcher parseFieldMatcher) 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);
            }
        }
    }

}