diff --git a/experimental/src/main/java/org/matsim/contrib/greedo/DisappointmentAnalyzer.java b/experimental/src/main/java/org/matsim/contrib/greedo/DisappointmentAnalyzer.java new file mode 100644 index 0000000..dc641d1 --- /dev/null +++ b/experimental/src/main/java/org/matsim/contrib/greedo/DisappointmentAnalyzer.java @@ -0,0 +1,162 @@ +/* + * Copyright 2020 Gunnar Flötteröd + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * contact: gunnar.flotterod@gmail.com + * + */ +package org.matsim.contrib.greedo; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; + +import org.matsim.api.core.v01.Id; +import org.matsim.api.core.v01.population.Person; +import org.matsim.contrib.greedo.datastructures.SpaceTimeCounts; +import org.matsim.contrib.greedo.datastructures.SpaceTimeIndicators; + +import floetteroed.utilities.DynamicData; +import floetteroed.utilities.Tuple; + +/** + * + * @author Gunnar Flötteröd + * + */ +public class DisappointmentAnalyzer { + + // -------------------- MEMBERS -------------------- + + private final GreedoConfigGroup conf; + + private final Map, Double> _B; + + // TODO Find more representative statistics. + // private Double lastNaiveIndividualAbsE = null; + // private Double lastEstimIndividualAbsE = null; + + // -------------------- CONSTRUCTION -------------------- + + public DisappointmentAnalyzer(final GreedoConfigGroup conf) { + this.conf = conf; + this._B = new LinkedHashMap<>(); + } + + // -------------------- INTERNALS -------------------- + + private double predictVariability(final Id personId, final SpaceTimeCounts> interactions) { + double result = 0; + for (Map.Entry, Integer>, Double> entry : interactions.entriesView()) { + final Id loc = entry.getKey().getA(); + final double interaction = entry.getValue(); + result += interaction * this._B.getOrDefault(loc, 0.0); + } + return result; + } + + // -------------------- IMPLEMENTATION -------------------- + + public void update(final Map, SpaceTimeIndicators>> currentIndicators, + final Map, SpaceTimeIndicators>> anticipatedIndicators, + final Map, Double> personId2realizedUtilityChange, + final Map, Double> personId2anticipatedUtilityChange, final Set> replannerIds, + final Map, SpaceTimeCounts>> personId2interactions, final double stepSize) { + +// this.lastEstimIndividualAbsE = 0.0; +// this.lastNaiveIndividualAbsE = 0.0; + + final Map, Double> deltaB = new LinkedHashMap<>(); + + double etaNumerator = 0.0; // TODO revisit step size logic + // vector in eta denominator equals deltaDiagonal2 + ... other ^2 terms + + for (Id personId : personId2realizedUtilityChange.keySet()) { + + final SpaceTimeCounts> interactions = personId2interactions.get(personId); + final double variability = this.predictVariability(personId, interactions); + + final double anticipatedUtilityChange; + if (replannerIds.contains(personId)) { + anticipatedUtilityChange = personId2anticipatedUtilityChange.get(personId); + } else { + anticipatedUtilityChange = 0.0; + } + + final double naiveEn = anticipatedUtilityChange - personId2realizedUtilityChange.get(personId); + + // TODO >>> Used this code before >>> +// double en; +// if (this.variabilityConfig.getUseAbsoluteVariability()) { +// throw new RuntimeException("unsupported"); +// } else { +// if (this.variabilityConfig.getTruncateImprovement()) { +// throw new RuntimeException("unsupported"); +// } else { +// en = this.beta * anticipatedUtilityChange - (this.variabilityConfig.getEstimateStationaryB() ? 0.0 +// : personId2realizedUtilityChange.get(personId)) - variability; +// } +// } + // TODO <<< Used this code before <<< + final double en = (anticipatedUtilityChange - variability) - personId2realizedUtilityChange.get(personId); + +// this.lastEstimIndividualAbsE += Math.abs(en); + etaNumerator += en * en; + + for (Map.Entry, Integer>, Double> entry : interactions.entriesView()) { + final Id loc = entry.getKey().getA(); + double interaction = entry.getValue(); + deltaB.put(loc, deltaB.getOrDefault(loc, 0.0) + en * interaction); + } + + } + + double etaDenominator = 0.0; // TODO stream + for (double val : deltaB.values()) { + etaDenominator += val * val; + } + + if (etaDenominator >= 1e-8) { + final double eta = stepSize * etaNumerator / etaDenominator; + for (Map.Entry, Double> entry : deltaB.entrySet()) { + final Id loc = entry.getKey(); + double newBValue = this._B.getOrDefault(loc, 0.0) + eta * entry.getValue(); + if (this.conf.getNonnegativeB()) { + newBValue = Math.max(0.0, newBValue); + } + this._B.put(loc, newBValue); + } + } + } + +// public Double getLastNaiveAbsE() { +// return this.lastNaiveIndividualAbsE; +// } + +// public Double getLastEstimAbsE() { +// return this.lastEstimIndividualAbsE; +// } + + // TODO Stick to a map; take out dynamics. + public DynamicData> getB() { + final DynamicData> result = new DynamicData<>(this.conf.newTimeDiscretization()); + for (Map.Entry, Double> entry : this._B.entrySet()) { + for (int timeBin = 0; timeBin < result.getBinCnt(); timeBin++) { + result.put(entry.getKey(), timeBin, entry.getValue()); + } + } + return result; + } +} diff --git a/experimental/src/main/java/org/matsim/contrib/greedo/GreedoConfigGroup.java b/experimental/src/main/java/org/matsim/contrib/greedo/GreedoConfigGroup.java index f42b454..227ae27 100644 --- a/experimental/src/main/java/org/matsim/contrib/greedo/GreedoConfigGroup.java +++ b/experimental/src/main/java/org/matsim/contrib/greedo/GreedoConfigGroup.java @@ -133,6 +133,50 @@ public double getAgeWeight(final double age) { return Math.pow(1.0 / (1.0 + age), this.getAgeWeightExponent()); } + // ---------- VARIABILITY ANALYSIS minPhysLinkSize_veh ---------- + + // TODO This could be used even in the slot definition. + + private double minPhysLinkSize_veh = 0.0; + + @StringGetter("minPhysLinkSize_veh") + public double getMinPhysLinkSize_veh() { + return this.minPhysLinkSize_veh; + } + + @StringSetter("minPhysLinkSize_veh") + public void setMinPhysLinkSize_veh(final double minPhysLinkSize_veh) { + this.minPhysLinkSize_veh = minPhysLinkSize_veh; + } + + // -------------------- VARIABILITY ANALYSIS: nonnegativeB -------------------- + + private boolean nonnegativeB = false; + + @StringGetter("nonnegativeB") + public boolean getNonnegativeB() { + return this.nonnegativeB; + } + + @StringSetter("nonnegativeB") + public void setNonnegativeB(final boolean nonnegativeB) { + this.nonnegativeB = nonnegativeB; + } + + // --------------- VARIABILITY ANALYSIS: selfInteraction --------------- + + private boolean selfInteraction = false; + + @StringGetter("selfInteraction") + public boolean getSelfInteraction() { + return this.selfInteraction; + } + + @StringSetter("selfInteraction") + public void setSelfInteraction(final boolean selfInteraction) { + this.selfInteraction = selfInteraction; + } + // --------------- adaptiveMSADenominatorIncreaseIfSuccess --------------- private double adaptiveMSADenominatorIncreaseIfSuccess = 0.1; diff --git a/experimental/src/main/java/org/matsim/contrib/greedo/LogDataWrapper.java b/experimental/src/main/java/org/matsim/contrib/greedo/LogDataWrapper.java index 5880d0d..cea0bab 100644 --- a/experimental/src/main/java/org/matsim/contrib/greedo/LogDataWrapper.java +++ b/experimental/src/main/java/org/matsim/contrib/greedo/LogDataWrapper.java @@ -35,13 +35,13 @@ public class LogDataWrapper { private final Utilities.SummaryStatistics utilitySummaryStatistics; - private final ReplannerIdentifier.SummaryStatistics replanningSummaryStatistics; + private final ReplannerIdentifierTII.SummaryStatistics replanningSummaryStatistics; private final int iteration; public LogDataWrapper(final GreedoConfigGroup greedoConfig, final Utilities.SummaryStatistics utilitySummaryStatistics, - final ReplannerIdentifier.SummaryStatistics replanningSummaryStatistics, final int iteration) { + final ReplannerIdentifierTII.SummaryStatistics replanningSummaryStatistics, final int iteration) { this.greedoConfig = greedoConfig; this.utilitySummaryStatistics = utilitySummaryStatistics; this.replanningSummaryStatistics = replanningSummaryStatistics; @@ -73,7 +73,7 @@ public int getIteration() { return this.iteration; } - public ReplannerIdentifier.SummaryStatistics getReplanningSummaryStatistics() { + public ReplannerIdentifierTII.SummaryStatistics getReplanningSummaryStatistics() { return this.replanningSummaryStatistics; } } diff --git a/experimental/src/main/java/org/matsim/contrib/greedo/ReplannerIdentifier.java b/experimental/src/main/java/org/matsim/contrib/greedo/ReplannerIdentifierTII.java similarity index 64% rename from experimental/src/main/java/org/matsim/contrib/greedo/ReplannerIdentifier.java rename to experimental/src/main/java/org/matsim/contrib/greedo/ReplannerIdentifierTII.java index bce7f4b..3386f14 100644 --- a/experimental/src/main/java/org/matsim/contrib/greedo/ReplannerIdentifier.java +++ b/experimental/src/main/java/org/matsim/contrib/greedo/ReplannerIdentifierTII.java @@ -19,8 +19,10 @@ */ package org.matsim.contrib.greedo; +import static org.matsim.contrib.greedo.datastructures.SlotUsageUtilities.addIndicatorsToTotalsTreatingNullAsZero; +import static org.matsim.contrib.greedo.datastructures.SlotUsageUtilities.newTotals; + import java.util.ArrayList; -import java.util.Collection; import java.util.Collections; import java.util.LinkedHashMap; import java.util.LinkedHashSet; @@ -31,19 +33,20 @@ import org.apache.log4j.Logger; import org.matsim.api.core.v01.Id; +import org.matsim.api.core.v01.network.Network; import org.matsim.api.core.v01.population.Person; +import org.matsim.contrib.greedo.datastructures.SpaceTimeCounts; import org.matsim.contrib.greedo.datastructures.SpaceTimeIndicators; import floetteroed.utilities.DynamicData; import floetteroed.utilities.DynamicDataUtils; -import floetteroed.utilities.TimeDiscretization; /** * * @author Gunnar Flötteröd * */ -class ReplannerIdentifier { +class ReplannerIdentifierTII { // -------------------- CONSTANTS -------------------- @@ -55,114 +58,70 @@ class ReplannerIdentifier { private final Map, SpaceTimeIndicators>> personId2physicalSlotUsage; private final Map, SpaceTimeIndicators>> personId2hypothetialSlotUsage; - private final DynamicData> currentWeightedCounts; - private final DynamicData> upcomingWeightedCounts; private final Map, Double> personId2hypotheticalUtilityChange; private final Map, Double> personId2currentUtility; - private final double sumOfWeightedCountDifferences2; + private final DynamicData> _B; + private final double lambdaBar; - private final double beta; + + private DynamicData> presenceResiduals; + private DynamicData> changeResiduals; + + private Map, SpaceTimeCounts>> personId2interactions = new LinkedHashMap<>(); + private Map, Double> personId2Dn0 = new LinkedHashMap<>(); + private Map, Double> personId2Tn = new LinkedHashMap<>(); private SummaryStatistics lastExpectations = null; // -------------------- CONSTRUCTION -------------------- - ReplannerIdentifier(// final Double unconstrainedBeta, - final GreedoConfigGroup greedoConfig, final int iteration, + ReplannerIdentifierTII(final GreedoConfigGroup greedoConfig, final Map, SpaceTimeIndicators>> personId2physicalSlotUsage, final Map, SpaceTimeIndicators>> personId2hypotheticalSlotUsage, final Map, Double> personId2hypotheticalUtilityChange, - final Map, Double> personId2currentUtility, - // final MovingWindowAverage realizedToTargetLambdaRatio, - final double betaScale) { + final Map, Double> personId2currentUtility, final DynamicData> _B, + final double lambdaBar) { + this._B = _B; + this.lambdaBar = lambdaBar; this.greedoConfig = greedoConfig; + this.personId2physicalSlotUsage = personId2physicalSlotUsage; this.personId2hypothetialSlotUsage = personId2hypotheticalSlotUsage; this.personId2hypotheticalUtilityChange = personId2hypotheticalUtilityChange; - final double totalUtilityChange = personId2hypotheticalUtilityChange.values().stream() - .mapToDouble(utlChange -> utlChange).sum(); this.personId2currentUtility = personId2currentUtility; - log.info("number of entries (persons) in different maps:"); - log.info(" personId2physicalSlotUsage.size() = " + personId2physicalSlotUsage.size()); - log.info(" personId2hypothetialSlotUsage.size() = " + personId2hypotheticalSlotUsage.size()); - log.info(" personId2currentUtility.size() = " + personId2currentUtility.size()); - log.info(" personId2hypotheticalUtilityChange.size() = " + personId2hypotheticalUtilityChange.size()); - - this.currentWeightedCounts = newWeightedCounts(this.greedoConfig.newTimeDiscretization(), - this.personId2physicalSlotUsage.values(), true, true); - this.upcomingWeightedCounts = newWeightedCounts(this.greedoConfig.newTimeDiscretization(), - this.personId2hypothetialSlotUsage.values(), true, true); - this.sumOfWeightedCountDifferences2 = DynamicDataUtils.sumOfDifferences2(this.currentWeightedCounts, - this.upcomingWeightedCounts); - - log.info("sumOfWeightedCountDifference2 = " + this.sumOfWeightedCountDifferences2 - + " based on the following data:"); - log.info(" currentWeightedCounts2 = " + DynamicDataUtils.sumOfEntries2(this.currentWeightedCounts)); - log.info(" upcomingWeightedCounts2 = " + DynamicDataUtils.sumOfEntries2(this.upcomingWeightedCounts)); - - // Logger.getLogger(this.getClass()).warn("Overriding beta!!!"); - // if ((unconstrainedBeta != null) && (unconstrainedBeta > 0.0)) { - // this.beta = unconstrainedBeta; - // this.lambdaBar = 0.5 * unconstrainedBeta * totalUtilityChange - // / Math.max(sumOfWeightedCountDifferences2, 1e-8); - // } else - // { - // final double ratio; - // if (realizedToTargetLambdaRatio.getTotalCount() > 0) { - // ratio = realizedToTargetLambdaRatio.average(); - // } else { - // ratio = 1.0; - // } - this.lambdaBar = greedoConfig.getMSAReplanningRate(iteration); - // this.beta = 2.0 * (this.lambdaBar / ratio) * sumOfWeightedCountDifferences2 - // / Math.max(totalUtilityChange, 1e-8); - this.beta = betaScale * 2.0 * this.lambdaBar * this.sumOfWeightedCountDifferences2 - / Math.max(totalUtilityChange, 1e-8); - - log.info("beta = " + this.beta + " based on the following data:"); - log.info(" betaScale = " + betaScale); - log.info(" lambdaBar = " + this.lambdaBar); - log.info(" sumOfWeightedCountDifferences2 = " + this.sumOfWeightedCountDifferences2); - log.info(" totalUtilityChange = " + totalUtilityChange); - - // } - } - - // -------------------- INTERNALS -------------------- - - private static void addIndicatorsToTotalsTreatingNullAsZero(final DynamicData weightedTotals, - final SpaceTimeIndicators indicators, final double factor, final boolean useParticleWeight, - final boolean useSlotWeight) { - if (indicators != null) { - for (int bin = 0; bin < indicators.getTimeBinCnt(); bin++) { - for (SpaceTimeIndicators.Visit visit : indicators.getVisits(bin)) { - weightedTotals.add(visit.spaceObject, bin, factor * visit.weight(useParticleWeight, useSlotWeight)); - } - } + this.log.info("number of entries (persons) in different maps:"); + this.log.info(" personId2physicalSlotUsage.size() = " + personId2physicalSlotUsage.size()); + this.log.info(" personId2hypothetialSlotUsage.size() = " + personId2hypotheticalSlotUsage.size()); + this.log.info(" personId2currentUtility.size() = " + personId2currentUtility.size()); + this.log.info(" personId2hypotheticalUtilityChange.size() = " + personId2hypotheticalUtilityChange.size()); + + { + final DynamicData> currentWeightedCounts = newTotals(this.greedoConfig.newTimeDiscretization(), + this.personId2physicalSlotUsage.values(), true, true); + final DynamicData> upcomingWeightedCounts = newTotals(this.greedoConfig.newTimeDiscretization(), + this.personId2hypothetialSlotUsage.values(), true, true); + this.changeResiduals = DynamicDataUtils.newWeightedSum(upcomingWeightedCounts, +lambdaBar, + currentWeightedCounts, -lambdaBar); } - } - private static DynamicData newWeightedCounts(final TimeDiscretization timeDiscr, - final Collection> allIndicators, final boolean useParticleWeight, - final boolean useSlotWeight) { - final DynamicData weightedCounts = new DynamicData(timeDiscr); - for (SpaceTimeIndicators indicators : allIndicators) { - addIndicatorsToTotalsTreatingNullAsZero(weightedCounts, indicators, 1.0, useParticleWeight, useSlotWeight); + { + final DynamicData> currentUnweightedCounts = newTotals(this.greedoConfig.newTimeDiscretization(), + this.personId2physicalSlotUsage.values(), true, false); + final DynamicData> upcomingUnweightedCounts = newTotals(this.greedoConfig.newTimeDiscretization(), + this.personId2hypothetialSlotUsage.values(), true, false); + this.presenceResiduals = DynamicDataUtils.newWeightedSum(currentUnweightedCounts, (1.0 - lambdaBar), + upcomingUnweightedCounts, lambdaBar); } - return weightedCounts; } // -------------------- IMPLEMENTATION -------------------- - Set> drawReplanners() { - - final DynamicData> interactionResiduals = DynamicDataUtils.newDifference(this.upcomingWeightedCounts, - this.currentWeightedCounts, this.lambdaBar); + Set> drawReplanners(Network network, final Map, Double> personId2cn) { final DynamicData> weightedReplannerCountDifferences = new DynamicData<>( this.greedoConfig.newTimeDiscretization()); @@ -177,20 +136,31 @@ Set> drawReplanners() { double replannerSizeSum = 0.0; double nonReplannerSizeSum = 0.0; + this.personId2Dn0.clear(); + this.personId2Tn.clear(); + final Set> replanners = new LinkedHashSet<>(); final List> allPersonIdsShuffled = new ArrayList<>(this.personId2hypotheticalUtilityChange.keySet()); Collections.shuffle(allPersonIdsShuffled); + int replanned = 0; for (Id personId : allPersonIdsShuffled) { + this.log.info("replanning " + (++replanned) + " of " + allPersonIdsShuffled.size()); - final ScoreUpdater> scoreUpdater = new ScoreUpdater<>(this.personId2physicalSlotUsage.get(personId), - this.personId2hypothetialSlotUsage.get(personId), this.lambdaBar, this.beta, interactionResiduals, - this.personId2hypotheticalUtilityChange.get(personId), this.greedoConfig.getCorrectAgentSize()); + final ScoreUpdaterTII> scoreUpdater = new ScoreUpdaterTII>( + this.personId2physicalSlotUsage.get(personId), this.personId2hypothetialSlotUsage.get(personId), + this.lambdaBar, this.personId2hypotheticalUtilityChange.get(personId), this._B, + this.presenceResiduals, this.changeResiduals, this.greedoConfig, network); - final boolean isReplanner = this.greedoConfig.getReplannerIdentifierRecipe().isReplanner(personId, - scoreUpdater.getScoreChangeIfOne(), scoreUpdater.getScoreChangeIfZero(), + boolean isReplanner = this.greedoConfig.getReplannerIdentifierRecipe().isReplanner(personId, + scoreUpdater.getScoreChangeIfOne(), + scoreUpdater.getScoreChangeIfZero() - personId2cn.getOrDefault(personId, 0.0), this.personId2currentUtility.get(personId), this.personId2hypotheticalUtilityChange.get(personId)); + this.personId2interactions.put(personId, scoreUpdater.getRealizedInteractions(isReplanner)); + this.personId2Dn0.put(personId, scoreUpdater.Dn0); + this.personId2Tn.put(personId, scoreUpdater.Tn); + if (isReplanner) { replanners.add(personId); addIndicatorsToTotalsTreatingNullAsZero(weightedReplannerCountDifferences, @@ -212,6 +182,7 @@ Set> drawReplanners() { this.personId2hypothetialSlotUsage.get(personId), +1.0, true, true); addIndicatorsToTotalsTreatingNullAsZero(weightedNonReplannerCountDifferences, this.personId2physicalSlotUsage.get(personId), -1.0, true, true); + nonReplannerUtilityChangeSum += this.personId2hypotheticalUtilityChange.get(personId); if (this.personId2physicalSlotUsage.containsKey(personId)) { nonReplannerSizeSum += this.personId2physicalSlotUsage.get(personId).size(); @@ -244,9 +215,8 @@ Set> drawReplanners() { personId2similarity.put(personId, similarityNumerator / this.personId2hypotheticalUtilityChange.size()); } - this.lastExpectations = new SummaryStatistics(this.lambdaBar, this.beta, replannerUtilityChangeSum, - nonReplannerUtilityChangeSum, this.sumOfWeightedCountDifferences2, - DynamicDataUtils.sumOfEntries2(weightedReplannerCountDifferences), + this.lastExpectations = new SummaryStatistics(this.lambdaBar, null, replannerUtilityChangeSum, + nonReplannerUtilityChangeSum, null, DynamicDataUtils.sumOfEntries2(weightedReplannerCountDifferences), DynamicDataUtils.sumOfEntries2(weightedNonReplannerCountDifferences), DynamicDataUtils.sumOfEntries2(locationWeightedReplannerCountDifferences), replannerSizeSum, nonReplannerSizeSum, replanners.size(), @@ -256,6 +226,18 @@ Set> drawReplanners() { return replanners; } + Map, SpaceTimeCounts>> getPersonId2InteractionsView() { + return Collections.unmodifiableMap(this.personId2interactions); + } + + public Map, Double> getPersonId2Dn0() { + return Collections.unmodifiableMap(personId2Dn0); + } + + public Map, Double> getPersonId2Tn() { + return Collections.unmodifiableMap(personId2Tn); + } + // -------------------- INNER CLASS -------------------- SummaryStatistics getSummaryStatistics(final Set> replanners, @@ -328,25 +310,6 @@ public Double getSumOfAnticipatedUtilityChanges() { } } - // public Double getSumOfUtilityChangesGivenUniformReplanning() { - // final Double sumOfUtilityChanges = this.getSumOfUtilityChanges(); - // if ((sumOfUtilityChanges != null) && (this.lambdaBar != null)) { - // return this.lambdaBar * sumOfUtilityChanges; - // } else { - // return null; - // } - // } - - // public Double getSumOfWeightedCountDifferences2() { - // if ((this.sumOfWeightedReplannerCountDifferences2 != null) - // && (this.sumOfWeightedNonReplannerCountDifferences2 != null)) { - // return (this.sumOfWeightedReplannerCountDifferences2 + - // this.sumOfWeightedNonReplannerCountDifferences2); - // } else { - // return null; - // } - // } - public Integer getNumberOfReplanningCandidates() { if ((this.numberOfReplanners != null) && (this.numberOfNonReplanners != null)) { return (this.numberOfReplanners + this.numberOfNonReplanners); diff --git a/experimental/src/main/java/org/matsim/contrib/greedo/ScoreUpdater.java b/experimental/src/main/java/org/matsim/contrib/greedo/ScoreUpdater.java deleted file mode 100644 index 05455a8..0000000 --- a/experimental/src/main/java/org/matsim/contrib/greedo/ScoreUpdater.java +++ /dev/null @@ -1,152 +0,0 @@ -/* - * Copyright 2018 Gunnar Flötteröd - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * contact: gunnar.flotterod@gmail.com - * - */ -package org.matsim.contrib.greedo; - -import java.util.Map; - -import org.matsim.contrib.greedo.datastructures.SpaceTimeCounts; -import org.matsim.contrib.greedo.datastructures.SpaceTimeIndicators; - -import floetteroed.utilities.DynamicData; -import floetteroed.utilities.Tuple; - -/** - * The "score" this class refers to is the anticipated change of the search - * acceleration objective function resulting from setting a single agent's - * (possibly space-weighted) 0/1 re-planning indicator. - * - * Implements the score used in the greedy heuristic of Merz, P. and Freisleben, - * B. (2002). "Greedy and local search heuristics for unconstrained binary - * quadratic programming." Journal of Heuristics 8:197–213. - * - * @author Gunnar Flötteröd - * - * @param L - * the space coordinate type - * - */ -class ScoreUpdater { - - // -------------------- MEMBERS -------------------- - - private final DynamicData interactionResiduals; - - private final SpaceTimeCounts individualWeightedChanges; - - private /* final */ double scoreChangeIfZero; - - private /* final */ double scoreChangeIfOne; - - private boolean residualsUpdated = false; - - // -------------------- CONSTRUCTION -------------------- - - ScoreUpdater(final SpaceTimeIndicators currentIndicators, final SpaceTimeIndicators upcomingIndicators, - final double meanLambda, final double beta, final DynamicData interactionResiduals, - final double individualUtilityChange, final boolean correctAgentSize) { - - /* - * One has to go beyond 0/1 indicator arithmetics in the following because the - * same vehicle may enter the same link multiple times during one time bin. - */ - this.individualWeightedChanges = new SpaceTimeCounts(upcomingIndicators, true, true); - this.individualWeightedChanges.subtract(new SpaceTimeCounts<>(currentIndicators, true, true)); - - /* - * Update the interaction residuals. - */ - this.interactionResiduals = interactionResiduals; - for (Map.Entry, Double> entry : this.individualWeightedChanges.entriesView()) { - final L spaceObj = entry.getKey().getA(); - final int timeBin = entry.getKey().getB(); - final double weightedIndividualChange = entry.getValue(); - double oldResidual = this.interactionResiduals.getBinValue(spaceObj, timeBin); - double newResidual = oldResidual - meanLambda * weightedIndividualChange; - this.interactionResiduals.put(spaceObj, timeBin, newResidual); - } - - /* - * Compute score (approximated objective function in the solution heuristic): - * - * interaction(lambda) - beta * lambda * individualUtilityChange. - */ - double sumOfWeightedIndividualChanges2 = 0.0; - double sumOfWeightedIndividualChangesTimesInteractionResiduals = 0.0; - for (Map.Entry, Double> entry : this.individualWeightedChanges.entriesView()) { - final L spaceObj = entry.getKey().getA(); - final int timeBin = entry.getKey().getB(); - final double weightedIndividualChange = entry.getValue(); - sumOfWeightedIndividualChanges2 += weightedIndividualChange * weightedIndividualChange; - sumOfWeightedIndividualChangesTimesInteractionResiduals += weightedIndividualChange - * this.interactionResiduals.getBinValue(spaceObj, timeBin); - } - - final double scoreIfOne = this.expectedInteraction(1.0, sumOfWeightedIndividualChanges2, - sumOfWeightedIndividualChangesTimesInteractionResiduals) - beta * 1.0 * individualUtilityChange; - final double scoreIfMean = this.expectedInteraction(meanLambda, sumOfWeightedIndividualChanges2, - sumOfWeightedIndividualChangesTimesInteractionResiduals) - beta * meanLambda * individualUtilityChange; - final double scoreIfZero = this.expectedInteraction(0.0, sumOfWeightedIndividualChanges2, - sumOfWeightedIndividualChangesTimesInteractionResiduals) - beta * 0.0 * individualUtilityChange; - - this.scoreChangeIfOne = scoreIfOne - scoreIfMean; - this.scoreChangeIfZero = scoreIfZero - scoreIfMean; - - if (correctAgentSize) { - final double agentSizeCorrection = (-1.0) * this.individualWeightedChanges.sumOfSquareEntries(); - this.scoreChangeIfOne += (1.0 - meanLambda) * agentSizeCorrection; - this.scoreChangeIfZero += (0.0 - meanLambda) * agentSizeCorrection; - } - } - - // -------------------- INTERNALS -------------------- - - private double expectedInteraction(final double lambda, final double sumOfWeightedIndividualChanges2, - final double sumOfWeightedIndividualChangesTimesInteractionResiduals) { - return lambda * lambda * sumOfWeightedIndividualChanges2 - + 2.0 * lambda * sumOfWeightedIndividualChangesTimesInteractionResiduals; - } - - // -------------------- IMPLEMENTATION -------------------- - - void updateResiduals(final double newLambda) { - if (this.residualsUpdated) { - throw new RuntimeException("Residuals have already been updated."); - } - this.residualsUpdated = true; - - for (Map.Entry, Double> entry : this.individualWeightedChanges.entriesView()) { - final L spaceObj = entry.getKey().getA(); - final int timeBin = entry.getKey().getB(); - final double oldResidual = this.interactionResiduals.getBinValue(spaceObj, timeBin); - final double newResidual = oldResidual + newLambda * entry.getValue(); - this.interactionResiduals.put(spaceObj, timeBin, newResidual); - } - } - - // -------------------- GETTERS -------------------- - - double getScoreChangeIfOne() { - return this.scoreChangeIfOne; - } - - double getScoreChangeIfZero() { - return this.scoreChangeIfZero; - } -} diff --git a/experimental/src/main/java/org/matsim/contrib/greedo/ScoreUpdaterTII.java b/experimental/src/main/java/org/matsim/contrib/greedo/ScoreUpdaterTII.java new file mode 100644 index 0000000..52d4a3b --- /dev/null +++ b/experimental/src/main/java/org/matsim/contrib/greedo/ScoreUpdaterTII.java @@ -0,0 +1,259 @@ +/* + * Copyright 2018 Gunnar Flötteröd + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * contact: gunnar.flotterod@gmail.com + * + */ +package org.matsim.contrib.greedo; + +import java.util.Map; + +import org.matsim.api.core.v01.network.Link; +import org.matsim.api.core.v01.network.Network; +import org.matsim.contrib.greedo.datastructures.SpaceTimeCounts; +import org.matsim.contrib.greedo.datastructures.SpaceTimeIndicators; + +import floetteroed.utilities.DynamicData; +import floetteroed.utilities.Tuple; + +/** + * The "score" this class refers to is the anticipated change of the search + * acceleration objective function resulting from setting a single agent's + * (possibly space-weighted) 0/1 re-planning indicator. + * + * Implements the score used in the greedy heuristic of Merz, P. and Freisleben, + * B. (2002). "Greedy and local search heuristics for unconstrained binary + * quadratic programming." Journal of Heuristics 8:197–213. + * + * @author Gunnar Flötteröd + * + * @param L the space coordinate type + * + */ +class ScoreUpdaterTII { + + // -------------------- MEMBERS -------------------- + + private final GreedoConfigGroup greedoConfig; + + private final DynamicData presenceResiduals; + private final DynamicData changeResiduals; + + private final SpaceTimeCounts unweightedNewXn; + private final SpaceTimeCounts unweightedOldXn; + + private final SpaceTimeCounts unweightedDeltaXn; + private final SpaceTimeCounts weightedDeltaXn; + + // Interaction i = x_ni * deltaY_i, without abs(.) operation. + private final SpaceTimeCounts interactionsWhenStaying; + private final SpaceTimeCounts interactionsWhenMoving; + + private DynamicData _B; + + private final double scoreChangeIfZero; + private final double scoreChangeIfOne; + + private final double disappointmentIfZero; + private final double disappointmentIfOne; + + private boolean residualsUpdated = false; + + public Double Tn = null; + public Double Dn0 = null; + + // -------------------- CONSTRUCTION -------------------- + + ScoreUpdaterTII(final SpaceTimeIndicators currentIndicators, final SpaceTimeIndicators upcomingIndicators, + final double meanLambda, final double individualUtilityChange, final DynamicData _B, + final DynamicData presenceResiduals, final DynamicData changeResiduals, + final GreedoConfigGroup greedoConfig, final Network network) { + + this.greedoConfig = greedoConfig; + this._B = _B; + + /* + * One has to go beyond 0/1 indicator arithmetics in the following because the + * same particle may enter the same slot multiple times during one time bin. + */ + this.unweightedNewXn = new SpaceTimeCounts(upcomingIndicators, true, false); + this.unweightedOldXn = new SpaceTimeCounts(currentIndicators, true, false); + + this.unweightedDeltaXn = new SpaceTimeCounts(upcomingIndicators, true, false); + this.unweightedDeltaXn.subtract(new SpaceTimeCounts(currentIndicators, true, false)); + + this.weightedDeltaXn = new SpaceTimeCounts(upcomingIndicators, true, true); + this.weightedDeltaXn.subtract(new SpaceTimeCounts<>(currentIndicators, true, true)); + + /* + * Update the residuals. + */ + this.presenceResiduals = presenceResiduals; + for (Map.Entry, Double> entry : this.unweightedDeltaXn.entriesView()) { + final L spaceObj = entry.getKey().getA(); + final int timeBin = entry.getKey().getB(); + final double oldResidual = this.presenceResiduals.getBinValue(spaceObj, timeBin); + final double newResidual = oldResidual - meanLambda * entry.getValue(); + this.presenceResiduals.put(spaceObj, timeBin, newResidual); + } + + this.changeResiduals = changeResiduals; + for (Map.Entry, Double> entry : this.weightedDeltaXn.entriesView()) { + final L spaceObj = entry.getKey().getA(); + final int timeBin = entry.getKey().getB(); + final double oldResidual = this.changeResiduals.getBinValue(spaceObj, timeBin); + final double newResidual = oldResidual - meanLambda * entry.getValue(); + this.changeResiduals.put(spaceObj, timeBin, newResidual); + } + + /* + * Interaction terms (new). + */ + this.interactionsWhenMoving = new SpaceTimeCounts(); + this.interactionsWhenStaying = new SpaceTimeCounts(); + for (int timeBin = 0; timeBin < _B.getBinCnt(); timeBin++) { + if (upcomingIndicators != null) { + for (SpaceTimeIndicators.Visit visit : upcomingIndicators.getVisits(timeBin)) { + if (this.largeEnough(visit.spaceObject, network, greedoConfig.getMinPhysLinkSize_veh())) { + this.interactionsWhenMoving.add(visit.spaceObject, timeBin, + changeResiduals.getBinValue(visit.spaceObject, timeBin)); + } + } + } + if (currentIndicators != null) { + for (SpaceTimeIndicators.Visit visit : currentIndicators.getVisits(timeBin)) { + if (this.largeEnough(visit.spaceObject, network, greedoConfig.getMinPhysLinkSize_veh())) { + this.interactionsWhenStaying.add(visit.spaceObject, timeBin, + changeResiduals.getBinValue(visit.spaceObject, timeBin)); + } + } + } + } + + /* + * Finally, the scores. + */ + + this.disappointmentIfOne = this.disappointment(1.0); + this.disappointmentIfZero = this.disappointment(0.0); + + this.Dn0 = this.disappointmentIfZero; + this.Tn = this.disappointmentIfOne - this.disappointmentIfZero; + + final double scoreIfOne = -(1.0 * individualUtilityChange - this.disappointmentIfOne); + final double scoreIfMean = -(meanLambda * individualUtilityChange - this.disappointment(meanLambda)); + final double scoreIfZero = -(0.0 * individualUtilityChange - this.disappointmentIfZero); + + this.scoreChangeIfOne = scoreIfOne - scoreIfMean; + this.scoreChangeIfZero = scoreIfZero - scoreIfMean; + } + + private boolean largeEnough(final L linkId, final Network net, final double minPhysVehSize_veh) { + final Link link = net.getLinks().get(linkId); // TODO works only for road traffic! + final double size_veh = link.getLength() * link.getNumberOfLanes() / 7.5; + return (size_veh >= minPhysVehSize_veh); + } + + // -------------------- INTERNALS -------------------- + + private double disappointment(final double lambda) { + double result = 0; + + for (Map.Entry, Double> entry : this.unweightedNewXn.entriesView()) { + final L spaceObj = entry.getKey().getA(); + final int timeBin = entry.getKey().getB(); + double variability = this.changeResiduals.getBinValue(spaceObj, timeBin); + if (this.greedoConfig.getSelfInteraction()) { + variability += lambda * this.weightedDeltaXn.get(spaceObj, timeBin); + } + result += lambda * entry.getValue() * this._B.getBinValue(spaceObj, timeBin) * variability; + } + + for (Map.Entry, Double> entry : this.unweightedOldXn.entriesView()) { + final L spaceObj = entry.getKey().getA(); + final int timeBin = entry.getKey().getB(); + double variability = this.changeResiduals.getBinValue(spaceObj, timeBin); + if (this.greedoConfig.getSelfInteraction()) { + variability += lambda * this.weightedDeltaXn.get(spaceObj, timeBin); + } + result += (1.0 - lambda) * entry.getValue() * this._B.getBinValue(spaceObj, timeBin) * variability; + } + + return result; + } + + // -------------------- IMPLEMENTATION -------------------- + + void updateResiduals(final double newLambda) { + if (this.residualsUpdated) { + throw new RuntimeException("Residuals have already been updated."); + } + this.residualsUpdated = true; + + for (Map.Entry, Double> entry : this.unweightedDeltaXn.entriesView()) { + final L spaceObj = entry.getKey().getA(); + final int timeBin = entry.getKey().getB(); + final double oldResidual = this.presenceResiduals.getBinValue(spaceObj, timeBin); + final double newResidual = oldResidual + newLambda * entry.getValue(); + this.presenceResiduals.put(spaceObj, timeBin, newResidual); + } + + for (Map.Entry, Double> entry : this.weightedDeltaXn.entriesView()) { + final L spaceObj = entry.getKey().getA(); + final int timeBin = entry.getKey().getB(); + final double oldResidual = this.changeResiduals.getBinValue(spaceObj, timeBin); + final double newResidual = oldResidual + newLambda * entry.getValue(); + this.changeResiduals.put(spaceObj, timeBin, newResidual); + } + } + + // -------------------- GETTERS -------------------- + + double getScoreChangeIfOne() { + return this.scoreChangeIfOne; + } + + double getScoreChangeIfZero() { + return this.scoreChangeIfZero; + } + + double getDisappointmentIfOne() { + return this.disappointmentIfOne; + } + + double getDisappointmentIfZero() { + return this.disappointmentIfZero; + } + + SpaceTimeCounts getRealizedInteractions(final boolean isReplanner) { + final SpaceTimeCounts result; + if (isReplanner) { + result = this.interactionsWhenMoving; + if (this.greedoConfig.getSelfInteraction()) { + for (Map.Entry, Double> entry : this.unweightedNewXn.entriesView()) { + final L spaceObj = entry.getKey().getA(); + final int timeBin = entry.getKey().getB(); + result.add(spaceObj, timeBin, this.weightedDeltaXn.get(spaceObj, timeBin)); + } + throw new RuntimeException("unsupported feature: self interaction"); + } + } else { + result = this.interactionsWhenStaying; + } + return result; + } + +} diff --git a/experimental/src/main/java/org/matsim/contrib/greedo/Utilities.java b/experimental/src/main/java/org/matsim/contrib/greedo/Utilities.java index c62af12..268a19c 100644 --- a/experimental/src/main/java/org/matsim/contrib/greedo/Utilities.java +++ b/experimental/src/main/java/org/matsim/contrib/greedo/Utilities.java @@ -76,12 +76,14 @@ Double getLastRealizedUtilityChange() { class SummaryStatistics { final Map, Double> personId2expectedUtilityChange; + final Map, Double> personId2realizedUtilityChange; // TODO NEW final Map, Double> personId2experiencedUtility; final public Double realizedUtilitySum; final public Double realizedUtilityChangeSum; private SummaryStatistics() { final Map, Double> personId2expectedUtilityChange = new LinkedHashMap<>(); + final Map, Double> personId2realizedUtilityChange = new LinkedHashMap<>(); // TODO NEW final Map, Double> personId2experiencedUtility = new LinkedHashMap<>(); Double realizedUtilitySum = null; Double realizedUtilityChangeSum = null; @@ -92,6 +94,10 @@ private SummaryStatistics() { if (entry.getLastExpectedUtilityChange() != null) { personId2expectedUtilityChange.put(personId, entry.getLastExpectedUtilityChange()); } + if (entry.getLastRealizedUtilityChange() != null) { + // TODO NEW + personId2realizedUtilityChange.put(personId, entry.getLastRealizedUtilityChange()); + } if (entry.getLastRealizedUtility() != null) { if (realizedUtilitySum == null) { realizedUtilitySum = entry.getLastRealizedUtility(); @@ -108,6 +114,7 @@ private SummaryStatistics() { } } this.personId2expectedUtilityChange = Collections.unmodifiableMap(personId2expectedUtilityChange); + this.personId2realizedUtilityChange = Collections.unmodifiableMap(personId2realizedUtilityChange); // TODO NEW this.personId2experiencedUtility = Collections.unmodifiableMap(personId2experiencedUtility); this.realizedUtilitySum = realizedUtilitySum; this.realizedUtilityChangeSum = realizedUtilityChangeSum; diff --git a/experimental/src/main/java/org/matsim/contrib/greedo/WireGreedoIntoMATSimControlerListener.java b/experimental/src/main/java/org/matsim/contrib/greedo/WireGreedoIntoMATSimControlerListener.java index d70a804..36638f4 100644 --- a/experimental/src/main/java/org/matsim/contrib/greedo/WireGreedoIntoMATSimControlerListener.java +++ b/experimental/src/main/java/org/matsim/contrib/greedo/WireGreedoIntoMATSimControlerListener.java @@ -21,6 +21,7 @@ import java.io.File; import java.util.LinkedHashMap; +import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -29,9 +30,11 @@ import javax.inject.Singleton; +import org.apache.log4j.Logger; import org.matsim.api.core.v01.Id; import org.matsim.api.core.v01.population.Person; import org.matsim.api.core.v01.population.Plan; +import org.matsim.contrib.greedo.datastructures.SpaceTimeCounts; import org.matsim.contrib.greedo.datastructures.SpaceTimeIndicators; import org.matsim.contrib.greedo.listeners.SlotUsageListener; import org.matsim.contrib.greedo.logging.AsymptoticAgeLogger; @@ -50,6 +53,7 @@ import org.matsim.contrib.greedo.logging.NormalizedWeightedNonReplannerCountDifferences2; import org.matsim.contrib.greedo.logging.NormalizedWeightedReplannerCountDifferences2; import org.matsim.contrib.greedo.logging.ReplanningRecipe; +import org.matsim.contrib.greedo.stationaryreplanningregulation.StationaryReplanningRegulator; import org.matsim.contrib.ier.replannerselection.ReplannerSelector; import org.matsim.core.config.ConfigUtils; import org.matsim.core.controler.MatsimServices; @@ -60,6 +64,7 @@ import com.google.inject.Inject; import com.google.inject.Provider; +import floetteroed.utilities.math.BasicStatistics; import floetteroed.utilities.statisticslogging.StatisticsWriter; import floetteroed.utilities.statisticslogging.TimeStampStatistic; @@ -83,7 +88,8 @@ public class WireGreedoIntoMATSimControlerListener implements Provider hypotheticalSlotUsageListeners = new LinkedList<>(); - private double betaScale = 1.0; + // private double betaScale = 1.0; + private double beta = 1.0; private Plans lastPhysicalPopulationState = null; - private ReplannerIdentifier.SummaryStatistics lastReplanningSummaryStatistics = new ReplannerIdentifier.SummaryStatistics(); + private ReplannerIdentifierTII.SummaryStatistics lastReplanningSummaryStatistics = new ReplannerIdentifierTII.SummaryStatistics(); + + private Map, SpaceTimeIndicators>> previousCurrentSlotUsages = null; + private Map, SpaceTimeIndicators>> previousAnticipatedSlotUsages = null; + private Set> previousReplannerIds = null; + + // private DynamicData> weightedReplannerDeltaX = null; + + private Map, SpaceTimeCounts>> personId2interactions = null; + + public Map, Double> personId2Dn0 = null; + public Map, Double> personId2Tn = null; // -------------------- CONSTRUCTION -------------------- @@ -109,8 +127,10 @@ public WireGreedoIntoMATSimControlerListener(final MatsimServices services) { this.physicalSlotUsageListener = new SlotUsageListener(this.greedoConfig.newTimeDiscretization(), this.ages.getWeightsView(), this.greedoConfig.getConcurrentLinkWeights(), this.greedoConfig.getConcurrentTransitVehicleWeights()); - // this.replanningEfficiencyEstimator = new - // ReplanningEfficiencyEstimator2(this.greedoConfig); + + this.disappointmentAnalyzer = new DisappointmentAnalyzer(this.greedoConfig); + this.stationaryReplanningRegulator = new StationaryReplanningRegulator( + this.greedoConfig.getMSAReplanningRate(0), this.greedoConfig.getMSAReplanningRate(0)); this.asymptoticAgeLogger = new AsymptoticAgeLogger(this.greedoConfig.getMaxRelativeMemoryLength(), new File("./output/"), "asymptoticAges.", ".txt"); @@ -219,6 +239,38 @@ public IEREventHandlerProvider beforeReplanningAndGetEventHandlerProvider() { this.lastPhysicalPopulationState.getSelectedPlan(person.getId()).getScore()); } + // >>> NEW 2020-08-27 >>> + if (this.previousReplannerIds != null /* has replanned before */) { + Utilities.SummaryStatistics utilityStats = this.utilities.newSummaryStatistics(); +// this.disappointment.update(utilityStats.personId2realizedUtilityChange, +// utilityStats.personId2expectedUtilityChange, this.previousReplannerIds, this.weightedReplannerDeltaX); + this.disappointmentAnalyzer.update(this.previousCurrentSlotUsages, this.previousAnticipatedSlotUsages, + utilityStats.personId2realizedUtilityChange, utilityStats.personId2expectedUtilityChange, + this.previousReplannerIds, this.personId2interactions, + this.greedoConfig.getMSAReplanningRate(this.iteration())); +// final double naiveE2 = this.disappointmentAnalyzer.getLastNaiveAbsE(); +// final double estimE2 = this.disappointmentAnalyzer.getLastEstimAbsE(); +// Logger.getLogger(this.getClass()) +// .info("DISAPPOINTMENT: naive E2 = " + naiveE2 + ", estimated E2 = " + estimE2 +// + ", reductionRatio = " + (estimE2 / naiveE2) + "; replanningThreshold = " +// + this.disappointmentAnalyzer.getTheta()); + + this.stationaryReplanningRegulator + .setMeanReplanningRate(this.greedoConfig.getMSAReplanningRate(this.iteration())); + this.stationaryReplanningRegulator.setStepSize(this.greedoConfig.getMSAReplanningRate(this.iteration())); + this.stationaryReplanningRegulator.update(utilityStats.personId2expectedUtilityChange, this.personId2Dn0, + this.personId2Tn); + + BasicStatistics stats = new BasicStatistics(); + for (Double cn : this.stationaryReplanningRegulator.getCnView().values()) { + stats.add(cn); + } + Logger.getLogger(this.getClass()) + .info("CN STATS: mean = " + stats.getAvg() + " stddev = " + stats.getStddev()); + + } + // <<< NEW 2020-08-27 <<< + final LogDataWrapper logDataWrapper = new LogDataWrapper(this.greedoConfig, this.utilities.newSummaryStatistics(), this.lastReplanningSummaryStatistics, this.services.getIterationNumber() - 1); @@ -261,14 +313,52 @@ public void afterReplanning() { this.hypotheticalSlotUsageListeners.clear(); final Utilities.SummaryStatistics utilityStats = this.utilities.newSummaryStatistics(); - final ReplannerIdentifier replannerIdentifier = new ReplannerIdentifier( - // this.replanningEfficiencyEstimator.getBeta(), - this.greedoConfig, this.iteration(), this.physicalSlotUsageListener.getIndicatorView(), - hypotheticalSlotUsageIndicators, utilityStats.personId2expectedUtilityChange, - utilityStats.personId2experiencedUtility, - // this.realizedToTargetLambdaRatio, - this.betaScale); - final Set> replannerIds = replannerIdentifier.drawReplanners(); + + // >>>>>>>>>> TODO 2020-09-05 Replaced the replanner identifier! >>>>>>>>>> + +// final ReplannerIdentifier replannerIdentifier = new ReplannerIdentifier( +// // this.replanningEfficiencyEstimator.getBeta(), +// this.greedoConfig, this.iteration(), this.physicalSlotUsageListener.getIndicatorView(), +// hypotheticalSlotUsageIndicators, utilityStats.personId2expectedUtilityChange, +// utilityStats.personId2experiencedUtility, +// // this.realizedToTargetLambdaRatio, +// this.betaScale); +// final Set> replannerIds = replannerIdentifier.drawReplanners(); + + final double lambdaBar; + if ((this.lastReplanningSummaryStatistics.numberOfReplanners != null) + && (this.lastReplanningSummaryStatistics.numberOfNonReplanners != null)) { + lambdaBar = ((double) this.lastReplanningSummaryStatistics.numberOfReplanners) + / (this.lastReplanningSummaryStatistics.numberOfReplanners + + this.lastReplanningSummaryStatistics.numberOfNonReplanners); + } else { + lambdaBar = 1.0; + } + + final ReplannerIdentifierTII replannerIdentifier = new ReplannerIdentifierTII(this.greedoConfig, + this.physicalSlotUsageListener.getIndicatorView(), hypotheticalSlotUsageIndicators, + utilityStats.personId2expectedUtilityChange, utilityStats.personId2experiencedUtility, + this.disappointmentAnalyzer.getB(), lambdaBar); + final Set> replannerIds = replannerIdentifier.drawReplanners( + this.services.getScenario().getNetwork(), this.stationaryReplanningRegulator.getCnView()); + + this.personId2interactions = replannerIdentifier.getPersonId2InteractionsView(); + this.personId2Dn0 = replannerIdentifier.getPersonId2Dn0(); + this.personId2Tn = replannerIdentifier.getPersonId2Tn(); + + this.previousAnticipatedSlotUsages = new LinkedHashMap<>(hypotheticalSlotUsageIndicators); + this.previousCurrentSlotUsages = new LinkedHashMap<>(this.physicalSlotUsageListener.getIndicatorView()); + this.previousReplannerIds = new LinkedHashSet<>(replannerIds); +// this.weightedReplannerDeltaX = replannerIdentifier.weightedReplannerCountDifferences; + + // <<<<<<<<<< TODO 2020-09-05 Replaced the replanner identifier! <<<<<<<<<< + +// for (Id personId : this.services.getScenario().getPopulation().getPersons().keySet()) { +// if (!replannerIds.contains(personId)) { +// this.previousAnticipatedSlotUsages.remove(personId); +// this.previousCurrentSlotUsages.remove(personId); +// } +// } // IERChecker.initHypothetical(); // IERChecker.clearRealized(); @@ -304,14 +394,11 @@ public void afterReplanning() { this.ages.update(replannerIds); this.physicalSlotUsageListener.updatePersonWeights(this.ages.getWeightsView()); - // TODO 2019-07-08 NEW - if (this.greedoConfig.getEnforceMeanReplanningRate()) { - final double lambdaRealized = ((double) this.lastReplanningSummaryStatistics.numberOfReplanners) - / this.lastReplanningSummaryStatistics.getNumberOfReplanningCandidates(); - final double lambdaBar = this.lastReplanningSummaryStatistics.lambdaBar; - // this.realizedToTargetLambdaRatio.add(lambdaRealized / lambdaBar); - this.betaScale *= (lambdaBar / lambdaRealized); - } +// if (this.greedoConfig.getEnforceMeanReplanningRate()) { +// final double lambdaRealized = ((double) this.lastReplanningSummaryStatistics.numberOfReplanners) +// / this.lastReplanningSummaryStatistics.getNumberOfReplanningCandidates(); +// this.betaScale *= (this.lastReplanningSummaryStatistics.lambdaBar / lambdaRealized); +// } } // --------------- IMPLEMENTATION OF Provider --------------- diff --git a/experimental/src/main/java/org/matsim/contrib/greedo/datastructures/DynamicDataUtils2.java b/experimental/src/main/java/org/matsim/contrib/greedo/datastructures/DynamicDataUtils2.java new file mode 100644 index 0000000..a74aa9a --- /dev/null +++ b/experimental/src/main/java/org/matsim/contrib/greedo/datastructures/DynamicDataUtils2.java @@ -0,0 +1,56 @@ +/* + * Copyright 2020 Gunnar Flötteröd + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * contact: gunnar.flotterod@gmail.com + * + */ +package org.matsim.contrib.greedo.datastructures; + +import floetteroed.utilities.DynamicData; + +/** + * + * @author Gunnar Flötteröd + * + */ +public class DynamicDataUtils2 { + + private DynamicDataUtils2() { + } + + public static void makeEntriesAbsolute(final DynamicData data) { + for (K key : data.keySet()) { + for (int bin = 0; bin < data.getBinCnt(); bin++) { + final double value = data.getBinValue(key, bin); + if (value < 0.0) { + data.put(key, bin, Math.abs(value)); + } + } + } + } + + public static void makeEntriesSquare(final DynamicData data) { + for (K key : data.keySet()) { + for (int bin = 0; bin < data.getBinCnt(); bin++) { + final double value = data.getBinValue(key, bin); + if (value != 0.0) { + data.put(key, bin, value * value); + } + } + } + } + +} diff --git a/experimental/src/main/java/org/matsim/contrib/greedo/datastructures/MatsimLinkDataIO.java b/experimental/src/main/java/org/matsim/contrib/greedo/datastructures/MatsimLinkDataIO.java new file mode 100644 index 0000000..49f0353 --- /dev/null +++ b/experimental/src/main/java/org/matsim/contrib/greedo/datastructures/MatsimLinkDataIO.java @@ -0,0 +1,46 @@ +/* + * Copyright 2020 Gunnar Flötteröd + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * contact: gunnar.flotterod@gmail.com + * + */ +package org.matsim.contrib.greedo.datastructures; + +import org.matsim.api.core.v01.Id; +import org.matsim.api.core.v01.network.Link; + +import floetteroed.utilities.DynamicDataXMLFileIO; + +/** + * + * @author Gunnar Flötteröd + * + */ +@SuppressWarnings("serial") +public class MatsimLinkDataIO extends DynamicDataXMLFileIO> { + + @Override + protected String key2attrValue(Id key) { + return key.toString(); + } + + @Override + protected Id attrValue2key(String string) { + return Id.createLinkId(string); + } + +} + diff --git a/experimental/src/main/java/org/matsim/contrib/greedo/datastructures/SlotUsageUtilities.java b/experimental/src/main/java/org/matsim/contrib/greedo/datastructures/SlotUsageUtilities.java new file mode 100644 index 0000000..4ab5dbb --- /dev/null +++ b/experimental/src/main/java/org/matsim/contrib/greedo/datastructures/SlotUsageUtilities.java @@ -0,0 +1,59 @@ +/* + * Copyright 2020 Gunnar Flötteröd + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * contact: gunnar.flotterod@gmail.com + * + */ +package org.matsim.contrib.greedo.datastructures; + +import java.util.Collection; + +import floetteroed.utilities.DynamicData; +import floetteroed.utilities.TimeDiscretization; + +/** + * + * @author Gunnar Flötteröd + * + */ +public class SlotUsageUtilities { + + private SlotUsageUtilities() { + } + + public static void addIndicatorsToTotalsTreatingNullAsZero(final DynamicData totals, + final SpaceTimeIndicators indicators, final double factor, final boolean useParticleWeight, + final boolean useSlotWeight) { + if (indicators != null) { + for (int bin = 0; bin < indicators.getTimeBinCnt(); bin++) { + for (SpaceTimeIndicators.Visit visit : indicators.getVisits(bin)) { + totals.add(visit.spaceObject, bin, factor * visit.weight(useParticleWeight, useSlotWeight)); + } + } + } + } + + public static DynamicData newTotals(final TimeDiscretization timeDiscr, + final Collection> allIndicators, final boolean useParticleWeight, + final boolean useSlotWeight) { + final DynamicData totals = new DynamicData(timeDiscr); + for (SpaceTimeIndicators indicators : allIndicators) { + addIndicatorsToTotalsTreatingNullAsZero(totals, indicators, 1.0, useParticleWeight, useSlotWeight); + } + return totals; + } + +} diff --git a/experimental/src/main/java/org/matsim/contrib/greedo/datastructures/SpaceTimeCounts.java b/experimental/src/main/java/org/matsim/contrib/greedo/datastructures/SpaceTimeCounts.java index a7050ce..b16e058 100644 --- a/experimental/src/main/java/org/matsim/contrib/greedo/datastructures/SpaceTimeCounts.java +++ b/experimental/src/main/java/org/matsim/contrib/greedo/datastructures/SpaceTimeCounts.java @@ -49,6 +49,10 @@ public class SpaceTimeCounts { // -------------------- CONSTRUCTION -------------------- + // TODO 2020-09-10 NEW + public SpaceTimeCounts() { + } + public SpaceTimeCounts(final SpaceTimeIndicators parent, final boolean useParticleWeight, final boolean useSlotWeight) { if (parent != null) { @@ -98,7 +102,15 @@ public void subtract(final SpaceTimeCounts other) { } } - // NEW + // TODO NEW 2020-09-05 + public double get(final L key, final int timeBin) { + return this.get(new Tuple<>(key, timeBin)); + } + + // TODO NEW 2020-09-10 + public void add(final L key, final int timeBin, final double addend) { + this.add(new Tuple<>(key, timeBin), addend); + } public double sumOfSquareEntries() { double result = 0.0; diff --git a/experimental/src/main/java/org/matsim/contrib/greedo/stationaryreplanningregulation/AdaptiveQuantileEstimator.java b/experimental/src/main/java/org/matsim/contrib/greedo/stationaryreplanningregulation/AdaptiveQuantileEstimator.java new file mode 100644 index 0000000..63da802 --- /dev/null +++ b/experimental/src/main/java/org/matsim/contrib/greedo/stationaryreplanningregulation/AdaptiveQuantileEstimator.java @@ -0,0 +1,78 @@ +/* + * Copyright 2020 Gunnar Flötteröd + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * contact: gunnar.flotterod@gmail.com + * + */ +package org.matsim.contrib.greedo.stationaryreplanningregulation; + +import java.util.Random; + +/** + * + * @author Gunnar Flötteröd + * + * @see https://www.expertsinuncertainty.net/Portals/60/Multimedia/Presentations/MMRM%20-%20EJ%20Workshop%20August%2014/Keming%20Yu%20-%20Bayesian%20Quantile%20Regression.pdf?ver=2017-08-08-192722-947 + */ +public class AdaptiveQuantileEstimator { + + private double stepSize; + + private double proba; + + private double quantile; + + public AdaptiveQuantileEstimator(final double stepSize, final double proba, final double initialQuantileGuess) { + this.stepSize = stepSize; + this.proba = proba; + this.quantile = initialQuantileGuess; + } + + public void setStepSize(final double stepSize) { + this.stepSize = stepSize; + } + + public void update(final double realization) { + + final double dLoss_dQuantile; + if (realization > this.quantile) { + dLoss_dQuantile = -this.proba; + } else { + dLoss_dQuantile = 1.0 - this.proba; + } + + // Step size one as we are only estimating a constant. + this.quantile -= this.stepSize * dLoss_dQuantile; + } + + public void setProbability(final double probability) { + this.proba = probability; + } + + public double getQuantile() { + return this.quantile; + } + + public static void main(String[] args) { + Random rnd = new Random(); + AdaptiveQuantileEstimator aqe = new AdaptiveQuantileEstimator(0.1, 0.1359, 0.0); + for (int i = 0; i < 100; i++) { + double realization = rnd.nextGaussian(); + aqe.update(realization); + System.out.println(realization + "\t" + aqe.quantile); + } + } +} diff --git a/experimental/src/main/java/org/matsim/contrib/greedo/stationaryreplanningregulation/StationaryReplanningRegulator.java b/experimental/src/main/java/org/matsim/contrib/greedo/stationaryreplanningregulation/StationaryReplanningRegulator.java new file mode 100644 index 0000000..23f170c --- /dev/null +++ b/experimental/src/main/java/org/matsim/contrib/greedo/stationaryreplanningregulation/StationaryReplanningRegulator.java @@ -0,0 +1,112 @@ +/* + * Copyright 2020 Gunnar Flötteröd + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * contact: gunnar.flotterod@gmail.com + * + */ +package org.matsim.contrib.greedo.stationaryreplanningregulation; + +import static java.util.Collections.unmodifiableMap; + +import java.util.LinkedHashMap; +import java.util.Map; + +import org.matsim.api.core.v01.Id; +import org.matsim.api.core.v01.population.Person; + +/** + * + * @author Gunnar Flötteröd + * + */ +public class StationaryReplanningRegulator { + + // -------------------- MEMBERS -------------------- + + private double stepSize; + + private double meanReplanningRate; + + private final Map, AdaptiveQuantileEstimator> personId2cn = new LinkedHashMap<>(); + + private final Map, Double> personId2expDn0 = new LinkedHashMap<>(); + private Double avgSqrtOfExpDn0; + + // -------------------- CONSTRUCTION -------------------- + + public StationaryReplanningRegulator(final double stepSize, final double meanReplanningRate) { + this.stepSize = stepSize; + this.meanReplanningRate = meanReplanningRate; + } + + // -------------------- IMPLEMENTATION -------------------- + + public void setStepSize(final double stepSize) { + this.stepSize = stepSize; + for (AdaptiveQuantileEstimator cn : this.personId2cn.values()) { + cn.setStepSize(stepSize); + } + } + + public void setMeanReplanningRate(final double meanReplanningRate) { + this.meanReplanningRate = meanReplanningRate; + } + + public Map, Double> getCnView() { + final Map, Double> result = new LinkedHashMap<>(); + for (Map.Entry, AdaptiveQuantileEstimator> entry : this.personId2cn.entrySet()) { + result.put(entry.getKey(), entry.getValue().getQuantile()); + } + return unmodifiableMap(result); + } + + // TODO CONTINUE HERE + + public void update(final Map, Double> personId2deltaUn0, final Map, Double> personId2Dn0, + final Map, Double> personId2Tn) { + + // Update disappointment when not replanning. + + this.avgSqrtOfExpDn0 = 0.0; + for (Map.Entry, Double> entry : personId2Dn0.entrySet()) { + final Id personId = entry.getKey(); + final double dn0 = entry.getValue(); + + final double newExpDn0 = Math.max(0.0, + (1.0 - this.stepSize) * this.personId2expDn0.getOrDefault(personId, 0.0) + this.stepSize * dn0); + this.personId2expDn0.put(personId, newExpDn0); + this.avgSqrtOfExpDn0 += Math.sqrt(newExpDn0); + + } + this.avgSqrtOfExpDn0 /= this.personId2expDn0.size(); + this.avgSqrtOfExpDn0 = Math.max(1e-8, this.avgSqrtOfExpDn0); + + // Adjust cn replanning thresholds. + + for (Id personId : personId2deltaUn0.keySet()) { + final double targetReplanningRate = this.meanReplanningRate * Math.sqrt(this.personId2expDn0.get(personId)) + / this.avgSqrtOfExpDn0; + AdaptiveQuantileEstimator cn = this.personId2cn.get(personId); + if (cn == null) { + cn = new AdaptiveQuantileEstimator(this.stepSize, targetReplanningRate, 0.0); + this.personId2cn.put(personId, cn); + } else { + cn.setProbability(targetReplanningRate); + } + cn.update(personId2deltaUn0.get(personId) - personId2Tn.get(personId)); + } + } +} diff --git a/experimental/src/main/java/stockholm/wum/WUMProductionRunner.java b/experimental/src/main/java/stockholm/wum/WUMProductionRunner.java index bf29a17..561c23c 100644 --- a/experimental/src/main/java/stockholm/wum/WUMProductionRunner.java +++ b/experimental/src/main/java/stockholm/wum/WUMProductionRunner.java @@ -160,7 +160,7 @@ static void runProductionScenarioWithSampersDynamics(final String configFileName controler.addOverridingModule(new SampersDifferentiatedPTScoringFunctionModule()); controler.addOverridingModule(new AbstractModule() { @Override - public void install() { + public void install() { this.install(new SBBTransitModule()); this.install(new SwissRailRaptorModule()); } diff --git a/utilities/src/main/java/floetteroed/utilities/DynamicDataUtils.java b/utilities/src/main/java/floetteroed/utilities/DynamicDataUtils.java index f2d8cf7..82ea0c6 100644 --- a/utilities/src/main/java/floetteroed/utilities/DynamicDataUtils.java +++ b/utilities/src/main/java/floetteroed/utilities/DynamicDataUtils.java @@ -28,7 +28,7 @@ public class DynamicDataUtils { private DynamicDataUtils() { } - + public static double sumOfEntries2(final DynamicData data) { double result = 0.0; for (L locObj : data.keySet()) { @@ -70,5 +70,22 @@ public static DynamicData newDifference(final DynamicData data1, final } return result; } -} + public static DynamicData newWeightedSum(final DynamicData data1, final double weight1, + final DynamicData data2, final double weight2) { + if (data1.getBinCnt() != data2.getBinCnt()) { + throw new RuntimeException( + "arg1 has " + data1.getBinCnt() + " bins, but arg2 has " + data2.getBinCnt() + " bins."); + } + final DynamicData result = new DynamicData(data1.getStartTime_s(), data1.getBinSize_s(), + data1.getBinCnt()); + for (L locObj : SetUtils.union(data1.keySet(), data2.keySet())) { + for (int bin = 0; bin < data1.getBinCnt(); bin++) { + result.put(locObj, bin, + weight1 * data1.getBinValue(locObj, bin) + weight2 * data2.getBinValue(locObj, bin)); + } + } + return result; + } + +}