#2 -- first implementations of date generator, modeling and forecasting
This commit is contained in:
parent
3cd0d77e0c
commit
84a228b06d
@ -13,6 +13,7 @@ import ru.ulstu.configurations.ApiConfiguration;
|
||||
import ru.ulstu.models.Forecast;
|
||||
import ru.ulstu.models.ForecastParams;
|
||||
import ru.ulstu.models.TimeSeries;
|
||||
import ru.ulstu.models.exceptions.ModelingException;
|
||||
import ru.ulstu.services.TimeSeriesService;
|
||||
|
||||
@RestController
|
||||
@ -39,7 +40,7 @@ public class TimeSeriesController {
|
||||
|
||||
@PostMapping("getForecast")
|
||||
@ApiOperation("Получить прогноз временного ряда")
|
||||
public ResponseEntity<Forecast> getForecastTimeSeries(@RequestBody ForecastParams forecastParams) {
|
||||
public ResponseEntity<Forecast> getForecastTimeSeries(@RequestBody ForecastParams forecastParams) throws ModelingException {
|
||||
return new ResponseEntity<>(timeSeriesService.getForecast(forecastParams.getOriginalTimeSeries(),
|
||||
forecastParams.getCountForecast()), HttpStatus.OK);
|
||||
}
|
||||
|
@ -1,23 +1,31 @@
|
||||
package ru.ulstu.models;
|
||||
|
||||
public class Forecast {
|
||||
private TimeSeries originalTimeSeries;
|
||||
private Model model;
|
||||
private TimeSeries forecast;
|
||||
|
||||
public Forecast(TimeSeries originalTimeSeries) {
|
||||
this.originalTimeSeries = originalTimeSeries;
|
||||
this.forecast = new TimeSeries("Forecast time series of '" + originalTimeSeries.getName() + "'");
|
||||
public Forecast(Model model) {
|
||||
this.model = model;
|
||||
this.forecast = new TimeSeries("Forecast time series of '" + model.getOriginalTimeSeries().getName() + "'");
|
||||
}
|
||||
|
||||
public TimeSeries getOriginalTimeSeries() {
|
||||
return originalTimeSeries;
|
||||
public Model getModel() {
|
||||
return model;
|
||||
}
|
||||
|
||||
public TimeSeries getForecast() {
|
||||
public TimeSeries getForecastTimeSeries() {
|
||||
return forecast;
|
||||
}
|
||||
|
||||
public void addValue(TimeSeriesValue timeSeriesValue) {
|
||||
forecast.addValue(timeSeriesValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Forecast{" +
|
||||
"model=" + model +
|
||||
", forecast=" + forecast +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,10 @@
|
||||
package ru.ulstu.models;
|
||||
|
||||
public class ModelTimeSeries {
|
||||
public class Model {
|
||||
private TimeSeries originalTimeSeries;
|
||||
private TimeSeries modelTimeSeries;
|
||||
|
||||
public ModelTimeSeries(TimeSeries originalTimeSeries) {
|
||||
public Model(TimeSeries originalTimeSeries) {
|
||||
this.originalTimeSeries = originalTimeSeries;
|
||||
this.modelTimeSeries = new TimeSeries("Model time series of '" + originalTimeSeries.getName() + "'");
|
||||
}
|
||||
@ -22,6 +22,6 @@ public class ModelTimeSeries {
|
||||
}
|
||||
|
||||
public void addValue(TimeSeriesValue basedOnValue, double value) {
|
||||
modelTimeSeries.getValues().add(new TimeSeriesValue(basedOnValue.getDate().plusDays(1), value));
|
||||
modelTimeSeries.getValues().add(new TimeSeriesValue(basedOnValue.getDate(), value));
|
||||
}
|
||||
}
|
@ -59,4 +59,26 @@ public class TimeSeries {
|
||||
public int getLength() {
|
||||
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,28 @@ package ru.ulstu.models;
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Objects;
|
||||
|
||||
public class TimeSeriesValue {
|
||||
private LocalDate date;
|
||||
private LocalDateTime date;
|
||||
private Double value;
|
||||
|
||||
@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.value = value;
|
||||
}
|
||||
|
||||
public LocalDate getDate() {
|
||||
public TimeSeriesValue(LocalDateTime date) {
|
||||
this.date = date;
|
||||
}
|
||||
|
||||
public LocalDateTime getDate() {
|
||||
return date;
|
||||
}
|
||||
|
||||
public void setDate(LocalDate date) {
|
||||
public void setDate(LocalDateTime date) {
|
||||
this.date = date;
|
||||
}
|
||||
|
||||
@ -30,4 +35,23 @@ public class TimeSeriesValue {
|
||||
public void setValue(Double 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);
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
package ru.ulstu.models.exceptions;
|
||||
|
||||
public class TimeSeriesValidateException extends Exception {
|
||||
public class TimeSeriesValidateException extends ModelingException {
|
||||
public TimeSeriesValidateException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
@ -6,10 +6,10 @@ import org.springframework.stereotype.Service;
|
||||
import ru.ulstu.models.Forecast;
|
||||
import ru.ulstu.models.TimeSeries;
|
||||
import ru.ulstu.models.TimeSeriesValue;
|
||||
import ru.ulstu.models.exceptions.TimeSeriesValidateException;
|
||||
import ru.ulstu.models.exceptions.ModelingException;
|
||||
import ru.ulstu.tsMethods.exponential.NoTrendNoSeason;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
|
||||
@Service
|
||||
@ -18,7 +18,7 @@ public class TimeSeriesService {
|
||||
|
||||
public TimeSeries getRandomTimeSeries(int length) {
|
||||
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++) {
|
||||
ts.getValues().add(new TimeSeriesValue(dateStart, Math.random()));
|
||||
dateStart = dateStart.plusDays(1);
|
||||
@ -26,15 +26,8 @@ public class TimeSeriesService {
|
||||
return ts;
|
||||
}
|
||||
|
||||
public Forecast getForecast(TimeSeries timeSeries, int countForecast) {
|
||||
try {
|
||||
NoTrendNoSeason nn = new NoTrendNoSeason(timeSeries);
|
||||
nn.setAlpa(0.1);
|
||||
nn.createModel();
|
||||
return nn.getForecast();
|
||||
} catch (TimeSeriesValidateException ex) {
|
||||
LOGGER.error("Некорректная инициализация метода моделирования", ex);
|
||||
return null;
|
||||
}
|
||||
public Forecast getForecast(TimeSeries timeSeries, int countPoints) throws ModelingException {
|
||||
NoTrendNoSeason nn = new NoTrendNoSeason(0.1);
|
||||
return nn.getForecast(timeSeries, countPoints);
|
||||
}
|
||||
}
|
||||
|
@ -1,45 +1,129 @@
|
||||
package ru.ulstu.tsMethods;
|
||||
|
||||
import ru.ulstu.models.Forecast;
|
||||
import ru.ulstu.models.ModelTimeSeries;
|
||||
import ru.ulstu.models.Model;
|
||||
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.util.Map;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
|
||||
/**
|
||||
* Наиболее общая логика моделировани и прогнозирования временных рядов
|
||||
*/
|
||||
public abstract class TimeSeriesMethod {
|
||||
protected TimeSeries originalTimeSeries;
|
||||
protected ModelTimeSeries model;
|
||||
protected Forecast forecast;
|
||||
protected int countForecast;
|
||||
protected Map<Param, Double> parameters = new HashMap<>();
|
||||
|
||||
protected TimeSeriesMethod(TimeSeries originalTimeSeries) throws TimeSeriesValidateException {
|
||||
validateTimeSeries(originalTimeSeries);
|
||||
this.originalTimeSeries = originalTimeSeries;
|
||||
model = new ModelTimeSeries(originalTimeSeries);
|
||||
forecast = new Forecast(originalTimeSeries);
|
||||
/**
|
||||
* Возвращает модельное представление временного ряда: для тех же точек времени что и в параметре timeSeries
|
||||
* строится модель. Количество точек может быть изменено: сокращено при сжатии ряда, увеличено при интерполяции.
|
||||
* Метод является шаблонным, выполняет операции валидации исходного ряда и потом его моделирование
|
||||
*
|
||||
* @param timeSeries исходный временной ряд подлежащий моделированию
|
||||
* @return модель временного ряда
|
||||
* @throws TimeSeriesValidateException
|
||||
*/
|
||||
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);
|
||||
|
||||
public ModelTimeSeries getModel() {
|
||||
return model;
|
||||
/**
|
||||
* Выполняет построение прогноза временного ряда. Даты спрогнозированных точек будут сгенерированы по модельным точкам.
|
||||
*
|
||||
* @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("Первая точка прогноза должна совпадать с последней модельной точкой");
|
||||
}
|
||||
|
||||
public Forecast getForecast() {
|
||||
return forecast;
|
||||
}
|
||||
|
||||
public abstract void createModel();
|
||||
/**
|
||||
* Выполняет построение прогноза ждя уже сгенерированных будущих точек временного ряда.
|
||||
*
|
||||
* @param forecast Заготовка прогноза временного ряда с пустыми значениями
|
||||
* @return
|
||||
*/
|
||||
protected abstract Forecast makeForecast(Forecast forecast);
|
||||
|
||||
protected Forecast generateEmptyForecastPoints(Forecast forecast, int countPointForecast) {
|
||||
long diffMilliseconds = getTimeDifferenceInMilliseconds(forecast);
|
||||
LocalDateTime lastTimeSeriesDateTime = forecast
|
||||
.getModel()
|
||||
.getOriginalTimeSeries()
|
||||
.getLastValue()
|
||||
.getDate()
|
||||
.atZone(ZoneId.systemDefault())
|
||||
.toLocalDateTime();
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Вычисляет среднее значение между датами исходного временного ряда
|
||||
*
|
||||
* @param forecast объект, содержащий результат прогнозирования
|
||||
* @return средняя разница между датами исходного временного ряда в миллисекундах
|
||||
*/
|
||||
protected long getTimeDifferenceInMilliseconds(Forecast forecast) {
|
||||
long diffMilliseconds = 0;
|
||||
for (int i = 1; i < forecast.getModel().getOriginalTimeSeries().getLength(); i++) {
|
||||
diffMilliseconds += forecast.getModel().getOriginalTimeSeries().getValues().get(i - 1).getDate()
|
||||
.until(forecast.getModel().getOriginalTimeSeries().getValues().get(i).getDate(), ChronoUnit.MILLIS);
|
||||
}
|
||||
return diffMilliseconds / (forecast.getModel().getOriginalTimeSeries().getLength() - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Выполняет построение модели и прогноза временного ряда. Даты спрогнозированных точек будут сгенерированы
|
||||
* по модельным точкам.
|
||||
*
|
||||
* @param timeSeries временной ряда
|
||||
* @param countPoints количество точек для прогнозирования
|
||||
* @return прогноз временного ряда
|
||||
*/
|
||||
public Forecast getForecast(TimeSeries timeSeries, int countPoints) throws ModelingException {
|
||||
validateForecastParams(countPoints);
|
||||
return getForecast(getModel(timeSeries), countPoints);
|
||||
}
|
||||
|
||||
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("Временной ряд содержит пустые значения");
|
||||
}
|
||||
|
@ -1,12 +1,45 @@
|
||||
package ru.ulstu.tsMethods.exponential;
|
||||
|
||||
import ru.ulstu.models.Forecast;
|
||||
import ru.ulstu.models.Model;
|
||||
import ru.ulstu.models.TimeSeries;
|
||||
import ru.ulstu.models.exceptions.TimeSeriesValidateException;
|
||||
import ru.ulstu.tsMethods.Param;
|
||||
import ru.ulstu.tsMethods.TimeSeriesMethod;
|
||||
|
||||
public class NoTrendNoSeason extends TimeSeriesMethod {
|
||||
public NoTrendNoSeason(TimeSeries originalTimeSeries) throws TimeSeriesValidateException {
|
||||
private double alpha;
|
||||
|
||||
public NoTrendNoSeason(double alpha) {
|
||||
this.alpha = alpha;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Model getModelOfValidTimeSeries(TimeSeries timeSeries) {
|
||||
Model model = new Model(timeSeries);
|
||||
model.addValue(timeSeries.getFirstValue());
|
||||
//выполняется проход модели по сглаживанию
|
||||
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;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Forecast makeForecast(Forecast forecast) {
|
||||
forecast.getForecastTimeSeries()
|
||||
.getFirstValue()
|
||||
.setValue(forecast.getModel().getModelTimeSeries().getLastValue().getValue());
|
||||
for (int t = 1; t < forecast.getForecastTimeSeries().getLength(); t++) {
|
||||
forecast.getForecastTimeSeries()
|
||||
.getValues()
|
||||
.get(t)
|
||||
.setValue(alpha * forecast.getForecastTimeSeries().getValues().get(t - 1).getValue());
|
||||
}
|
||||
return forecast;
|
||||
}
|
||||
|
||||
/* public NoTrendNoSeason(TimeSeries originalTimeSeries) throws TimeSeriesValidateException {
|
||||
super(originalTimeSeries);
|
||||
model.addValue(originalTimeSeries.getValues().get(0));
|
||||
forecast.addValue(originalTimeSeries.getValues().get(0));
|
||||
@ -34,4 +67,5 @@ public class NoTrendNoSeason extends TimeSeriesMethod {
|
||||
forecast.addValue(model.getModelTimeSeries().getLastValue()); // прогноз
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user