2 * ============LICENSE_START=======================================================
3 * Copyright (C) 2016-2018 Ericsson. All rights reserved.
4 * ================================================================================
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
9 * http://www.apache.org/licenses/LICENSE-2.0
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
17 * SPDX-License-Identifier: Apache-2.0
18 * ============LICENSE_END=========================================================
21 package org.onap.policy.apex.examples.adaptive.model.java;
23 import java.util.Arrays;
24 import java.util.LinkedHashMap;
25 import java.util.LinkedList;
26 import java.util.List;
29 import org.apache.commons.math3.distribution.TDistribution;
30 import org.apache.commons.math3.util.FastMath;
31 import org.onap.policy.apex.core.engine.executor.context.TaskSelectionExecutionContext;
32 import org.onap.policy.apex.examples.adaptive.concepts.AnomalyDetection;
33 import org.onap.policy.apex.model.basicmodel.concepts.ApexException;
34 import org.slf4j.Logger;
37 * The Class AnomalyDetectionPolicy_Decide_TaskSelectionLogic.
39 // CHECKSTYLE:OFF: checkstyle:className
40 public class AnomalyDetectionPolicy_Decide_TaskSelectionLogic {
41 // CHECKSTYLE:ON: checkstyle:className
43 private Logger logger;
45 private static final double ANOMALY_SENSITIVITY = 0.05;
46 private static final int FREQUENCY = 360;
49 * A map to hold the Anomaly degree/levels/probabilities required for each task.<br>
50 * If there is no task defined for a calculated anomaly-degree, then the default task is
52 * The map use (LinkedHashMap) is an insertion-ordered map, so the first interval matching a
55 // CHECKSTYLE:OFF: checkstyle:magicNumber
56 private static final Map<double[], String> TASK_INTERVALS = new LinkedHashMap<>();
59 TASK_INTERVALS.put(new double[] {0.0, 0.1}, null); // null will mean default task
60 TASK_INTERVALS.put(new double[] {0.25, 0.5}, "AnomalyDetectionDecideTask1");
61 TASK_INTERVALS.put(new double[] {0.5, 1.01}, "AnomalyDetectionDecideTask2");
63 // CHECKSTYLE:ON: checkstyle:magicNumber
65 private volatile TaskSelectionExecutionContext executionContext;
70 * @param executor the executor
73 public boolean getTask(final TaskSelectionExecutionContext executor) {
74 executionContext = executor;
75 logger = executionContext.logger;
76 logger.debug(executor.subject.getId());
77 logger.debug(executor.inFields.toString());
78 final double now = (Double) (executor.inFields.get("MonitoredValue"));
79 final Integer iteration = (Integer) (executor.inFields.get("Iteration"));
80 // get the double[forecastedValue, AnomalyScore, AnomalyProbability]
81 final double[] vals = this.forecastingAndAnomaly(now);
82 final double anomalyness = vals[2];
84 for (final Map.Entry<double[], String> i : TASK_INTERVALS.entrySet()) {
85 if (checkInterval(anomalyness, i.getKey())) {
91 executionContext.subject.getDefaultTaskKey().copyTo(executionContext.selectedTask);
93 executionContext.subject.getTaskKey(task).copyTo(executionContext.selectedTask);
95 if (logger.isDebugEnabled()) {
97 "TestAnomalyDetectionTSLPolicy0000DecideStateTaskSelectionLogic.getTask():\t************\t\t\t\t"
98 + "Iteration:\t" + iteration + "\tValue:\t" + now + "\tForecast:\t" + vals[0]
99 + "\tAnomalyScore:\t" + vals[1] + "\tAnomalyProbability:\t" + vals[2] + "\tInvoking Task:\t"
100 + executionContext.selectedTask);
106 * Anomaly detection and forecast.
108 * @param value The current value
109 * @return Null if the function can not be executed correctly, otherwise double[forecastedValue,
110 * AnomalyScore, AnomalyProbability]
112 public double[] forecastingAndAnomaly(final double value) {
114 executionContext.getContextAlbum("AnomalyDetectionAlbum").lockForWriting("AnomalyDetection");
115 } catch (final ApexException e) {
116 logger.error("Failed to acquire write lock on \"AnomalyDetection\" context", e);
120 // Get the context object
121 AnomalyDetection anomalyDetection =
122 (AnomalyDetection) executionContext.getContextAlbum("AnomalyDetectionAlbum").get("AnomalyDetection");
123 if (anomalyDetection == null) {
124 anomalyDetection = new AnomalyDetection();
125 executionContext.getContextAlbum("AnomalyDetectionAlbum").put("AnomalyDetection", anomalyDetection);
128 // Check the lists are initialized
129 if (!anomalyDetection.isInitialized()) {
130 anomalyDetection.init(FREQUENCY);
133 boolean unsetfirstround = false;
135 int frequency = anomalyDetection.getFrequency();
136 frequency = frequency + 1;
138 // reset frequency counter
139 if (frequency >= FREQUENCY) {
140 unsetfirstround = true;
143 anomalyDetection.setFrequency(frequency);
145 if (unsetfirstround && anomalyDetection.getFirstRound()) {
146 anomalyDetection.setFirstRound(false);
149 // --------- calculate the forecasted value - simple version
150 final Double lastForecast = anomalyDetection.getFrequencyForecasted().get(frequency);
152 // get forecast for current value
153 final double forecastedValue = lastForecast == null ? value : expMovingAverage(value, lastForecast);
155 // --------- calculate the anomalyScore
156 final double anomalyScore = lastForecast == null ? 0.0 : FastMath.abs(lastForecast - value);
158 anomalyDetection.getFrequencyForecasted().set(frequency, forecastedValue);
160 // anomaly score is ignored in the first frequency period
161 if (!anomalyDetection.getFirstRound()) {
162 ((LinkedList<Double>) anomalyDetection.getAnomalyScores()).addLast(anomalyScore);
165 // CHECKSTYLE:OFF: checkstyle:magicNumber
166 // max FREQUENCY*4 anomaly scores history
167 listSizeControl(anomalyDetection.getAnomalyScores(), FREQUENCY * 4);
169 // ---------- calculate the anomaly probability
170 double anomalyProbability = 0.0;
171 if (anomalyDetection.getAnomalyScores().size() > 30) {
173 anomalyProbability = getStatsTest(anomalyDetection.getAnomalyScores(), ANOMALY_SENSITIVITY);
175 // CHECKSTYLE:ON: checkstyle:magicNumber
178 executionContext.getContextAlbum("AnomalyDetectionAlbum").unlockForWriting("AnomalyDetection");
179 } catch (final ApexException e) {
180 logger.error("Failed to release write lock on \"AnomalyDetection\" context", e);
184 return new double[] {forecastedValue, anomalyScore, anomalyProbability};
188 * Is the passed value inside the interval, i.e. (value < interval[1] && value>=interval[0]).
190 * @param value The value to check
191 * @param interval A 2 element double array describing an interval
192 * @return true if the value is between interval[0] (inclusive) and interval[1] (exclusive),
193 * i.e. (value < interval[1] && value>=interval[0]). Otherwise false;
195 private static boolean checkInterval(final double value, final double[] interval) {
196 if (interval == null || interval.length != 2) {
197 throw new IllegalArgumentException("something other than an interval passed to checkInterval");
199 final double min = interval[0];
200 final double max = interval[1];
201 return (value < max && value >= min);
205 * calculate the anomaly probability using statistical test.
207 * @param values the values
208 * @param significanceLevel the significance level
209 * @return the anomaly probability
211 private static double getStatsTest(final List<Double> values, final double significanceLevel) {
212 if (isAllEqual(values)) {
215 // the targeted value or the last value
216 final double currentV = values.get(values.size() - 1);
217 Double[] lvaluesCopy = values.toArray(new Double[values.size()]);
218 Arrays.sort(lvaluesCopy); // takes ~40% of method time
220 double mean = getMean(lvaluesCopy);
221 // get the test value: val
222 double val = getV(lvaluesCopy, mean, true);
223 // get the p value for the test value
224 double pvalue = getPValue(lvaluesCopy, val, mean); // takes approx 25% of method time
226 // check the critical level
227 while (pvalue < significanceLevel) { // takes approx 20% of method time
228 // the score value as the anomaly probability
229 final double score = (significanceLevel - pvalue) / significanceLevel;
230 if (Double.compare(val, currentV) == 0) {
233 // do the critical check again for the left values
234 lvaluesCopy = removevalue(lvaluesCopy, val);
235 if (isAllEqual(lvaluesCopy)) {
239 mean = getMean(lvaluesCopy);
240 val = getV(lvaluesCopy, mean, true);
241 pvalue = getPValue(lvaluesCopy, val, mean);
247 * Get the test value based on mean from sorted values.
249 * @param lvalues the l values
250 * @param mean the mean
251 * @param maxValueOnly : only the max extreme value will be tested
252 * @return the value to be tested
254 private static double getV(final Double[] lvalues, final double mean, final boolean maxValueOnly) {
255 double val = lvalues[lvalues.length - 1];
256 // max value as the extreme value
260 // check the extreme side
261 if ((val - mean) < (mean - lvalues[0])) {
268 * calculate the P value for the t distribution.
270 * @param lvalues the l values
271 * @param val the value
272 * @param mean the mean
273 * @return the p value
275 private static double getPValue(final Double[] lvalues, final double val, final double mean) {
277 final double z = FastMath.abs(val - mean) / getStdDev(lvalues, mean);
279 final double n = lvalues.length;
280 final double s = (z * z * n * (2.0 - n)) / (z * z * n - (n - 1.0) * (n - 1.0));
281 final double t = FastMath.sqrt(s);
282 // default p value = 0
284 if (!Double.isNaN(t)) {
285 // t distribution with n-2 degrees of freedom
286 final TDistribution tDist = new TDistribution(n - 2);
287 pvalue = n * (1.0 - tDist.cumulativeProbability(t));
288 // set max pvalue = 1
289 pvalue = pvalue > 1.0 ? 1.0 : pvalue;
295 * Some utility methods
297 // exponential = 2(n+1)
298 private static final double EMA_EXPONENT = 2.0 / (7.0 + 1.0);
299 private static final double EMA_EXPONENT_1 = (1.0 - EMA_EXPONENT);
302 * exponential moving average.
304 * @param value the value
305 * @param lastForecast the last forecast
308 private static double expMovingAverage(final double value, final double lastForecast) {
309 return (value * EMA_EXPONENT) + (lastForecast * EMA_EXPONENT_1);
313 * Remove the first occurrence of the value val from the array.
315 * @param lvalues the l values
316 * @param val the value
317 * @return the double[]
319 private static Double[] removevalue(final Double[] lvalues, final double val) {
320 for (int i = 0; i < lvalues.length; i++) {
321 if (Double.compare(lvalues[i], val) == 0) {
322 final Double[] ret = new Double[lvalues.length - 1];
323 System.arraycopy(lvalues, 0, ret, 0, i);
324 System.arraycopy(lvalues, i + 1, ret, i, lvalues.length - i - 1);
332 * get mean value of double list.
334 * @param lvalues the l values
337 private static double getMean(final Double[] lvalues) {
339 for (final double d : lvalues) {
343 return sum / lvalues.length;
347 * get standard deviation of double list.
349 * @param lvalues the l values
350 * @param mean the mean
353 private static double getStdDev(final Double[] lvalues, final double mean) {
355 for (final double d : lvalues) {
356 temp += (mean - d) * (mean - d);
358 return FastMath.sqrt(temp / lvalues.length);
362 * Chop head off list to make it length max .
364 * @param list the list to chop
365 * @param max the max size
367 private static void listSizeControl(final List<?> list, final int max) {
368 final int k = list.size();
370 // Chop the head off the list.
371 list.subList(0, k - max).clear();
376 * return true if all values are equal.
378 * @param lvalues the l values
379 * @return true, if checks if is all equal
381 private static boolean isAllEqual(final List<Double> lvalues) {
382 final double first = lvalues.get(0);
383 for (final Double d : lvalues) {
384 if (Double.compare(d, first) != 0) {
392 * return true if all values are equal.
394 * @param lvalues the l values
395 * @return true, if checks if is all equal
397 private static boolean isAllEqual(final Double[] lvalues) {
398 final double first = lvalues[0];
399 for (final Double d : lvalues) {
400 if (Double.compare(d, first) != 0) {