/* * * * 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 getAvailableParameters(); public abstract TimeSeriesMethod setAvailableParameters(List parameters); }