fi.hsl.parkandride.core.domain.prediction.RelativizedAverageOfPreviousWeeksPredictor.java Source code

Java tutorial

Introduction

Here is the source code for fi.hsl.parkandride.core.domain.prediction.RelativizedAverageOfPreviousWeeksPredictor.java

Source

// Copyright  2015 HSL <https://www.hsl.fi>
// This program is dual-licensed under the EUPL v1.2 and AGPLv3 licenses.

package fi.hsl.parkandride.core.domain.prediction;

import fi.hsl.parkandride.back.ListUtil;
import fi.hsl.parkandride.core.back.PredictionRepository;
import fi.hsl.parkandride.core.domain.Utilization;
import org.joda.time.DateTime;
import org.joda.time.Minutes;
import org.joda.time.ReadablePeriod;
import org.joda.time.Weeks;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;
import java.util.stream.Collectors;

public class RelativizedAverageOfPreviousWeeksPredictor implements Predictor {

    private static final Logger log = LoggerFactory.getLogger(RelativizedAverageOfPreviousWeeksPredictor.class);

    public static final List<ReadablePeriod> LOOKBACK_PERIODS = Arrays.asList(Weeks.weeks(1), Weeks.weeks(2),
            Weeks.weeks(3));
    public static final Minutes LOOKBACK_MINUTES = Minutes.minutes(120);

    public static final String TYPE = "relative-average-of-previous-weeks";

    @Override
    public String getType() {
        return TYPE;
    }

    @Override
    public List<Prediction> predict(PredictorState state, UtilizationHistory history, int maxCapacity) {
        Optional<Utilization> latest = history.getLatest();
        if (!latest.isPresent()) {
            return Collections.emptyList();
        }
        DateTime now = state.latestUtilization = latest.get().timestamp;
        final UtilizationHistory inMemoryHistory = new UtilizationHistoryList(
                history.getRange(now.minusWeeks(3).minus(LOOKBACK_MINUTES), now));

        List<List<Prediction>> groupedByWeek = LOOKBACK_PERIODS.stream().map(offset -> {
            DateTime start = now.minus(offset);
            DateTime end = start.plus(PredictionRepository.PREDICTION_WINDOW);
            Optional<Utilization> utilizationAtReferenceTime = inMemoryHistory.getAt(start);
            if (!utilizationAtReferenceTime.isPresent()) {
                return null;
            }

            Integer spacesAvailableAtReferenceTime = utilizationAtReferenceTime.get().spacesAvailable;
            List<Utilization> utilizations = inMemoryHistory.getRange(start, end);
            return utilizations.stream().map(u -> new Prediction(u.timestamp.plus(offset),
                    u.spacesAvailable - spacesAvailableAtReferenceTime)).collect(Collectors.toList());
        }).filter(Objects::nonNull).collect(Collectors.toList());

        List<List<Prediction>> groupedByTimeOfDay = ListUtil.transpose(groupedByWeek);

        return groupedByTimeOfDay.stream().map(predictions -> reduce(predictions, latest.get().spacesAvailable,
                getUtilizationMultiplier(now, inMemoryHistory), maxCapacity)).collect(Collectors.toList());
    }

    private Double getUtilizationMultiplier(DateTime now, UtilizationHistory inMemoryHistory) {
        final List<Utilization> recentUtilizations = inMemoryHistory.getRange(now.minus(LOOKBACK_MINUTES), now);
        Double recentUtilizationArea = Math.max(1, calculateAreaAverageByDataPoints(recentUtilizations));
        Double referenceUtilizationAreaAverage = Math.max(1,
                LOOKBACK_PERIODS.stream().map(offset -> now.minus(offset))
                        .map(referenceTime -> inMemoryHistory.getRange(referenceTime.minus(LOOKBACK_MINUTES),
                                referenceTime))
                        .filter(utilizationList -> !utilizationList.isEmpty())
                        .mapToDouble(utilizationList -> calculateAreaAverageByDataPoints(utilizationList)).average()
                        .orElseGet(() -> recentUtilizationArea));
        return Math.max(1, recentUtilizationArea / referenceUtilizationAreaAverage);
    }

    private double calculateAreaAverageByDataPoints(List<Utilization> utilizationList) {
        int referenceSpaces = utilizationList.get(utilizationList.size() - 1).spacesAvailable;
        return utilizationList.stream().mapToDouble(u -> Math.abs(u.spacesAvailable - referenceSpaces)).average()
                .getAsDouble();
    }

    private Prediction reduce(List<Prediction> predictions, int spacesAvailableCorrection,
            double utilizationMultiplier, int maxCapacity) {
        DateTime timestamp = predictions.get(0).timestamp;
        if (!predictions.stream().map(p -> p.timestamp).allMatch(timestamp::equals)) {
            log.warn("Something went wrong. Not all predictions have the same timestamp: {}", predictions);
        }
        int spacesAvailable = (int) Math.round(utilizationMultiplier
                * predictions.stream().mapToDouble(u -> u.spacesAvailable).average().getAsDouble());
        final int predictedSpacesAvailable = Math.min(maxCapacity,
                Math.max(0, spacesAvailable + spacesAvailableCorrection));
        return new Prediction(timestamp, predictedSpacesAvailable);
    }
}