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 used.<br>
51 * The map use (LinkedHashMap) is an insertion-ordered map, so the first interval matching a query is used.
53 // CHECKSTYLE:OFF: checkstyle:magicNumber
54 private static final Map<double[], String> TASK_INTERVALS = new LinkedHashMap<>();
56 TASK_INTERVALS.put(new double[] { 0.0, 0.1 }, null); // null will mean default task
57 TASK_INTERVALS.put(new double[] { 0.25, 0.5 }, "AnomalyDetectionDecideTask1");
58 TASK_INTERVALS.put(new double[] { 0.5, 1.01 }, "AnomalyDetectionDecideTask2");
60 // CHECKSTYLE:ON: checkstyle:magicNumber
62 private volatile TaskSelectionExecutionContext executionContext;
67 * @param executor the executor
70 public boolean getTask(final TaskSelectionExecutionContext executor) {
71 executionContext = executor;
72 logger = executionContext.logger;
73 logger.debug(executor.subject.getId());
74 logger.debug(executor.inFields.toString());
75 final double now = (Double) (executor.inFields.get("MonitoredValue"));
76 final Integer iteration = (Integer) (executor.inFields.get("Iteration"));
77 final double[] vals = this.forecastingAndAnomaly(now); // double[forecastedValue, AnomalyScore,
78 // AnomalyProbability]
79 final double anomalyness = vals[2];
81 for (final Map.Entry<double[], String> i : TASK_INTERVALS.entrySet()) {
82 if (checkInterval(anomalyness, i.getKey())) {
88 executionContext.subject.getDefaultTaskKey().copyTo(executionContext.selectedTask);
90 executionContext.subject.getTaskKey(task).copyTo(executionContext.selectedTask);
92 if (logger.isDebugEnabled()) {
94 "TestAnomalyDetectionTSLPolicy0000DecideStateTaskSelectionLogic.getTask():\t************\t\t\t\t"
95 + "Iteration:\t" + iteration + "\tValue:\t" + now + "\tForecast:\t" + vals[0]
96 + "\tAnomalyScore:\t" + vals[1] + "\tAnomalyProbability:\t" + vals[2] + "\tInvoking Task:\t"
97 + executionContext.selectedTask);
103 * Anomaly detection and forecast.
105 * @param value The current value
106 * @return Null if the function can not be executed correctly, otherwise double[forecastedValue, AnomalyScore,
107 * AnomalyProbability]
109 public double[] forecastingAndAnomaly(final double value) {
111 executionContext.getContextAlbum("AnomalyDetectionAlbum").lockForWriting("AnomalyDetection");
112 } catch (final ApexException e) {
113 logger.error("Failed to acquire write lock on \"AnomalyDetection\" context", e);
117 // Get the context object
118 AnomalyDetection anomalyDetection =
119 (AnomalyDetection) executionContext.getContextAlbum("AnomalyDetectionAlbum").get("AnomalyDetection");
120 if (anomalyDetection == null) {
121 anomalyDetection = new AnomalyDetection();
122 executionContext.getContextAlbum("AnomalyDetectionAlbum").put("AnomalyDetection", anomalyDetection);
125 // Check the lists are initialized
126 if (!anomalyDetection.isInitialized()) {
127 anomalyDetection.init(FREQUENCY);
130 boolean unsetfirstround = false;
132 int frequency = anomalyDetection.getFrequency();
133 frequency = frequency + 1;
135 // reset frequency counter
136 if (frequency >= FREQUENCY) {
137 unsetfirstround = true;
140 anomalyDetection.setFrequency(frequency);
142 if (unsetfirstround && anomalyDetection.getFirstRound()) {
143 anomalyDetection.setFirstRound(false);
146 // --------- calculate the forecasted value - simple version
147 final Double lastForecast = anomalyDetection.getFrequencyForecasted().get(frequency);
149 // get forecast for current value
150 final double forecastedValue = lastForecast == null ? value : expMovingAverage(value, lastForecast);
152 // --------- calculate the anomalyScore
153 final double anomalyScore = lastForecast == null ? 0.0 : FastMath.abs(lastForecast - value);
155 anomalyDetection.getFrequencyForecasted().set(frequency, forecastedValue);
157 // anomaly score is ignored in the first frequency period
158 if (!anomalyDetection.getFirstRound()) {
159 ((LinkedList<Double>) anomalyDetection.getAnomalyScores()).addLast(anomalyScore);
162 // CHECKSTYLE:OFF: checkstyle:magicNumber
163 // max FREQUENCY*4 anomaly scores history
164 listSizeControl(anomalyDetection.getAnomalyScores(), FREQUENCY * 4);
166 // ---------- calculate the anomaly probability
167 double anomalyProbability = 0.0;
168 if (anomalyDetection.getAnomalyScores().size() > 30) {
170 anomalyProbability = gStatsTest(anomalyDetection.getAnomalyScores(), ANOMALY_SENSITIVITY);
172 // CHECKSTYLE:ON: checkstyle:magicNumber
175 executionContext.getContextAlbum("AnomalyDetectionAlbum").unlockForWriting("AnomalyDetection");
176 } catch (final ApexException e) {
177 logger.error("Failed to release write lock on \"AnomalyDetection\" context", e);
181 return new double[] { forecastedValue, anomalyScore, anomalyProbability };
185 * Is the passed value inside the interval, i.e. (value<interval[1] && value>=interval[0])
187 * @param value The value to check
188 * @param interval A 2 element double array describing an interval
189 * @return true if the value is between interval[0] (inclusive) and interval[1] (exclusive), i.e. (value<interval[1]
190 * && value>=interval[0]). Otherwise false;
192 private static boolean checkInterval(final double value, final double[] interval) {
193 if (interval == null || interval.length != 2) {
194 throw new IllegalArgumentException("something other than an interval passed to checkInterval");
196 final double min = interval[0];
197 final double max = interval[1];
198 return (value < max && value >= min);
202 * calculate the anomaly probability using statistical test.
204 * @param values the values
205 * @param significanceLevel the significance level
208 private static double gStatsTest(final List<Double> values, final double significanceLevel) {
209 if (isAllEqual(values)) {
212 // the targeted value or the last value
213 final double currentV = values.get(values.size() - 1);
214 Double[] lValuesCopy = values.toArray(new Double[values.size()]);
215 Arrays.sort(lValuesCopy); // takes ~40% of method time
216 // if(logger.isDebugEnabled()){
217 // logger.debug("values:" + Arrays.toString(lValuesCopy));
220 double mean = getMean(lValuesCopy);
221 // get the test value: v
222 double v = getV(lValuesCopy, mean, true);
223 // get the p value for the test value
224 double pValue = getPValue(lValuesCopy, v, mean); // takes approx 25% of method time
225 // if(logger.isDebugEnabled()){
226 // logger.debug("pValue:" + pValue);
229 // check the critical level
230 while (pValue < significanceLevel) { // takes approx 20% of method time
231 // the score value as the anomaly probability
232 final double score = (significanceLevel - pValue) / significanceLevel;
233 if (Double.compare(v, currentV) == 0) {
236 // do the critical check again for the left values
237 lValuesCopy = removevalue(lValuesCopy, v);
238 if (isAllEqual(lValuesCopy)) {
241 // if(logger.isDebugEnabled()){
242 // logger.debug("left values:" + Arrays.toString(lValuesCopy));
244 mean = getMean(lValuesCopy);
245 v = getV(lValuesCopy, mean, true);
246 pValue = getPValue(lValuesCopy, v, mean);
252 * get the test value based on mean from sorted values.
254 * @param lValues the l values
255 * @param mean the mean
256 * @param maxValueOnly : only the max extreme value will be tested
257 * @return the value to be tested
259 private static double getV(final Double[] lValues, final double mean, final boolean maxValueOnly) {
260 double v = lValues[lValues.length - 1];
261 // max value as the extreme value
265 // check the extreme side
266 if ((v - mean) < (mean - lValues[0])) {
273 * calculate the P value for the t distribution.
275 * @param lValues the l values
277 * @param mean the mean
278 * @return the p value
280 private static double getPValue(final Double[] lValues, final double v, final double mean) {
282 final double z = FastMath.abs(v - mean) / getStdDev(lValues, mean);
283 // logger.debug("z: " + z);
285 final double n = lValues.length;
286 final double s = (z * z * n * (2.0 - n)) / (z * z * n - (n - 1.0) * (n - 1.0));
287 final double t = FastMath.sqrt(s);
288 // logger.debug("t:" + t);
289 // default p value = 0
291 if (!Double.isNaN(t)) {
292 // t distribution with n-2 degrees of freedom
293 final TDistribution tDist = new TDistribution(n - 2);
294 pValue = n * (1.0 - tDist.cumulativeProbability(t));
295 // set max pValue = 1
296 pValue = pValue > 1.0 ? 1.0 : pValue;
298 // logger.debug("value: "+ v + " , pValue: " + pValue);
303 * Some utility methods
305 // exponential = 2(n+1)
306 private static final double EMA_EXPONENT = 2.0 / (7.0 + 1.0);
307 private static final double EMA_EXPONENT_1 = (1.0 - EMA_EXPONENT);
310 * exponential moving average.
312 * @param value the value
313 * @param lastForecast the last forecast
316 private static double expMovingAverage(final double value, final double lastForecast) {
317 return (value * EMA_EXPONENT) + (lastForecast * EMA_EXPONENT_1);
321 * Remove the first occurence of the value v from the array.
323 * @param lValues the l values
325 * @return the double[]
327 private static Double[] removevalue(final Double[] lValues, final double v) {
328 for (int i = 0; i < lValues.length; i++) {
329 if (Double.compare(lValues[i], v) == 0) {
330 final Double[] ret = new Double[lValues.length - 1];
331 System.arraycopy(lValues, 0, ret, 0, i);
332 System.arraycopy(lValues, i + 1, ret, i, lValues.length - i - 1);
340 * get mean value of double list.
342 * @param lValues the l values
345 private static double getMean(final Double[] lValues) {
347 for (final double d : lValues) {
351 return sum / lValues.length;
355 * get standard deviation of double list.
357 * @param lValues the l values
358 * @param mean the mean
361 private static double getStdDev(final Double[] lValues, final double mean) {
363 for (final double d : lValues) {
364 temp += (mean - d) * (mean - d);
366 return FastMath.sqrt(temp / lValues.length);
370 * Chop head off list to make it length max .
372 * @param list the list to chop
373 * @param max the max size
375 private static void listSizeControl(final List<?> list, final int max) {
376 final int k = list.size();
378 // Chop the head off the list.
379 list.subList(0, k - max).clear();
384 * return true if all values are equal.
386 * @param lValues the l values
387 * @return true, if checks if is all equal
389 private static boolean isAllEqual(final List<Double> lValues) {
390 final double first = lValues.get(0);
391 for (final Double d : lValues) {
392 if (Double.compare(d, first) != 0) {
400 * return true if all values are equal.
402 * @param lValues the l values
403 * @return true, if checks if is all equal
405 private static boolean isAllEqual(final Double[] lValues) {
406 final double first = lValues[0];
407 for (final Double d : lValues) {
408 if (Double.compare(d, first) != 0) {