146 lines
6.8 KiB
Java
146 lines
6.8 KiB
Java
/*
|
||
*
|
||
* * Copyright (C) 2021 Anton Romanov - All Rights Reserved
|
||
* * You may use, distribute and modify this code, please write to: romanov73@gmail.com.
|
||
*
|
||
*
|
||
*/
|
||
|
||
package ru.ulstu.tsMethods;
|
||
|
||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||
import ru.ulstu.TimeSeriesUtils;
|
||
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.time.temporal.ChronoUnit;
|
||
import java.util.List;
|
||
|
||
/**
|
||
* Наиболее общая логика моделировании и прогнозирования временных рядов
|
||
*/
|
||
public abstract class TimeSeriesMethod {
|
||
@JsonIgnore
|
||
protected TimeSeries originalTimeSeries;
|
||
@JsonIgnore
|
||
private TimeSeries model;
|
||
|
||
public abstract TimeSeriesMethod createFor(TimeSeries originalTimeSeries);
|
||
|
||
/**
|
||
* Возвращает модельное представление временного ряда: для тех же точек времени что и в параметре timeSeries
|
||
* строится модель. Количество точек может быть изменено: сокращено при сжатии ряда, увеличено при интерполяции.
|
||
* Метод является шаблонным, выполняет операции валидации исходного ряда и потом его моделирование
|
||
*
|
||
* @throws ModelingException генерируется, если есть проблемы моделирования при задании параметров
|
||
*/
|
||
protected void makeModel() throws ModelingException {
|
||
validateTimeSeries();
|
||
model = getModelOfValidTimeSeries();
|
||
}
|
||
|
||
/**
|
||
* Возвращает модельное представление валидного временного ряда: для тех же точек времени что и в параметре timeSeries
|
||
* строится модель. Количество точек может быть изменено: сокращено при сжатии ряда, увеличено при интерполяции.
|
||
*
|
||
* @return модельное представление временного ряда
|
||
*/
|
||
protected abstract TimeSeries getModelOfValidTimeSeries() throws ModelingException;
|
||
|
||
/**
|
||
* Выполняет построение прогноза временного ряда. Даты спрогнозированных точек будут сгенерированы по модельным точкам.
|
||
*
|
||
* @param countPoints количество точек для прогнозирования
|
||
* @return прогноз временного ряда
|
||
*/
|
||
public TimeSeries getForecastWithValidParams(int countPoints) throws ModelingException {
|
||
TimeSeries forecast = generateEmptyForecastPoints(originalTimeSeries, countPoints);
|
||
getModel();
|
||
forecast.getFirstValue().setValue(originalTimeSeries.getLastValue().getValue());
|
||
forecast = makeForecast(forecast);
|
||
return forecast;
|
||
}
|
||
|
||
/**
|
||
* Выполняет построение прогноза для уже сгенерированных будущих точек временного ряда.
|
||
*
|
||
* @param forecast Заготовка прогноза временного ряда с пустыми значениями
|
||
* @return временной ряд прогноза
|
||
*/
|
||
protected abstract TimeSeries makeForecast(TimeSeries forecast) throws ModelingException;
|
||
|
||
protected TimeSeries generateEmptyForecastPoints(TimeSeries modelTimeSeries, int countPointForecast) {
|
||
long diffMilliseconds = TimeSeriesUtils.getTimeDifferenceInMilliseconds(originalTimeSeries);
|
||
TimeSeries forecast = new TimeSeries("Forecast of " + originalTimeSeries.getName());
|
||
forecast.addValue(new TimeSeriesValue(modelTimeSeries.getLastValue().getDate()));
|
||
for (int i = 1; i < countPointForecast + 1; i++) {
|
||
forecast.addValue(new TimeSeriesValue(forecast.getValues().get(i - 1).getDate().plus(diffMilliseconds, ChronoUnit.MILLIS)));
|
||
}
|
||
return forecast;
|
||
}
|
||
|
||
/**
|
||
* Выполняет построение модели и прогноза временного ряда. Даты спрогнозированных точек будут сгенерированы
|
||
* по модельным точкам.
|
||
*
|
||
* @param countPoints количество точек для прогнозирования
|
||
* @return прогноз временного ряда
|
||
*/
|
||
public TimeSeries getForecast(int countPoints) throws ModelingException {
|
||
validateForecastParams(countPoints);
|
||
return getForecastWithValidParams(countPoints);
|
||
}
|
||
|
||
private void validateForecastParams(int countPoints) throws ForecastValidateException {
|
||
if (countPoints < 1) {
|
||
throw new ForecastValidateException("Количество прогнозных точек должно быть больше 0");
|
||
}
|
||
}
|
||
|
||
protected void validateTimeSeries() throws ModelingException {
|
||
if (originalTimeSeries == null || originalTimeSeries.isEmpty()) {
|
||
throw new TimeSeriesValidateException("Временной ряд должен быть не пустым");
|
||
}
|
||
if (originalTimeSeries.getLength() < 2) {
|
||
throw new TimeSeriesValidateException("Временной ряд должен содержать хотя бы 2 точки");
|
||
}
|
||
if (originalTimeSeries.getValues().stream().anyMatch(val -> val == null || val.getValue() == null)) {
|
||
throw new TimeSeriesValidateException("Временной ряд содержит пустые значения");
|
||
}
|
||
if (originalTimeSeries.getValues().stream().anyMatch(val -> val.getDate() == null)) {
|
||
throw new TimeSeriesValidateException("Временной ряд должен иметь отметки времени");
|
||
}
|
||
validateAdditionalParams();
|
||
}
|
||
|
||
protected void validateAdditionalParams() throws ModelingException {
|
||
|
||
}
|
||
|
||
public TimeSeries getModel() throws ModelingException {
|
||
//TODO: what if always run?
|
||
//if (model == null) {
|
||
makeModel();
|
||
//}
|
||
return model;
|
||
}
|
||
|
||
public boolean canMakeForecast(int countPoints) {
|
||
try {
|
||
validateTimeSeries();
|
||
validateForecastParams(countPoints);
|
||
} catch (ModelingException ex) {
|
||
return false;
|
||
}
|
||
return true;
|
||
}
|
||
|
||
@JsonIgnore
|
||
public abstract List<MethodParameter> getAvailableParameters();
|
||
|
||
public abstract TimeSeriesMethod setAvailableParameters(List<TimeSeriesMethodParamValue> parameters);
|
||
}
|