From 35fedf4fec745bf962ce9dfabed540868ac34667 Mon Sep 17 00:00:00 2001 From: Anton Romanov Date: Tue, 19 Apr 2022 18:06:40 +0400 Subject: [PATCH] #3 -- add F-Transform --- .../ru/ulstu/controller/IndexController.java | 4 +- .../ru/ulstu/method/MethodParamValue.java | 8 ++ .../java/ru/ulstu/method/MethodParameter.java | 10 ++- .../AddTrendAddSeasonModel.java | 3 +- .../AddTrendNoSeasonModel.java | 3 +- .../notrendnoseason/NoTrendNoSeasonModel.java | 3 +- .../parameter/ExponentialMethodParameter.java | 3 +- .../ulstu/method/ftransform/AComponent.java | 57 ++++++++++++ .../ulstu/method/ftransform/FTransform.java | 87 +++++++++++++++++++ .../method/ftransform/FTransformModel.java | 43 +++++++++ .../parameter/NumberOfCoveredPoints.java | 29 +++++++ .../ulstu/service/MethodParamBruteForce.java | 22 +++-- .../ru/ulstu/service/TimeSeriesService.java | 4 + 13 files changed, 262 insertions(+), 14 deletions(-) create mode 100644 src/main/java/ru/ulstu/method/ftransform/AComponent.java create mode 100644 src/main/java/ru/ulstu/method/ftransform/FTransform.java create mode 100644 src/main/java/ru/ulstu/method/ftransform/FTransformModel.java create mode 100644 src/main/java/ru/ulstu/method/ftransform/parameter/NumberOfCoveredPoints.java diff --git a/src/main/java/ru/ulstu/controller/IndexController.java b/src/main/java/ru/ulstu/controller/IndexController.java index 86389b2..05814e0 100644 --- a/src/main/java/ru/ulstu/controller/IndexController.java +++ b/src/main/java/ru/ulstu/controller/IndexController.java @@ -82,11 +82,13 @@ public class IndexController { private void addChartToModel(TimeSeries timeSeries, String method, Model model) throws ExecutionException, InterruptedException, InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException, ModelingException { int countForecastPoints = timeSeries.getLength() > 20 ? 10 : timeSeries.getLength() / 3; - TimeSeries timeSeriesModel = timeSeriesService.smoothTimeSeries(timeSeries).getTimeSeries(); + TimeSeries timeSeriesModel; ModelingResult modelingResult; if (method == null) { + timeSeriesModel = timeSeriesService.smoothTimeSeries(timeSeries).getTimeSeries(); modelingResult = timeSeriesService.getForecast(timeSeries, countForecastPoints); } else { + timeSeriesModel = timeSeriesService.smoothTimeSeries(timeSeries, method).getTimeSeries(); modelingResult = timeSeriesService.getForecast(timeSeries, method, countForecastPoints); } TimeSeries forecast = modelingResult.getTimeSeries(); diff --git a/src/main/java/ru/ulstu/method/MethodParamValue.java b/src/main/java/ru/ulstu/method/MethodParamValue.java index df61b32..f3411c2 100644 --- a/src/main/java/ru/ulstu/method/MethodParamValue.java +++ b/src/main/java/ru/ulstu/method/MethodParamValue.java @@ -16,4 +16,12 @@ public class MethodParamValue { public Number getValue() { return value; } + + public Integer getIntValue() { + return value.intValue(); + } + + public void setValue(Number value) { + this.value = value; + } } diff --git a/src/main/java/ru/ulstu/method/MethodParameter.java b/src/main/java/ru/ulstu/method/MethodParameter.java index 58d6681..23e4c6f 100644 --- a/src/main/java/ru/ulstu/method/MethodParameter.java +++ b/src/main/java/ru/ulstu/method/MethodParameter.java @@ -1,8 +1,10 @@ package ru.ulstu.method; +import ru.ulstu.datamodel.ts.TimeSeries; + import java.util.List; -public abstract class MethodParameter implements Comparable { +public abstract class MethodParameter implements Comparable { protected String name; public MethodParameter(String name) { @@ -13,10 +15,10 @@ public abstract class MethodParameter implements Comparable { return name; } - public abstract List getAvailableValues(); + public abstract List getAvailableValues(TimeSeries timeSeries); @Override - public int compareTo(Object o) { - return this.name.compareTo(((MethodParameter) o).getName()); + public int compareTo(MethodParameter o) { + return this.name.compareTo(o.getName()); } } diff --git a/src/main/java/ru/ulstu/method/exponential/addtrendaddseason/AddTrendAddSeasonModel.java b/src/main/java/ru/ulstu/method/exponential/addtrendaddseason/AddTrendAddSeasonModel.java index 1e54be4..0f2d163 100644 --- a/src/main/java/ru/ulstu/method/exponential/addtrendaddseason/AddTrendAddSeasonModel.java +++ b/src/main/java/ru/ulstu/method/exponential/addtrendaddseason/AddTrendAddSeasonModel.java @@ -1,5 +1,6 @@ package ru.ulstu.method.exponential.addtrendaddseason; +import ru.ulstu.datamodel.Model; import ru.ulstu.datamodel.ts.TimeSeries; import ru.ulstu.method.MethodParamValue; import ru.ulstu.method.MethodParameter; @@ -13,7 +14,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; -public class AddTrendAddSeasonModel extends ru.ulstu.datamodel.Model { +public class AddTrendAddSeasonModel extends Model { private final ExponentialMethodParamValue alpha = new ExponentialMethodParamValue<>(Alpha.getInstance(), 0.5); private final ExponentialMethodParamValue beta = new ExponentialMethodParamValue<>(Beta.getInstance(), 0.5); private final ExponentialMethodParamValue gamma = new ExponentialMethodParamValue<>(Gamma.getInstance(), 0.5); diff --git a/src/main/java/ru/ulstu/method/exponential/addtrendnoseason/AddTrendNoSeasonModel.java b/src/main/java/ru/ulstu/method/exponential/addtrendnoseason/AddTrendNoSeasonModel.java index aaf5f32..f16cd9e 100644 --- a/src/main/java/ru/ulstu/method/exponential/addtrendnoseason/AddTrendNoSeasonModel.java +++ b/src/main/java/ru/ulstu/method/exponential/addtrendnoseason/AddTrendNoSeasonModel.java @@ -1,5 +1,6 @@ package ru.ulstu.method.exponential.addtrendnoseason; +import ru.ulstu.datamodel.Model; import ru.ulstu.datamodel.ts.TimeSeries; import ru.ulstu.method.MethodParamValue; import ru.ulstu.method.MethodParameter; @@ -11,7 +12,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; -public class AddTrendNoSeasonModel extends ru.ulstu.datamodel.Model { +public class AddTrendNoSeasonModel extends Model { private final ExponentialMethodParamValue alpha = new ExponentialMethodParamValue<>(Alpha.getInstance(), 0.5); private final ExponentialMethodParamValue beta = new ExponentialMethodParamValue<>(Beta.getInstance(), 0.5); private final List smoothedComponent = new ArrayList<>(); diff --git a/src/main/java/ru/ulstu/method/exponential/notrendnoseason/NoTrendNoSeasonModel.java b/src/main/java/ru/ulstu/method/exponential/notrendnoseason/NoTrendNoSeasonModel.java index aaf0558..0bfdb2e 100644 --- a/src/main/java/ru/ulstu/method/exponential/notrendnoseason/NoTrendNoSeasonModel.java +++ b/src/main/java/ru/ulstu/method/exponential/notrendnoseason/NoTrendNoSeasonModel.java @@ -1,5 +1,6 @@ package ru.ulstu.method.exponential.notrendnoseason; +import ru.ulstu.datamodel.Model; import ru.ulstu.datamodel.ts.TimeSeries; import ru.ulstu.method.MethodParamValue; import ru.ulstu.method.MethodParameter; @@ -10,7 +11,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; -public class NoTrendNoSeasonModel extends ru.ulstu.datamodel.Model { +public class NoTrendNoSeasonModel extends Model { private final ExponentialMethodParamValue alpha = new ExponentialMethodParamValue<>(Alpha.getInstance(), 0.5); private final List smoothedComponent = new ArrayList<>(); diff --git a/src/main/java/ru/ulstu/method/exponential/parameter/ExponentialMethodParameter.java b/src/main/java/ru/ulstu/method/exponential/parameter/ExponentialMethodParameter.java index 0df6150..cd18acf 100644 --- a/src/main/java/ru/ulstu/method/exponential/parameter/ExponentialMethodParameter.java +++ b/src/main/java/ru/ulstu/method/exponential/parameter/ExponentialMethodParameter.java @@ -1,6 +1,7 @@ package ru.ulstu.method.exponential.parameter; import com.fasterxml.jackson.annotation.JsonIgnore; +import ru.ulstu.datamodel.ts.TimeSeries; import ru.ulstu.method.MethodParameter; import java.util.ArrayList; @@ -35,7 +36,7 @@ public abstract class ExponentialMethodParameter extends MethodParameter { @Override @JsonIgnore - public List getAvailableValues() { + public List getAvailableValues(TimeSeries timeSeries) { List values = new ArrayList<>(); for (double i = minValue.doubleValue(); i <= maxValue.doubleValue(); i += optimizationStep.doubleValue()) { values.add(i); diff --git a/src/main/java/ru/ulstu/method/ftransform/AComponent.java b/src/main/java/ru/ulstu/method/ftransform/AComponent.java new file mode 100644 index 0000000..5ba4fe4 --- /dev/null +++ b/src/main/java/ru/ulstu/method/ftransform/AComponent.java @@ -0,0 +1,57 @@ +package ru.ulstu.method.ftransform; + +/** + * Треугольная функция принадлежности + */ +public class AComponent { + private int start; // левая граница треугольника + private int end; // правая граница треугольника + private int top; // вершина треугольника + + public int getStart() { + return start; + } + + public AComponent() { + } + + public AComponent(int start, int top, int end) { + this.start = start; + this.top = top; + this.end = end; + } + + public void setStart(int start) { + this.start = start; + } + + + public int getEnd() { + return end; + } + + public void setEnd(int end) { + this.end = end; + } + + public int getTop() { + return top; + } + + public void setTop(int top) { + this.top = top; + } + + public double getValueAtPoint(int pointIndex) { + if (pointIndex == this.getTop()) { + return 1; + } else if ((pointIndex >= this.getEnd()) || (pointIndex <= this.getStart())) { + return 0; + } else if (pointIndex < this.getTop()) { + return (double) (pointIndex - this.getStart()) / (this.getTop() - this.getStart()); + } else if (pointIndex > this.getTop()) { + return (double) -(pointIndex - this.getEnd()) / (this.getEnd() - this.getTop()); + } + return 0; + } +} diff --git a/src/main/java/ru/ulstu/method/ftransform/FTransform.java b/src/main/java/ru/ulstu/method/ftransform/FTransform.java new file mode 100644 index 0000000..3981e97 --- /dev/null +++ b/src/main/java/ru/ulstu/method/ftransform/FTransform.java @@ -0,0 +1,87 @@ +package ru.ulstu.method.ftransform; + +import org.springframework.stereotype.Component; +import ru.ulstu.datamodel.Model; +import ru.ulstu.datamodel.ts.TimeSeries; +import ru.ulstu.method.Method; +import ru.ulstu.method.MethodParamValue; +import ru.ulstu.method.MethodParameter; + +import java.util.List; + +@Component +public class FTransform extends Method { + + @Override + protected FTransformModel getModelOfValidTimeSeries(TimeSeries ts, + List parameters) { + FTransformModel model = new FTransformModel(ts, parameters); + List aComponents = generateAComponents(ts, model.getNumberOfCoveredPoints().getIntValue(), model.getAComponents()); + ; + TimeSeries piecewiseLinearTrend = model.getPiecewiseLinearTrend(); + TimeSeries tsModel = model.getTimeSeriesModel(); + + for (AComponent aComponent : aComponents) { + double sum1 = 0; + double sum2 = 0; + for (int j = 0; j < ts.getLength(); j++) { + double membership = aComponent.getValueAtPoint(j); + sum1 += membership * ts.getNumericValue(j); + sum2 += membership; + } + piecewiseLinearTrend.addValue(ts.getValue(aComponent.getTop()), sum1 / sum2); + tsModel.addValue(ts.getValue(aComponent.getTop()), sum1 / sum2); + } + return model; + } + + private List generateAComponents(TimeSeries ts, int numberOfCoveredPoints, List piecewiseLinearTrend) { + long deltaForTriangle = Math.round(numberOfCoveredPoints / 2.0); + int currentPoint = 0; + while (currentPoint < ts.getLength()) { + int startPoint = (currentPoint == 0) + ? 0 + : piecewiseLinearTrend.get(piecewiseLinearTrend.size() - 1).getTop(); + AComponent bf = new AComponent(startPoint, currentPoint, currentPoint + numberOfCoveredPoints / 2); + if (bf.getStart() < 0) { + bf.setStart(0); + } + if (bf.getEnd() > ts.getLength() - 1) { + bf.setEnd(ts.getLength() - 1); + } + + if (bf.getTop() > ts.getLength() - 1) { + bf.setTop(ts.getLength() - 1); + } + + piecewiseLinearTrend.add(bf); + currentPoint += deltaForTriangle; + } + if (piecewiseLinearTrend.get(piecewiseLinearTrend.size() - 1).getEnd() != piecewiseLinearTrend.get(piecewiseLinearTrend.size() - 1).getTop()) { + AComponent bf = new AComponent(piecewiseLinearTrend.get(piecewiseLinearTrend.size() - 1).getTop(), + piecewiseLinearTrend.get(piecewiseLinearTrend.size() - 1).getEnd(), + piecewiseLinearTrend.get(piecewiseLinearTrend.size() - 1).getEnd()); + piecewiseLinearTrend.add(bf); + } + return piecewiseLinearTrend; + } + + @Override + protected TimeSeries getForecastWithValidParams(Model model, TimeSeries forecast) { + FTransformModel fTransformModel = (FTransformModel) model; + for (int t = 1; t < forecast.getLength(); t++) { + forecast.getValues().get(t).setValue(fTransformModel.getPiecewiseLinearTrend().getLastValue().getValue()); + } + return forecast; + } + + @Override + public List getAvailableParameters() { + return FTransformModel.getAvailableParameters(); + } + + @Override + public String getName() { + return "F - преобразование"; + } +} diff --git a/src/main/java/ru/ulstu/method/ftransform/FTransformModel.java b/src/main/java/ru/ulstu/method/ftransform/FTransformModel.java new file mode 100644 index 0000000..af38aaa --- /dev/null +++ b/src/main/java/ru/ulstu/method/ftransform/FTransformModel.java @@ -0,0 +1,43 @@ +package ru.ulstu.method.ftransform; + +import ru.ulstu.datamodel.Model; +import ru.ulstu.datamodel.ts.TimeSeries; +import ru.ulstu.method.MethodParamValue; +import ru.ulstu.method.MethodParameter; +import ru.ulstu.method.ftransform.parameter.NumberOfCoveredPoints; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class FTransformModel extends Model { + private final MethodParamValue numberOfCoveredPoints = new MethodParamValue(NumberOfCoveredPoints.getInstance(), 3); + private final List aComponents = new ArrayList<>(); + private final TimeSeries piecewiseLinearTrend; + + public FTransformModel(TimeSeries ts, List parameters) { + super(ts); + piecewiseLinearTrend = new TimeSeries("Piecewise linear trend of ", ts.getKey()); + for (MethodParamValue parameter : parameters) { + if (parameter.getParameter() instanceof NumberOfCoveredPoints) { + numberOfCoveredPoints.setValue(parameter.getValue()); + } + } + } + + public TimeSeries getPiecewiseLinearTrend() { + return piecewiseLinearTrend; + } + + public List getAComponents() { + return aComponents; + } + + public MethodParamValue getNumberOfCoveredPoints() { + return numberOfCoveredPoints; + } + + public static List getAvailableParameters() { + return Collections.singletonList(NumberOfCoveredPoints.getInstance()); + } +} diff --git a/src/main/java/ru/ulstu/method/ftransform/parameter/NumberOfCoveredPoints.java b/src/main/java/ru/ulstu/method/ftransform/parameter/NumberOfCoveredPoints.java new file mode 100644 index 0000000..a027b7c --- /dev/null +++ b/src/main/java/ru/ulstu/method/ftransform/parameter/NumberOfCoveredPoints.java @@ -0,0 +1,29 @@ +package ru.ulstu.method.ftransform.parameter; + +import ru.ulstu.datamodel.ts.TimeSeries; +import ru.ulstu.method.MethodParameter; + +import java.util.ArrayList; +import java.util.List; + +public class NumberOfCoveredPoints extends MethodParameter { + private final static int MIN_NUMBER_OF_COVERED_POINTS = 3; + private final static int MIN_INCREASING_STEP_OF_NUMBER_OF_COVERED_POINTS = 2; + + public NumberOfCoveredPoints() { + super("Number of covered points"); + } + + public static NumberOfCoveredPoints getInstance() { + return new NumberOfCoveredPoints(); + } + + @Override + public List getAvailableValues(TimeSeries timeSeries) { + List values = new ArrayList<>(); + for (double i = MIN_NUMBER_OF_COVERED_POINTS; i <= (timeSeries.getLength() < 10 ? 7 : timeSeries.getLength() / 3.0); i += MIN_INCREASING_STEP_OF_NUMBER_OF_COVERED_POINTS) { + values.add(i); + } + return values; + } +} diff --git a/src/main/java/ru/ulstu/service/MethodParamBruteForce.java b/src/main/java/ru/ulstu/service/MethodParamBruteForce.java index ec4a5af..348d63d 100644 --- a/src/main/java/ru/ulstu/service/MethodParamBruteForce.java +++ b/src/main/java/ru/ulstu/service/MethodParamBruteForce.java @@ -48,7 +48,7 @@ class MethodParamBruteForce { .collect(Collectors.toMap(TimeSeriesValue::getDate, TimeSeriesValue::getValue)); for (Method method : methods) { - List> availableParametersValues = getAvailableParametersValues(method.getAvailableParameters()); + List> availableParametersValues = getAvailableParametersValues(timeSeries, method.getAvailableParameters()); for (List parametersValues : availableParametersValues) { Method methodInstance = method.getClass().getDeclaredConstructor().newInstance(); if (methodInstance.canMakeForecast(reducedTimeSeries, parametersValues, countPoints)) { @@ -119,7 +119,7 @@ class MethodParamBruteForce { } */ - public ModelingResult getSmoothedTimeSeries(TimeSeries timeSeries) throws ExecutionException, InterruptedException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { + public ModelingResult getSmoothedTimeSeries(TimeSeries timeSeries, List methods) throws ExecutionException, InterruptedException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { List> results = new ArrayList<>(); List results2 = new CopyOnWriteArrayList<>(); @@ -127,7 +127,7 @@ class MethodParamBruteForce { .collect(Collectors.toMap(TimeSeriesValue::getDate, TimeSeriesValue::getValue)); for (Method method : methods) { - List> availableParametersValues = getAvailableParametersValues(method.getAvailableParameters()); + List> availableParametersValues = getAvailableParametersValues(timeSeries, method.getAvailableParameters()); for (List parametersValues : availableParametersValues) { Method methodInstance = method.getClass().getDeclaredConstructor().newInstance(); if (methodInstance.canMakeModel(timeSeries, parametersValues)) { @@ -150,13 +150,25 @@ class MethodParamBruteForce { .orElse(null); } - private List> getAvailableParametersValues(List availableParameters) { + public ModelingResult getSmoothedTimeSeries(TimeSeries timeSeries, String methodClassName) throws ExecutionException, InterruptedException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, ModelingException { + Method method = methods.stream() + .filter(m -> m.getClass().getSimpleName().equals(methodClassName)) + .findAny() + .orElseThrow(() -> new ModelingException("Неизвестный метод прогнозирования")); + return getSmoothedTimeSeries(timeSeries, List.of(method)); + } + + public ModelingResult getSmoothedTimeSeries(TimeSeries timeSeries) throws ExecutionException, InterruptedException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { + return getSmoothedTimeSeries(timeSeries, methods); + } + + private List> getAvailableParametersValues(TimeSeries timeSeries, List availableParameters) { List> result = new ArrayList<>(); Map parameterOffset = new TreeMap<>(); Map> parameterValues = new TreeMap<>(); for (MethodParameter methodParameter : availableParameters) { parameterOffset.put(methodParameter, 0); - parameterValues.put(methodParameter, methodParameter.getAvailableValues()); + parameterValues.put(methodParameter, methodParameter.getAvailableValues(timeSeries)); } while (isNotAllParameterValuesUsed(parameterOffset, parameterValues)) { List resultRow = new ArrayList<>(); diff --git a/src/main/java/ru/ulstu/service/TimeSeriesService.java b/src/main/java/ru/ulstu/service/TimeSeriesService.java index 4b80bc7..525eb79 100644 --- a/src/main/java/ru/ulstu/service/TimeSeriesService.java +++ b/src/main/java/ru/ulstu/service/TimeSeriesService.java @@ -35,6 +35,10 @@ public class TimeSeriesService { return methodParamBruteForce.getSmoothedTimeSeries(timeSeries); } + public ModelingResult smoothTimeSeries(TimeSeries timeSeries, String methodClassName) throws ExecutionException, InterruptedException, InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException, ModelingException { + return methodParamBruteForce.getSmoothedTimeSeries(timeSeries, methodClassName); + } + public List getAvailableMethods() { return methodParamBruteForce.getAvailableMethods(); }