This is an automated email from the ASF dual-hosted git repository.
spricoder pushed a commit to branch research/seasonal-repair
in repository https://gitbox.apache.org/repos/asf/iotdb.git
The following commit(s) were added to refs/heads/research/seasonal-repair by
this push:
new ba9fc78d8e4 Add UDF "UDTFSeasonalRepair" to the research branch
"research/seasonal-repair" (#12671)
ba9fc78d8e4 is described below
commit ba9fc78d8e4226fd4491b673df9621068aca92cd
Author: Zane Chen <[email protected]>
AuthorDate: Thu Jun 6 10:54:15 2024 +0800
Add UDF "UDTFSeasonalRepair" to the research branch
"research/seasonal-repair" (#12671)
---
.../iotdb/library/drepair/UDTFSeasonalRepair.java | 149 ++++++++++++++++
.../iotdb/library/drepair/util/Decomposition.java | 130 ++++++++++++++
.../iotdb/library/drepair/util/LinearMedian.java | 130 ++++++++++++++
.../iotdb/library/drepair/util/MovingMedian.java | 187 +++++++++++++++++++++
.../iotdb/library/drepair/util/SeasonalRepair.java | 134 +++++++++++++++
5 files changed, 730 insertions(+)
diff --git
a/library-udf/src/main/java/org/apache/iotdb/library/drepair/UDTFSeasonalRepair.java
b/library-udf/src/main/java/org/apache/iotdb/library/drepair/UDTFSeasonalRepair.java
new file mode 100644
index 00000000000..8861dae26b9
--- /dev/null
+++
b/library-udf/src/main/java/org/apache/iotdb/library/drepair/UDTFSeasonalRepair.java
@@ -0,0 +1,149 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF 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.apache.iotdb.library.drepair;
+
+import org.apache.iotdb.library.drepair.util.SeasonalRepair;
+import org.apache.iotdb.udf.api.UDTF;
+import org.apache.iotdb.udf.api.access.Row;
+import org.apache.iotdb.udf.api.access.RowIterator;
+import org.apache.iotdb.udf.api.access.RowWindow;
+import org.apache.iotdb.udf.api.collector.PointCollector;
+import org.apache.iotdb.udf.api.customizer.config.UDTFConfigurations;
+import org.apache.iotdb.udf.api.customizer.parameter.UDFParameterValidator;
+import org.apache.iotdb.udf.api.customizer.parameter.UDFParameters;
+import
org.apache.iotdb.udf.api.customizer.strategy.SlidingSizeWindowAccessStrategy;
+import org.apache.iotdb.udf.api.type.Type;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This function is used to repair the value of the time series.
+ */
+public class UDTFSeasonalRepair implements UDTF {
+ private double k;
+ private int period;
+ private int max_iter;
+
+ private final List<Long> times = new ArrayList<>();
+ private final List<Double> values = new ArrayList<>();
+
+ @Override
+ public void validate(UDFParameterValidator validator) throws Exception {
+ validator
+ .validateInputSeriesNumber(1)
+ .validateInputSeriesDataType(0, Type.FLOAT, Type.DOUBLE,
Type.INT32, Type.INT64)
+ .validate(
+ period -> (int) period > 0,
+ "Parameter \"period\" should be a positive integer.",
+ validator.getParameters().getInt("period"))
+ .validate(
+ k -> (double) k > 0.0,
+ "parameter \"k\" should be a positive float number.",
+ validator.getParameters().getDoubleOrDefault("k", 9.0))
+ .validate(
+ max_iter -> (int) max_iter > 0,
+ "parameter \"max_iter\" should be a positive integer.",
+ validator.getParameters().getIntOrDefault("max_iter",
10));
+ }
+
+ @Override
+ public void beforeStart(UDFParameters parameters, UDTFConfigurations
configurations) throws Exception {
+ configurations.setAccessStrategy(new
SlidingSizeWindowAccessStrategy(Integer.MAX_VALUE))
+ .setOutputDataType(parameters.getDataType(0));
+ period = parameters.getInt("period");
+ k = parameters.getDoubleOrDefault("k", 9.0);
+ max_iter = parameters.getIntOrDefault("max_iter", 10);
+ }
+
+ @Override
+ public void transform(RowWindow rowWindow, PointCollector collector)
throws Exception {
+ RowIterator rowIterator = rowWindow.getRowIterator();
+ while (rowIterator.hasNextRow()) { // load data
+ Row row = rowIterator.next();
+ times.add(row.getTime());
+ double value = switch (row.getDataType(0)) {
+ case INT32 -> row.getInt(0);
+ case INT64 -> row.getLong(0);
+ case FLOAT -> row.getFloat(0);
+ case DOUBLE -> row.getDouble(0);
+ default -> throw new Exception();
+ };
+ values.add(value);
+ }
+ processNaN();
+ }
+
+ @Override
+ public void terminate(PointCollector collector) throws Exception {
+ SeasonalRepair seasonalRepair = new SeasonalRepair(getTimesArray(),
getValuesArray(), period, k, max_iter);
+ double[] repaired = seasonalRepair.getTd_repair();
+ for (int i = 0; i < values.size(); i++) {
+ collector.putDouble(times.get(i), repaired[i]);
+ }
+ }
+
+ private long[] getTimesArray() {
+ long[] rtn = new long[times.size()];
+ for (int i = 0; i < times.size(); ++i)
+ rtn[i] = times.get(i);
+ return rtn;
+ }
+
+ private double[] getValuesArray() {
+ double[] rtn = new double[values.size()];
+ for (int i = 0; i < values.size(); ++i)
+ rtn[i] = values.get(i);
+ return rtn;
+ }
+
+ private void setValues(int i, int idx1, int idx2) {
+ values.set(i, values.get(idx1) + (values.get(idx2) - values.get(idx1))
* (times.get(i) - times.get(idx1)) / (times.get(idx2) - times.get(idx1)));
+ }
+
+ private void processNaN() throws Exception {
+ int idx1 = 0, idx2, n = values.size();
+ while (idx1 < n && Double.isNaN(values.get(idx1))) {
+ idx1++;
+ }
+ idx2 = idx1 + 1;
+ while (idx2 < n && Double.isNaN(values.get(idx2))) {
+ idx2++;
+ }
+ if (idx2 >= n) {
+ throw new Exception("At least two non-NaN values are needed");
+ }
+ for (int i = 0; i < idx2; i++) {
+ setValues(i, idx1, idx2);
+ }
+ for (int i = idx2 + 1; i < n; i++) {
+ if (!Double.isNaN(values.get(i))) {
+ idx1 = idx2;
+ idx2 = i;
+ for (int j = idx1 + 1; j < idx2; j++) {
+ setValues(j, idx1, idx2);
+ }
+ }
+ }
+ for (int i = idx2 + 1; i < n; i++) {
+ setValues(i, idx1, idx2);
+ }
+ }
+}
diff --git
a/library-udf/src/main/java/org/apache/iotdb/library/drepair/util/Decomposition.java
b/library-udf/src/main/java/org/apache/iotdb/library/drepair/util/Decomposition.java
new file mode 100644
index 00000000000..a6c4d70c1ea
--- /dev/null
+++
b/library-udf/src/main/java/org/apache/iotdb/library/drepair/util/Decomposition.java
@@ -0,0 +1,130 @@
+package org.apache.iotdb.library.drepair.util;
+
+public class Decomposition {
+ private final long[] td_time;
+ private final double[] td;
+ private final int period;
+
+ private final double[] seasonal;
+ private final double[] trend;
+ private final double[] residual;
+
+ public Decomposition(long[] td_time, double[] td, int period) throws
Exception {
+ this.td = td;
+ this.td_time = td_time;
+ this.period = period;
+
+ this.seasonal = new double[period];
+ this.trend = new double[td.length];
+ this.residual = new double[td.length];
+
+ this.decompose();
+ }
+
+ private void decompose() throws Exception {
+ if (period > td.length)
+ throw new Exception("Error: Period exceed the size of time
series!");
+
+ // constant
+ int interval = period / 2;
+ int size = td.length;
+ // structure
+ double[] de_trend = new double[size];
+ MovingMedian movingMedian = new MovingMedian(period);
+
+ // step 1: trend
+ if (period % 2 == 1) {
+ throw new Exception("Period must be even.");
+ } else {
+ // initial
+ for (int i = 0; i < period - 1; ++i) movingMedian.update(td[i]);
+ // moving median
+ for (int i = period - 1; i < size; ++i) {
+ movingMedian.update(td[i]);
+ trend[i - interval + 1] = movingMedian.getMedian();
+ }
+ }
+
+ // trend extension
+ constant_ext();
+
+ // step 2: de-trend
+ for (int i = 0; i < size; ++i)
+ de_trend[i] = td[i] - trend[i];
+
+ // step 3: seasonal
+ double[] cal_median = new double[Math.max(period, size / period + 1)];
+ for (int i = 0; i < period; ++i) {
+ // in each cycle
+ for (int j = 0; j < size / period; ++j)
+ cal_median[j] = de_trend[j * period + i];
+
+ if (i < size % period) {
+ cal_median[size / period] = de_trend[i + (size / period) *
period];
+ seasonal[i] = LinearMedian.getMedian(cal_median, size / period
+ 1);
+ } else {
+ seasonal[i] = LinearMedian.getMedian(cal_median, size /
period);
+ }
+ }
+
+ // de-median
+ for (int i = 0; i < period; i++) {
+ cal_median[i] = seasonal[i];
+ }
+ // TODO: mean?
+ double median_s = LinearMedian.getMedian(cal_median, period);
+ for (int i = 0; i < period; ++i)
+ seasonal[i] = seasonal[i] - median_s;
+
+ // step 3: residual
+ for (int i = 0; i < size; ++i)
+ residual[i] = de_trend[i] - seasonal[i % period];
+ }
+
+ private void constant_ext() {
+ int interval = period / 2;
+ for (int i = interval; i > 0; --i)
+ trend[i - 1] = trend[i];
+ for (int i = trend.length - interval - 1; i < trend.length - 1; ++i)
+ trend[i + 1] = trend[i];
+ }
+
+ private void ar_ext() {
+ int interval = period / 2;
+ int end = trend.length - interval - 1;
+
+ double a = 0.0, b = 0.0, d = trend.length - 2 * interval - 1, tmp;
+ for (int i = interval; i < end; ++i) {
+ b -= trend[i];
+ a += trend[i] * trend[i];
+ }
+ tmp = a * d - b * b;
+ a /= tmp;
+ b /= tmp;
+ d /= tmp;
+
+ double sigma = 0.0, a1 = 0.0;
+ for (int i = interval; i < end; ++i) {
+ sigma += (a + b * trend[i]) * trend[i + 1];
+ a1 += (b + d * trend[i]) * trend[i + 1];
+ }
+
+ // extend
+ for (int i = interval; i > 0; --i)
+ trend[i - 1] = (trend[i] - sigma) / a1;
+ for (int i = trend.length - interval - 1; i < trend.length - 1; ++i)
+ trend[i + 1] = a1 * trend[i] + sigma;
+ }
+
+ public double[] getSeasonal() {
+ return seasonal;
+ }
+
+ public double[] getTrend() {
+ return trend;
+ }
+
+ public double[] getResidual() {
+ return residual;
+ }
+}
diff --git
a/library-udf/src/main/java/org/apache/iotdb/library/drepair/util/LinearMedian.java
b/library-udf/src/main/java/org/apache/iotdb/library/drepair/util/LinearMedian.java
new file mode 100644
index 00000000000..f0172c434b1
--- /dev/null
+++
b/library-udf/src/main/java/org/apache/iotdb/library/drepair/util/LinearMedian.java
@@ -0,0 +1,130 @@
+package org.apache.iotdb.library.drepair.util;
+
+public class LinearMedian {
+ //Function to invoke LinearSelect
+ public static double getMedian(double[] S, int size) {
+ return linearSelect(0, size - 1, S, size / 2);
+ }
+
+ //do linearSelect in a recursive way
+ private static double linearSelect(int left, int right, double[] array,
int k) {
+ //if there is only one element now, just record.
+ if (left >= right) {
+ return array[left];
+ }
+ //do the partition
+ int p = pickCleverPivot(left, right, array);
+ int eIndex = partition(left, right, array, p);
+ //after the partition, do following ops
+ if (k <= eIndex) {
+ return linearSelect(left, eIndex - 1, array, k);
+ } else if (k == eIndex + 1) {
+ return array[eIndex];
+ } else {
+ return linearSelect(eIndex + 1, right, array, k);
+ }
+
+ }
+
+ //do Partition with a pivot
+ private static int partition(int left, int right, double[] array, int
pIndex) {
+ //move pivot to last index of the array
+ swap(array, pIndex, right);
+
+ double p = array[right];
+ int l = left;
+ int r = right - 1;
+
+ while (l <= r) {
+ while (l <= r && array[l] <= p) {
+ l++;
+ }
+ while (l <= r && array[r] >= p) {
+ r--;
+ }
+ if (l < r) {
+ swap(array, l, r);
+ }
+ }
+
+ swap(array, l, right);
+ return l;
+ }
+
+ //Pick a random pivot to do the LinearSelect
+ //Implementation inspired from pseudocode provided in assignment
+ //instructions and at https://en.wikipedia.org/wiki/Median_of_medians
+ private static int pickCleverPivot(int left, int right, double[] array) {
+ int n = array.length;
+
+ //Base case: If array length is less than 5, get median of it
+ if ((right - left) < 5) {
+ return getMedianValue(left, right, array);
+ }//end if
+
+ int count = left;
+
+ //Divide array into subgroups of 5
+ //Sort respective subarray to retrieve its median
+ for (int i = left; i <= right; i += 5) {
+
+ int tempRight = i + 4;
+
+ if (tempRight > right) {
+ tempRight = right;
+ }//end if
+
+ int medianSubgroup;
+
+ if ((tempRight - i) <= 2) {
+ continue;
+ } else {
+ medianSubgroup = getMedianValue(i, tempRight, array);
//Retrieve median of subarray
+ }
+
+ //Swap median to front of array
+ swap(array, medianSubgroup, count);
+
+ count++;
+ }//end for
+
+ //Recursively call pickCleverPivot for median of medians
+ return pickCleverPivot(left, count, array);
+ }//end pickCleverPivot
+
+
+ private static int getMedianValue(int left, int right, double[] array) {
+
+ //Sort that subarray! (Using insertion sort that is.)
+ for (int i = left; i <= right; i++) {
+ int j = i;
+ while (j > left && array[j - 1] > array[j]) {
+ swap(array, j, j - 1);
+ j -= 1;
+ }//end while
+ }//end for
+
+
+ int medianPos = 0;
+
+ //Check if length of subarray is even or not
+ //Implementation inspired by code found here:
http://stackoverflow.com/a/11955900
+ if ((right - left) % 2 == 0) {
+ //Retrieve value left of middle to be treated as median
+ medianPos = ((right - left) / 2) - 1;
+ } else {
+ medianPos = (right - left) / 2;
+ }//end if/else statement
+
+ return left + medianPos;
+
+ }//end getMedianValue
+
+
+ //swap two elements in the array
+ private static void swap(double[] array, int a, int b) {
+ double tmp = array[a];
+ array[a] = array[b];
+ array[b] = tmp;
+ }
+}
diff --git
a/library-udf/src/main/java/org/apache/iotdb/library/drepair/util/MovingMedian.java
b/library-udf/src/main/java/org/apache/iotdb/library/drepair/util/MovingMedian.java
new file mode 100644
index 00000000000..84616a2e05e
--- /dev/null
+++
b/library-udf/src/main/java/org/apache/iotdb/library/drepair/util/MovingMedian.java
@@ -0,0 +1,187 @@
+package org.apache.iotdb.library.drepair.util;
+
+// period must be even
+public class MovingMedian {
+
+ private final int windowSize;
+
+ /* The current size of the window */
+ private int size;
+
+ /* Position of the oldest element of the window */
+ private int startIndex;
+
+ /*
+ * Each element of windowHeap is a pointer to either maxHeap or minHeap.
+ * Each element of windowHeapIndex is a index of the element in the
corresponding Heap.
+ * Together they represent the moving window, such that the k-th element
of the window
+ * can be accessed using windowHeap[k].heap[window[k]]
+ * */
+ private final Heap[] windowHeap;
+ private final int[] window;
+
+ private final Heap maxHeap;
+ private final Heap minHeap;
+ private double median;
+
+ public MovingMedian(int windowSize) {
+ this.windowSize = windowSize;
+ this.maxHeap = new Heap(windowSize / 2, false);
+ this.minHeap = new Heap(windowSize / 2, true);
+ window = new int[windowSize];
+ windowHeap = new Heap[windowSize];
+ }
+
+ private int inc(int k) {
+ return ++k == windowSize ? 0 : k;
+ }
+
+ private void calcMedian() {
+ median = (minHeap.top() + maxHeap.top()) / 2;
+ }
+
+ public double getMedian() {
+ return median;
+ }
+
+ public void update(double value) {
+ if (size >= windowSize) {
+ windowHeap[startIndex].extract(window[startIndex]);
+ size--;
+ }
+
+ if (value <= maxHeap.top()) {
+ if (maxHeap.size == maxHeap.cap || (maxHeap.size - minHeap.size)
>= 1) {
+ maxHeap.transfer(0, minHeap);
+ }
+ maxHeap.insert(value, startIndex);
+ } else {
+ if (minHeap.size == minHeap.cap || (minHeap.size - maxHeap.size)
>= 1) {
+ minHeap.transfer(0, maxHeap);
+ }
+ minHeap.insert(value, startIndex);
+ }
+
+ while (minHeap.size > 2 && maxHeap.top() > minHeap.top()) {
+ maxHeap.exchange(0, minHeap, 0);
+ }
+ size++;
+ startIndex = inc(startIndex);
+ calcMedian();
+ }
+
+ class Heap {
+ private int size = 0;
+ private final double[] heap;
+ // contains the inverted index of each element in the heap to their
positions in the window
+ // such that, heap[i] == heap[window[invertedIndex[i]]].
+ private final int[] invertedIndex;
+ private final boolean minHeap;
+ private final int cap;
+
+ public Heap(int cap, boolean minHeap) {
+ this.cap = cap;
+ this.heap = new double[cap + 1];
+ this.invertedIndex = new int[cap + 1];
+ this.minHeap = minHeap;
+ }
+
+ private double top() {
+ return heap[0];
+ }
+
+ void insert(double value, int qIndex) {
+ heap[size] = value;
+ window[qIndex] = size;
+ windowHeap[qIndex] = this;
+ invertedIndex[size] = qIndex;
+ size++;
+ bubleUp(size - 1);
+ }
+
+ void extract(int k) {
+ size--;
+ double d = heap[k];
+ if (size > k) {
+ // Move the last element to the k-th position
+ heap[k] = heap[size];
+ invertedIndex[k] = invertedIndex[size];
+ window[invertedIndex[k]] = k;
+ windowHeap[invertedIndex[k]] = this;
+ }
+ bubleUp(k);
+ bubleDown(k);
+ }
+
+ void transfer(int k, Heap other) {
+ other.insert(heap[k], invertedIndex[k]);
+ extract(k);
+ }
+
+ void exchange(int k, Heap other, int m) {
+ double val = other.heap[m];
+ int qIndex = other.invertedIndex[m];
+ other.extract(m);
+ transfer(k, other);
+ insert(val, qIndex);
+ }
+
+ private void swap(int i, int j) {
+ int eli = invertedIndex[i];
+ int elj = invertedIndex[j];
+ double di = heap[i];
+ double dj = heap[j];
+ window[elj] = i;
+ window[eli] = j;
+ heap[j] = di;
+ heap[i] = dj;
+ invertedIndex[i] = elj;
+ invertedIndex[j] = eli;
+ }
+
+ private void bubleUp(int k) {
+ while (k > 0) {
+ int parent = parentOf(k);
+ boolean swap = minHeap ? heap[parent] > heap[k] : heap[parent]
< heap[k];
+ if (swap) {
+ swap(k, parent);
+ }
+ k = parent;
+ }
+ }
+
+ private void bubleDown(int k) {
+ int half = size >>> 1;
+ while (k < half) {
+ double val = heap[k];
+ int leftChild = leftChildOf(k);
+ int rightChild = leftChild + 1;
+ int child = leftChild;
+ double childVal = heap[child];
+ boolean swap;
+
+ if (rightChild < size && (minHeap ? heap[rightChild] <
childVal : heap[rightChild] > childVal)) {
+ child = rightChild;
+ childVal = heap[child];
+ }
+ swap = minHeap ? childVal < val : childVal > val;
+ if (!swap)
+ break;
+ swap(k, child);
+ k = child;
+ }
+ }
+
+ private int parentOf(int k) {
+ return (k - 1) >>> 1;
+ }
+
+ private int leftChildOf(int k) {
+ return 2 * k + 1;
+ }
+
+ private int rightChildOf(int i) {
+ return leftChildOf(i) + 1;
+ }
+ }
+}
diff --git
a/library-udf/src/main/java/org/apache/iotdb/library/drepair/util/SeasonalRepair.java
b/library-udf/src/main/java/org/apache/iotdb/library/drepair/util/SeasonalRepair.java
new file mode 100644
index 00000000000..171206c8508
--- /dev/null
+++
b/library-udf/src/main/java/org/apache/iotdb/library/drepair/util/SeasonalRepair.java
@@ -0,0 +1,134 @@
+package org.apache.iotdb.library.drepair.util;
+
+import java.util.ArrayList;
+
+public class SeasonalRepair {
+ private final long[] td_time;
+ private final double[] td_dirty;
+ private final double[] td_repair;
+ private final int period;
+ private final double k; // k*mad
+ private final int max_iter;
+
+ private double mu, sigma;
+ private double[] seasonal, trend, residual;
+ private final int size;
+ private final long cost_time;
+
+ public SeasonalRepair(long[] td_time, double[] td_dirty, int period,
double k, int max_iter) throws Exception {
+ this.td_time = td_time;
+ this.td_dirty = td_dirty;
+ this.td_repair = new double[td_dirty.length];
+ this.period = period;
+ this.k = k;
+ this.max_iter = max_iter;
+
+ this.size = td_dirty.length;
+
+ long startTime = System.currentTimeMillis();
+ this.repair();
+ long endTime = System.currentTimeMillis();
+ this.cost_time = endTime - startTime;
+// System.out.println("SRRD time cost:" + cost_time + "ms");
+ }
+
+ private void repair() throws Exception {
+ System.arraycopy(td_dirty, 0, td_repair, 0, td_dirty.length);
+
+ int h = 0;
+ for (; h < max_iter; ++h) {
+ Decomposition de = new Decomposition(td_time, td_repair, period);
+ seasonal = de.getSeasonal();
+ trend = de.getTrend();
+ residual = de.getResidual();
+
+ estimate();
+
+ boolean flag = true;
+ for (int i = 0; i < size; ++i) {
+ if (sub(residual[i], mu) > k * sigma) {
+ flag = false;
+ td_repair[i] = generate(i);
+ }
+ }
+ if (flag) break;
+ }
+// System.out.println("Stop after " + (h + 1) + " iterations");
+ }
+
+
+ private void estimate() {
+ // get the sum of array
+ double sum = 0.0;
+ for (double i : residual) {
+ sum += i;
+ }
+
+ // get the mean of array
+ int length = residual.length;
+
+ mu = sum / length;
+
+ // calculate the standard deviation
+ double standardDeviation = 0.0;
+ for (double num : residual) {
+ standardDeviation += Math.pow(num - mu, 2);
+ }
+
+ sigma = Math.sqrt(standardDeviation / length);
+ }
+
+ private double generate(int pos) {
+ // in each cycle
+ int i = pos % period;
+ ArrayList<Double> arr = new ArrayList<>();
+
+ for (int j = 0; j < size / period; ++j)
+ if (j * period + i != pos) // remove anomaly
+ arr.add(residual[j * period + i]);
+ if (i < size % period && i + (size / period) * period != pos)
+ arr.add(residual[i + (size / period) * period]);
+
+ double[] cal_median = new double[arr.size()];
+ for (int j = 0; j < arr.size(); j++)
+ cal_median[j] = arr.get(j);
+
+ return get_median(cal_median) + seasonal[pos % period] + trend[pos];
+ }
+
+ private double sub(double a, double b) {
+ return a > b ? a - b : b - a;
+ }
+
+ private double get_median(double[] A) {
+ return quickSelect(A, 0, A.length - 1, A.length / 2);
+ }
+
+ private double quickSelect(double[] A, int l, int r, int k) {
+ if (l >= r) return A[k];
+
+ double x = A[l];
+ int i = l - 1, j = r + 1;
+ while (i < j) {
+ do i++; while (A[i] < x);
+ do j--; while (A[j] > x);
+ if (i < j) {
+ double temp = A[i];
+ A[i] = A[j];
+ A[j] = temp;
+ }
+ }
+
+ if (k <= j) return quickSelect(A, l, j, k);
+ else return quickSelect(A, j + 1, r, k);
+ }
+
+ public double[] getTd_repair() {
+ return td_repair;
+ }
+
+ public long getCost_time() {
+ return cost_time;
+ }
+}
+