Merge branch '2-common-logic' into 'master'
Resolve "Реализовать наиболее общую логику сглаживания и прогнозирования" Closes #2 See merge request romanov73/time-series-smoothing!2
This commit is contained in:
commit
78bb2ac508
35
src/main/java/ru/ulstu/TimeSeriesUtils.java
Normal file
35
src/main/java/ru/ulstu/TimeSeriesUtils.java
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
package ru.ulstu;
|
||||||
|
|
||||||
|
import ru.ulstu.models.TimeSeries;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.temporal.ChronoUnit;
|
||||||
|
|
||||||
|
public class TimeSeriesUtils {
|
||||||
|
/**
|
||||||
|
* Вычисляет среднее значение между датами временного ряда
|
||||||
|
*
|
||||||
|
* @param timeSeries объект, содержащий временной ряд
|
||||||
|
* @return средняя разница между датами исходного временного ряда в миллисекундах
|
||||||
|
*/
|
||||||
|
public static long getTimeDifferenceInMilliseconds(TimeSeries timeSeries) {
|
||||||
|
long diffMilliseconds = 0;
|
||||||
|
for (int i = 1; i < timeSeries.getLength(); i++) {
|
||||||
|
diffMilliseconds += timeSeries.getValues().get(i - 1).getDate()
|
||||||
|
.until(timeSeries.getValues().get(i).getDate(), ChronoUnit.MILLIS);
|
||||||
|
}
|
||||||
|
return diffMilliseconds / (timeSeries.getLength() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TimeSeries fillDates(TimeSeries timeSeries, long milliseconds) {
|
||||||
|
timeSeries.getLastValue().setDate(LocalDateTime.now());
|
||||||
|
for (int i = timeSeries.getLength() - 2; i >= 0; i--) {
|
||||||
|
timeSeries.getValues().get(i).setDate(timeSeries.getValues().get(i + 1).getDate().minus(milliseconds, ChronoUnit.MILLIS));
|
||||||
|
}
|
||||||
|
return timeSeries;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TimeSeries fillDates(TimeSeries timeSeries) {
|
||||||
|
return fillDates(timeSeries, 1000 * 60 * 60 * 24);
|
||||||
|
}
|
||||||
|
}
|
59
src/main/java/ru/ulstu/controllers/AdviceController.java
Normal file
59
src/main/java/ru/ulstu/controllers/AdviceController.java
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2020. Anton Romanov
|
||||||
|
* Licensed 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 ru.ulstu.controllers;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||||
|
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
import org.springframework.web.client.HttpServerErrorException;
|
||||||
|
import ru.ulstu.models.exceptions.ForecastValidateException;
|
||||||
|
import ru.ulstu.models.exceptions.TimeSeriesValidateException;
|
||||||
|
import ru.ulstu.models.response.ErrorConstants;
|
||||||
|
import ru.ulstu.models.response.ResponseExtended;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@ControllerAdvice
|
||||||
|
public class AdviceController {
|
||||||
|
private final Logger log = LoggerFactory.getLogger(AdviceController.class);
|
||||||
|
|
||||||
|
private <E> ResponseExtended<E> handleException(ErrorConstants error, E errorData) {
|
||||||
|
log.warn(error.toString());
|
||||||
|
return new ResponseExtended<>(error, errorData);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler(Exception.class)
|
||||||
|
public ResponseExtended<String> handleUnknownException(Throwable e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return handleException(ErrorConstants.UNKNOWN, e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler(HttpServerErrorException.class)
|
||||||
|
public ResponseExtended<String> handleHttpClientException(Throwable e) {
|
||||||
|
return handleException(ErrorConstants.HTTP_CLIENT_ERROR, e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler(TimeSeriesValidateException.class)
|
||||||
|
public ResponseExtended<String> handleTimeSeriesValidateException(Throwable e) {
|
||||||
|
return handleException(ErrorConstants.TIME_SERIES_VALIDATE_ERROR, e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler(ForecastValidateException.class)
|
||||||
|
public ResponseExtended<String> handleForecastValidateException(Throwable e) {
|
||||||
|
return handleException(ErrorConstants.FORECAST_PARAMS_ERROR, e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
@ -11,8 +11,9 @@ import org.springframework.web.bind.annotation.RequestParam;
|
|||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
import ru.ulstu.configurations.ApiConfiguration;
|
import ru.ulstu.configurations.ApiConfiguration;
|
||||||
import ru.ulstu.models.Forecast;
|
import ru.ulstu.models.Forecast;
|
||||||
import ru.ulstu.models.ForecastRequestParams;
|
import ru.ulstu.models.ForecastParams;
|
||||||
import ru.ulstu.models.TimeSeries;
|
import ru.ulstu.models.TimeSeries;
|
||||||
|
import ru.ulstu.models.exceptions.ModelingException;
|
||||||
import ru.ulstu.services.TimeSeriesService;
|
import ru.ulstu.services.TimeSeriesService;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@ -37,10 +38,22 @@ public class TimeSeriesController {
|
|||||||
return new ResponseEntity<>(timeSeriesService.getRandomTimeSeries(length), HttpStatus.OK);
|
return new ResponseEntity<>(timeSeriesService.getRandomTimeSeries(length), HttpStatus.OK);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping("getFromString")
|
||||||
|
@ApiOperation("Преобразовать строку с разделителями во временной ряд")
|
||||||
|
public ResponseEntity<TimeSeries> getTimeSeriesFromString(@RequestParam("tsString") String tsString) {
|
||||||
|
return new ResponseEntity<>(timeSeriesService.getTimeSeriesFromString(tsString), HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("timeSeriesToString")
|
||||||
|
@ApiOperation("Преобразовать временной ряд в строку с разделителями")
|
||||||
|
public ResponseEntity<String> getTimeSeriesToString(@RequestBody TimeSeries timeSeries) {
|
||||||
|
return new ResponseEntity<>(timeSeriesService.getTimeSeriesToString(timeSeries), HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
@PostMapping("getForecast")
|
@PostMapping("getForecast")
|
||||||
@ApiOperation("Получить прогноз временного ряда")
|
@ApiOperation("Получить прогноз временного ряда")
|
||||||
public ResponseEntity<Forecast> getForecastTimeSeries(@RequestBody ForecastRequestParams forecastRequestParams) {
|
public ResponseEntity<Forecast> getForecastTimeSeries(@RequestBody ForecastParams forecastParams) throws ModelingException {
|
||||||
return new ResponseEntity<>(timeSeriesService.getForecast(forecastRequestParams.getOriginalTimeSeries(),
|
return new ResponseEntity<>(timeSeriesService.getForecast(forecastParams.getOriginalTimeSeries(),
|
||||||
forecastRequestParams.getCountForecast()), HttpStatus.OK);
|
forecastParams.getCountForecast()), HttpStatus.OK);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,23 +1,31 @@
|
|||||||
package ru.ulstu.models;
|
package ru.ulstu.models;
|
||||||
|
|
||||||
public class Forecast {
|
public class Forecast {
|
||||||
private TimeSeries originalTimeSeries;
|
private Model model;
|
||||||
private TimeSeries forecast;
|
private TimeSeries forecast;
|
||||||
|
|
||||||
public Forecast(TimeSeries originalTimeSeries) {
|
public Forecast(Model model) {
|
||||||
this.originalTimeSeries = originalTimeSeries;
|
this.model = model;
|
||||||
this.forecast = new TimeSeries("Forecast time series of '" + originalTimeSeries.getName() + "'");
|
this.forecast = new TimeSeries("Forecast time series of '" + model.getOriginalTimeSeries().getName() + "'");
|
||||||
}
|
}
|
||||||
|
|
||||||
public TimeSeries getOriginalTimeSeries() {
|
public Model getModel() {
|
||||||
return originalTimeSeries;
|
return model;
|
||||||
}
|
}
|
||||||
|
|
||||||
public TimeSeries getForecast() {
|
public TimeSeries getForecastTimeSeries() {
|
||||||
return forecast;
|
return forecast;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addValue(TimeSeriesValue timeSeriesValue) {
|
public void addValue(TimeSeriesValue timeSeriesValue) {
|
||||||
forecast.addValue(timeSeriesValue);
|
forecast.addValue(timeSeriesValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "Forecast{" +
|
||||||
|
"model=" + model +
|
||||||
|
", forecast=" + forecast +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
package ru.ulstu.models;
|
package ru.ulstu.models;
|
||||||
|
|
||||||
public class ForecastRequestParams {
|
public class ForecastParams {
|
||||||
private TimeSeries originalTimeSeries;
|
private TimeSeries originalTimeSeries;
|
||||||
private int countForecast;
|
private int countForecast;
|
||||||
|
|
27
src/main/java/ru/ulstu/models/Model.java
Normal file
27
src/main/java/ru/ulstu/models/Model.java
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
package ru.ulstu.models;
|
||||||
|
|
||||||
|
public class Model {
|
||||||
|
private TimeSeries originalTimeSeries;
|
||||||
|
private TimeSeries modelTimeSeries;
|
||||||
|
|
||||||
|
public Model(TimeSeries originalTimeSeries) {
|
||||||
|
this.originalTimeSeries = originalTimeSeries;
|
||||||
|
this.modelTimeSeries = new TimeSeries("Model time series of '" + originalTimeSeries.getName() + "'");
|
||||||
|
}
|
||||||
|
|
||||||
|
public TimeSeries getOriginalTimeSeries() {
|
||||||
|
return originalTimeSeries;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TimeSeries getModelTimeSeries() {
|
||||||
|
return modelTimeSeries;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addValue(TimeSeriesValue timeSeriesValue) {
|
||||||
|
modelTimeSeries.addValue(timeSeriesValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addValue(TimeSeriesValue basedOnValue, double value) {
|
||||||
|
modelTimeSeries.getValues().add(new TimeSeriesValue(basedOnValue.getDate(), value));
|
||||||
|
}
|
||||||
|
}
|
@ -24,6 +24,10 @@ public class TimeSeries {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public TimeSeries(List<TimeSeriesValue> values) {
|
||||||
|
this.values = values;
|
||||||
|
}
|
||||||
|
|
||||||
public List<TimeSeriesValue> getValues() {
|
public List<TimeSeriesValue> getValues() {
|
||||||
return values;
|
return values;
|
||||||
}
|
}
|
||||||
@ -59,4 +63,26 @@ public class TimeSeries {
|
|||||||
public int getLength() {
|
public int getLength() {
|
||||||
return values.size();
|
return values.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public TimeSeriesValue getFirstValue() {
|
||||||
|
if ((values.size() > 0)) {
|
||||||
|
return values.get(0);
|
||||||
|
}
|
||||||
|
throw new RuntimeException("Временной ряд пуст");
|
||||||
|
}
|
||||||
|
|
||||||
|
public Double getNumericValue(int t) {
|
||||||
|
if ((values.size() > t) && (t >= 0)) {
|
||||||
|
return values.get(t).getValue();
|
||||||
|
}
|
||||||
|
throw new RuntimeException("Индекс выходит за границы временного ряда");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "TimeSeries{" +
|
||||||
|
"values=" + values +
|
||||||
|
", name='" + name + '\'' +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,23 +3,32 @@ package ru.ulstu.models;
|
|||||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
public class TimeSeriesValue {
|
public class TimeSeriesValue {
|
||||||
private LocalDate date;
|
private LocalDateTime date;
|
||||||
private Double value;
|
private Double value;
|
||||||
|
|
||||||
@JsonCreator
|
@JsonCreator
|
||||||
public TimeSeriesValue(@JsonProperty(value="date") LocalDate date, @JsonProperty(value = "value") Double value) {
|
public TimeSeriesValue(@JsonProperty(value = "date") LocalDateTime date, @JsonProperty(value = "value") Double value) {
|
||||||
this.date = date;
|
this.date = date;
|
||||||
this.value = value;
|
this.value = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public LocalDate getDate() {
|
public TimeSeriesValue(LocalDateTime date) {
|
||||||
|
this.date = date;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TimeSeriesValue(Double value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalDateTime getDate() {
|
||||||
return date;
|
return date;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setDate(LocalDate date) {
|
public void setDate(LocalDateTime date) {
|
||||||
this.date = date;
|
this.date = date;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -30,4 +39,23 @@ public class TimeSeriesValue {
|
|||||||
public void setValue(Double value) {
|
public void setValue(Double value) {
|
||||||
this.value = value;
|
this.value = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return value.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
TimeSeriesValue that = (TimeSeriesValue) o;
|
||||||
|
return Objects.equals(date, that.date) &&
|
||||||
|
Objects.equals(value, that.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(date, value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,7 @@
|
|||||||
|
package ru.ulstu.models.exceptions;
|
||||||
|
|
||||||
|
public class ForecastValidateException extends ModelingException {
|
||||||
|
public ForecastValidateException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
package ru.ulstu.models.exceptions;
|
||||||
|
|
||||||
|
public class ModelingException extends Exception {
|
||||||
|
public ModelingException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
package ru.ulstu.models.exceptions;
|
||||||
|
|
||||||
|
public class TimeSeriesValidateException extends ModelingException {
|
||||||
|
public TimeSeriesValidateException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
package ru.ulstu.models.response;
|
||||||
|
|
||||||
|
class ControllerResponse<D, E> {
|
||||||
|
private final D data;
|
||||||
|
private final ControllerResponseError<E> error;
|
||||||
|
|
||||||
|
ControllerResponse(D data) {
|
||||||
|
this.data = data;
|
||||||
|
this.error = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
ControllerResponse(ControllerResponseError<E> error) {
|
||||||
|
this.data = null;
|
||||||
|
this.error = error;
|
||||||
|
}
|
||||||
|
|
||||||
|
public D getData() {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ControllerResponseError<E> getError() {
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
package ru.ulstu.models.response;
|
||||||
|
|
||||||
|
class ControllerResponseError<D> {
|
||||||
|
private final ErrorConstants description;
|
||||||
|
private final D data;
|
||||||
|
|
||||||
|
ControllerResponseError(ErrorConstants description, D data) {
|
||||||
|
this.description = description;
|
||||||
|
this.data = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCode() {
|
||||||
|
return description.getCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMessage() {
|
||||||
|
return description.getMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
public D getData() {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
29
src/main/java/ru/ulstu/models/response/ErrorConstants.java
Normal file
29
src/main/java/ru/ulstu/models/response/ErrorConstants.java
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
package ru.ulstu.models.response;
|
||||||
|
|
||||||
|
public enum ErrorConstants {
|
||||||
|
UNKNOWN(0, "Unknown error"),
|
||||||
|
TIME_SERIES_VALIDATE_ERROR(10, "Некорректный временной ряд"),
|
||||||
|
FORECAST_PARAMS_ERROR(11, "Некорректные параметры для прогнозирования"),
|
||||||
|
HTTP_CLIENT_ERROR(66, "Http client error");
|
||||||
|
|
||||||
|
private final int code;
|
||||||
|
private final String message;
|
||||||
|
|
||||||
|
ErrorConstants(int code, String message) {
|
||||||
|
this.code = code;
|
||||||
|
this.message = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCode() {
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMessage() {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return String.format("%d: %s", code, message);
|
||||||
|
}
|
||||||
|
}
|
15
src/main/java/ru/ulstu/models/response/Response.java
Normal file
15
src/main/java/ru/ulstu/models/response/Response.java
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
package ru.ulstu.models.response;
|
||||||
|
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
|
||||||
|
public class Response<D> extends ResponseEntity<Object> {
|
||||||
|
|
||||||
|
public Response(D data) {
|
||||||
|
super(new ControllerResponse<D, Void>(data), HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Response(ErrorConstants error) {
|
||||||
|
super(new ControllerResponse<Void, Void>(new ControllerResponseError<>(error, null)), HttpStatus.OK);
|
||||||
|
}
|
||||||
|
}
|
11
src/main/java/ru/ulstu/models/response/ResponseExtended.java
Normal file
11
src/main/java/ru/ulstu/models/response/ResponseExtended.java
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package ru.ulstu.models.response;
|
||||||
|
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
|
||||||
|
public class ResponseExtended<E> extends ResponseEntity<Object> {
|
||||||
|
|
||||||
|
public ResponseExtended(ErrorConstants error, E errorData) {
|
||||||
|
super(new ControllerResponse<Void, E>(new ControllerResponseError<E>(error, errorData)), HttpStatus.OK);
|
||||||
|
}
|
||||||
|
}
|
@ -1,19 +1,28 @@
|
|||||||
package ru.ulstu.services;
|
package ru.ulstu.services;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
import ru.ulstu.TimeSeriesUtils;
|
||||||
import ru.ulstu.models.Forecast;
|
import ru.ulstu.models.Forecast;
|
||||||
import ru.ulstu.models.TimeSeries;
|
import ru.ulstu.models.TimeSeries;
|
||||||
import ru.ulstu.models.TimeSeriesValue;
|
import ru.ulstu.models.TimeSeriesValue;
|
||||||
|
import ru.ulstu.models.exceptions.ModelingException;
|
||||||
import ru.ulstu.tsMethods.exponential.NoTrendNoSeason;
|
import ru.ulstu.tsMethods.exponential.NoTrendNoSeason;
|
||||||
|
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
public class TimeSeriesService {
|
public class TimeSeriesService {
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(TimeSeriesService.class);
|
||||||
|
|
||||||
public TimeSeries getRandomTimeSeries(int length) {
|
public TimeSeries getRandomTimeSeries(int length) {
|
||||||
TimeSeries ts = new TimeSeries("Random time series");
|
TimeSeries ts = new TimeSeries("Random time series");
|
||||||
LocalDate dateStart = LocalDate.now().minusDays(length);
|
LocalDateTime dateStart = LocalDateTime.now().minusDays(length);
|
||||||
for (int i = 0; i < length; i++) {
|
for (int i = 0; i < length; i++) {
|
||||||
ts.getValues().add(new TimeSeriesValue(dateStart, Math.random()));
|
ts.getValues().add(new TimeSeriesValue(dateStart, Math.random()));
|
||||||
dateStart = dateStart.plusDays(1);
|
dateStart = dateStart.plusDays(1);
|
||||||
@ -21,10 +30,35 @@ public class TimeSeriesService {
|
|||||||
return ts;
|
return ts;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Forecast getForecast(TimeSeries timeSeries, int countForecast) {
|
public Forecast getForecast(TimeSeries timeSeries, int countPoints) throws ModelingException {
|
||||||
NoTrendNoSeason nn = new NoTrendNoSeason(timeSeries, countForecast);
|
NoTrendNoSeason nn = new NoTrendNoSeason(0.8);
|
||||||
nn.setAlpa(0.1);
|
return nn.getForecast(timeSeries, countPoints);
|
||||||
nn.createModel();
|
}
|
||||||
return nn.getForecastTimeSeries();
|
|
||||||
|
public TimeSeries getTimeSeriesFromString(String tsString) {
|
||||||
|
List<TimeSeriesValue> tsValues = Arrays.stream(tsString.split("\n"))
|
||||||
|
.flatMap(v -> Arrays.stream(v.split(";")))
|
||||||
|
.flatMap(v -> Arrays.stream(v.split(",")))
|
||||||
|
.flatMap(v -> Arrays.stream(v.split("<br>")))
|
||||||
|
.filter(v -> {
|
||||||
|
try {
|
||||||
|
Double.parseDouble(v);
|
||||||
|
return true;
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.map(Double::parseDouble)
|
||||||
|
.map(TimeSeriesValue::new)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
return TimeSeriesUtils.fillDates(new TimeSeries(tsValues));
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTimeSeriesToString(TimeSeries timeSeries) {
|
||||||
|
return timeSeries
|
||||||
|
.getValues()
|
||||||
|
.stream()
|
||||||
|
.map(v -> v.getValue().toString().replaceAll("\\.", ","))
|
||||||
|
.collect(Collectors.joining(";"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,34 +1,111 @@
|
|||||||
package ru.ulstu.tsMethods;
|
package ru.ulstu.tsMethods;
|
||||||
|
|
||||||
|
import ru.ulstu.TimeSeriesUtils;
|
||||||
import ru.ulstu.models.Forecast;
|
import ru.ulstu.models.Forecast;
|
||||||
|
import ru.ulstu.models.Model;
|
||||||
import ru.ulstu.models.TimeSeries;
|
import ru.ulstu.models.TimeSeries;
|
||||||
|
import ru.ulstu.models.TimeSeriesValue;
|
||||||
|
import ru.ulstu.models.exceptions.ForecastValidateException;
|
||||||
|
import ru.ulstu.models.exceptions.ModelingException;
|
||||||
|
import ru.ulstu.models.exceptions.TimeSeriesValidateException;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.time.temporal.ChronoUnit;
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Наиболее общая логика моделировани и прогнозирования временных рядов
|
||||||
|
*/
|
||||||
public abstract class TimeSeriesMethod {
|
public abstract class TimeSeriesMethod {
|
||||||
protected TimeSeries originalTimeSeries;
|
/**
|
||||||
protected TimeSeries modelTimeSeries;
|
* Возвращает модельное представление временного ряда: для тех же точек времени что и в параметре timeSeries
|
||||||
protected Forecast forecastTimeSeries;
|
* строится модель. Количество точек может быть изменено: сокращено при сжатии ряда, увеличено при интерполяции.
|
||||||
protected int countForecast;
|
* Метод является шаблонным, выполняет операции валидации исходного ряда и потом его моделирование
|
||||||
protected Map<Param, Double> parameters = new HashMap<>();
|
*
|
||||||
|
* @param timeSeries исходный временной ряд подлежащий моделированию
|
||||||
protected void init() {
|
* @return модель временного ряда
|
||||||
modelTimeSeries = new TimeSeries("Model time series of '" + originalTimeSeries.getName() + "'");
|
* @throws TimeSeriesValidateException
|
||||||
forecastTimeSeries = new Forecast(originalTimeSeries);
|
*/
|
||||||
|
public Model getModel(TimeSeries timeSeries) throws TimeSeriesValidateException {
|
||||||
|
validateTimeSeries(timeSeries);
|
||||||
|
return getModelOfValidTimeSeries(timeSeries);
|
||||||
}
|
}
|
||||||
|
|
||||||
public TimeSeries getOriginalTimeSeries() {
|
/**
|
||||||
return originalTimeSeries;
|
* Возвращает модельное представление валидного временного ряда: для тех же точек времени что и в параметре timeSeries
|
||||||
|
* строится модель. Количество точек может быть изменено: сокращено при сжатии ряда, увеличено при интерполяции.
|
||||||
|
*
|
||||||
|
* @param timeSeries исходный временной ряд подлежащий моделированию
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
protected abstract Model getModelOfValidTimeSeries(TimeSeries timeSeries);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Выполняет построение прогноза временного ряда. Даты спрогнозированных точек будут сгенерированы по модельным точкам.
|
||||||
|
*
|
||||||
|
* @param model модель временного ряда
|
||||||
|
* @param countPoints количество точек для прогнозирования
|
||||||
|
* @return прогноз временного ряда
|
||||||
|
*/
|
||||||
|
public Forecast getForecast(Model model, int countPoints) throws ModelingException {
|
||||||
|
Forecast forecast = new Forecast(model);
|
||||||
|
forecast = generateEmptyForecastPoints(forecast, countPoints);
|
||||||
|
forecast = makeForecast(forecast);
|
||||||
|
if (!forecast.getForecastTimeSeries().getFirstValue()
|
||||||
|
.equals(forecast.getModel().getModelTimeSeries().getLastValue())) {
|
||||||
|
throw new ForecastValidateException("Первая точка прогноза должна совпадать с последней модельной точкой");
|
||||||
|
}
|
||||||
|
return forecast;
|
||||||
}
|
}
|
||||||
|
|
||||||
public TimeSeries getModelTimeSeries() {
|
/**
|
||||||
return modelTimeSeries;
|
* Выполняет построение прогноза для уже сгенерированных будущих точек временного ряда.
|
||||||
|
*
|
||||||
|
* @param forecast Заготовка прогноза временного ряда с пустыми значениями
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
protected abstract Forecast makeForecast(Forecast forecast);
|
||||||
|
|
||||||
|
protected Forecast generateEmptyForecastPoints(Forecast forecast, int countPointForecast) {
|
||||||
|
long diffMilliseconds = TimeSeriesUtils.getTimeDifferenceInMilliseconds(forecast.getModel().getOriginalTimeSeries());
|
||||||
|
forecast.getForecastTimeSeries()
|
||||||
|
.addValue(new TimeSeriesValue(forecast.getModel().getModelTimeSeries().getLastValue().getDate()));
|
||||||
|
for (int i = 1; i < countPointForecast + 1; i++) {
|
||||||
|
forecast.getForecastTimeSeries()
|
||||||
|
.addValue(new TimeSeriesValue(forecast.getForecastTimeSeries().getValues().get(i - 1).getDate().plus(diffMilliseconds, ChronoUnit.MILLIS)));
|
||||||
|
}
|
||||||
|
return forecast;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Forecast getForecastTimeSeries() {
|
/**
|
||||||
return forecastTimeSeries;
|
* Выполняет построение модели и прогноза временного ряда. Даты спрогнозированных точек будут сгенерированы
|
||||||
|
* по модельным точкам.
|
||||||
|
*
|
||||||
|
* @param timeSeries временной ряда
|
||||||
|
* @param countPoints количество точек для прогнозирования
|
||||||
|
* @return прогноз временного ряда
|
||||||
|
*/
|
||||||
|
public Forecast getForecast(TimeSeries timeSeries, int countPoints) throws ModelingException {
|
||||||
|
validateForecastParams(countPoints);
|
||||||
|
return getForecast(getModel(timeSeries), countPoints);
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract void createModel();
|
private void validateForecastParams(int countPoints) throws ForecastValidateException {
|
||||||
|
if (countPoints < 1) {
|
||||||
|
throw new ForecastValidateException("Количество прогнозных точек должно быть больше 0");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateTimeSeries(TimeSeries timeSeries) throws TimeSeriesValidateException {
|
||||||
|
if (timeSeries == null || timeSeries.isEmpty()) {
|
||||||
|
throw new TimeSeriesValidateException("Временной ряд должен быть не пустым");
|
||||||
|
}
|
||||||
|
if (timeSeries.getLength() < 2) {
|
||||||
|
throw new TimeSeriesValidateException("Временной ряд должен содержать хотя бы 2 точки");
|
||||||
|
}
|
||||||
|
if (timeSeries.getValues().stream().anyMatch(val -> val == null || val.getValue() == null)) {
|
||||||
|
throw new TimeSeriesValidateException("Временной ряд содержит пустые значения");
|
||||||
|
}
|
||||||
|
if (timeSeries.getValues().stream().anyMatch(val -> val.getDate() == null)) {
|
||||||
|
throw new TimeSeriesValidateException("Временной ряд должен иметь отметки времени");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,45 +1,41 @@
|
|||||||
package ru.ulstu.tsMethods.exponential;
|
package ru.ulstu.tsMethods.exponential;
|
||||||
|
|
||||||
|
import ru.ulstu.models.Forecast;
|
||||||
|
import ru.ulstu.models.Model;
|
||||||
import ru.ulstu.models.TimeSeries;
|
import ru.ulstu.models.TimeSeries;
|
||||||
import ru.ulstu.tsMethods.Param;
|
|
||||||
import ru.ulstu.tsMethods.TimeSeriesMethod;
|
import ru.ulstu.tsMethods.TimeSeriesMethod;
|
||||||
|
|
||||||
public class NoTrendNoSeason extends TimeSeriesMethod {
|
public class NoTrendNoSeason extends TimeSeriesMethod {
|
||||||
public NoTrendNoSeason(TimeSeries originalTimeSeries, int countForecast) {
|
private double alpha;
|
||||||
this.originalTimeSeries = originalTimeSeries;
|
|
||||||
this.countForecast = countForecast;
|
public NoTrendNoSeason(double alpha) {
|
||||||
if (originalTimeSeries.isEmpty()) {
|
this.alpha = alpha;
|
||||||
throw new RuntimeException("Time series must not empty");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void init() {
|
protected Model getModelOfValidTimeSeries(TimeSeries timeSeries) {
|
||||||
super.init();
|
Model model = new Model(timeSeries);
|
||||||
modelTimeSeries.addValue(originalTimeSeries.getValues().get(0));
|
model.addValue(timeSeries.getFirstValue());
|
||||||
forecastTimeSeries.addValue(originalTimeSeries.getValues().get(0));
|
//выполняется проход модели по сглаживанию
|
||||||
parameters.put(Param.ALPHA, 1.0);
|
for (int t = 1; t < timeSeries.getValues().size(); t++) {
|
||||||
|
model.addValue(timeSeries.getValues().get(t),
|
||||||
|
(1 - alpha) * timeSeries.getNumericValue(t)
|
||||||
|
+ alpha * model.getModelTimeSeries().getValues().get(t - 1).getValue());
|
||||||
}
|
}
|
||||||
|
return model;
|
||||||
public void setAlpa(double value) {
|
|
||||||
parameters.put(Param.ALPHA, value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void createModel() {
|
protected Forecast makeForecast(Forecast forecast) {
|
||||||
init();
|
forecast.getForecastTimeSeries()
|
||||||
double e;
|
.getFirstValue()
|
||||||
|
.setValue(forecast.getModel().getModelTimeSeries().getLastValue().getValue());
|
||||||
//выполняется проход модели по сглаживанию и прогнозированию countPointForecast точек
|
for (int t = 1; t < forecast.getForecastTimeSeries().getLength(); t++) {
|
||||||
for (int t = 0; t < originalTimeSeries.getValues().size() - 1 + countForecast; t++) {
|
forecast.getForecastTimeSeries()
|
||||||
// пока не дошли до конца ряда - сглаживаем, иначе строим прогноз
|
.getValues()
|
||||||
if (t < originalTimeSeries.getValues().size()) {
|
.get(t)
|
||||||
e = originalTimeSeries.getValues().get(t).getValue() - forecastTimeSeries.getForecast().getValues().get(t).getValue();
|
.setValue(forecast.getForecastTimeSeries().getValues().get(t - 1).getValue());
|
||||||
} else {
|
|
||||||
e = 0;
|
|
||||||
}
|
|
||||||
modelTimeSeries.addValue(modelTimeSeries.getLastValue(), modelTimeSeries.getValues().get(t).getValue() + parameters.get(Param.ALPHA) * e); // уровень
|
|
||||||
forecastTimeSeries.addValue(modelTimeSeries.getLastValue()); // прогноз
|
|
||||||
}
|
}
|
||||||
|
return forecast;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user