diff --git a/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/EstimateCalculator.java b/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/EstimateCalculator.java new file mode 100644 index 00000000000..dd12534b067 --- /dev/null +++ b/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/EstimateCalculator.java @@ -0,0 +1,323 @@ +package org.matsim.modechoice; + +import com.google.inject.Inject; +import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; +import it.unimi.dsi.fastutil.objects.ReferenceSet; +import org.matsim.api.core.v01.population.Leg; +import org.matsim.core.config.groups.QSimConfigGroup; +import org.matsim.core.router.TripStructureUtils; +import org.matsim.core.utils.timing.TimeInterpretation; +import org.matsim.core.utils.timing.TimeTracker; +import org.matsim.modechoice.constraints.TripConstraint; +import org.matsim.modechoice.estimators.*; + +import javax.annotation.Nullable; +import java.util.*; +import java.util.function.Predicate; + +/** + * Provides methods for computing estimates. + */ +public final class EstimateCalculator { + + @Inject + private QSimConfigGroup qsim; + + @Inject + private EstimateRouter router; + + @Inject + private Map legEstimators; + + @Inject + private Map tripEstimator; + + @Inject + private Set> constraints; + + @Inject + private Set tripScores; + + @Inject + private ActivityEstimator actEstimator; + + @Inject + private TimeInterpretation timeInterpretation; + + @Inject + private Map fixedCosts; + + @Inject + private PlanModelService service; + + /** + * Route and prepare estimates for all trips, expect those that are not considered. + */ + public void prepareEstimates(PlanModel planModel, EstimatorContext context, @Nullable Set consideredModes, @Nullable boolean[] mask) { + + if (planModel.getEstimates().isEmpty()) + service.initEstimates(planModel); + + router.routeModes(planModel, consideredModes == null ? planModel.filterModes(ModeEstimate::isUsable) : consideredModes, (mode, tripIdx) -> mask == null || mask[tripIdx]); + calculateEstimates(context, planModel); + } + + /** + * Prepare estimates only for the list of mode combinations. + */ + public void prepareEstimates(PlanModel planModel, EstimatorContext context, List modes) { + + if (planModel.getEstimates().isEmpty()) + service.initEstimates(planModel); + + Set all = new HashSet<>(); + Set[] perTrip = new Set[planModel.trips()]; + + for (String[] ms : modes) { + for (int i = 0; i < ms.length; i++) { + all.add(ms[i]); + perTrip[i] = perTrip[i] == null ? new HashSet<>() : perTrip[i]; + perTrip[i].add(ms[i]); + } + } + + router.routeModes(planModel, all, (mode, tripIdx) -> perTrip[tripIdx].contains(mode)); + calculateEstimates(context, planModel); + } + + /** + * Prepare estimates for a single trip. + */ + public void prepareEstimates(PlanModel planModel, EstimatorContext context, Predicate useModes, int tripId) { + + if (planModel.getEstimates().isEmpty()) + service.initEstimates(planModel); + + + router.routeModes(planModel, planModel.filterModes(useModes), (mode, tripIdx) -> tripIdx == tripId); + calculateEstimates(context, planModel); + } + + /** + * Computes the estimate of a whole plan. Note that this does not check for constraints. + */ + public double calculatePlanEstimate(EstimatorContext context, PlanModel planModel, String[] modes) { + + if (planModel.getEstimates().isEmpty()) + throw new IllegalArgumentException("Plan model contains no estimates. Use .prepareEstimates() first."); + + ReferenceSet usedModes = new ReferenceOpenHashSet<>(); + + Map singleOptions = new HashMap<>(); + // reduce to single options that are usable + for (Map.Entry> e : planModel.getEstimates().entrySet()) { + for (ModeEstimate o : e.getValue()) { + // check if a mode can be used at all + if (!o.isUsable() || o.isMin()) + continue; + + singleOptions.put(e.getKey(), o); + break; + } + } + + TimeTracker tt = new TimeTracker(timeInterpretation); + tt.setTime(planModel.getStartTimes()[0]); + + double estimate = 0; + for (int i = 0; i < modes.length; i++) { + + String mode = modes[i]; + + // If no mode is selected, estimates are skipped + if (mode == null) { + if (i < modes.length - 1) + tt.setTime(planModel.getStartTimes()[i + 1]); + + continue; + } + + List legs = planModel.getLegs(mode, i); + + if (tt.getTime().isUndefined()) + tt.setTime(planModel.getStartTimes()[i]); + + if (legs == null) + return Double.NEGATIVE_INFINITY; + + for (Leg leg : legs) { + tt.addLeg(leg); + } + + ModeEstimate opt = singleOptions.get(mode); + + // Some illegal option without estimates + if (opt == null) { + return Double.NEGATIVE_INFINITY; + } + + // This estimated is unrouted and skipped + if (opt.getLegEstimates()[i] == Double.NEGATIVE_INFINITY) { + if (i < modes.length - 1) + tt.setTime(planModel.getStartTimes()[i + 1]); + + continue; + } + + estimate += opt.getLegEstimates()[i]; + + TripEstimator t = tripEstimator.get(mode); + + // This trip estimator can be used directly + if (t != null && !t.providesMinEstimate(context, mode, opt.getOption())) + estimate += opt.getTripEstimates()[i]; + + // Store modes that have been used + if (!opt.getNoRealUsage()[i]) + usedModes.add(mode); + + TripStructureUtils.Trip trip = planModel.getTrip(i); + + for (TripScoreEstimator tripScore : tripScores) { + estimate += tripScore.estimate(context, mode, trip); + } + + // Try to estimate aborted plans + if (qsim.getEndTime().isDefined() && tt.getTime().seconds() > qsim.getEndTime().seconds()) { + estimate += context.scoring.abortedPlanScore; + + // Estimate the first activity, because the overnight estimation will not be performed + estimate += actEstimator.estimate(context, 0, planModel.getTrip(0).getOriginActivity()); + break; + } + + // Estimate overnight scoring if applicable + if (modes.length > 1 && i == modes.length - 1) { + estimate += actEstimator.estimateLastAndFirstOfDay(context, tt.getTime().seconds(), trip.getDestinationActivity(), planModel.getTrip(0).getOriginActivity()); + } else + estimate += actEstimator.estimate(context, tt.getTime().seconds(), trip.getDestinationActivity()); + + tt.addActivity(trip.getDestinationActivity()); + } + + // Add the fixed costs estimate if a mode has been used + for (ModeEstimate mode : singleOptions.values()) { + FixedCostsEstimator f = fixedCosts.get(mode.getMode()); + TripEstimator t = tripEstimator.get(mode.getMode()); + + if (usedModes.contains(mode.getMode())) { + + if (f != null) + estimate += f.usageUtility(context, mode.getMode(), mode.getOption()); + + // This estimator is used on the whole trip + if (t != null && t.providesMinEstimate(context, mode.getMode(), mode.getOption())) + estimate += t.estimatePlan(context, mode.getMode(), modes, planModel, mode.getOption()); + + } + + if (f != null) + estimate += f.fixedUtility(context, mode.getMode(), mode.getOption()); + } + + return estimate; + } + + @SuppressWarnings("unchecked") + public List> buildConstraints(EstimatorContext context, PlanModel planModel) { + + List> constraints = new ArrayList<>(); + for (TripConstraint c : this.constraints) { + constraints.add(new PlanModelService.ConstraintHolder<>( + (TripConstraint) c, + c.getContext(context, planModel) + )); + } + + return constraints; + } + + /** + * Calculate the estimates for all options. Note that plan model has to be routed before computing estimates. + */ + @SuppressWarnings("rawtypes") + private void calculateEstimates(EstimatorContext context, PlanModel planModel) { + + for (Map.Entry> e : planModel.getEstimates().entrySet()) { + + for (ModeEstimate c : e.getValue()) { + + if (!c.isUsable()) + continue; + + // All estimates are stored within the objects and modified directly here + double[] legEstimates = c.getLegEstimates(); + double[] tValues = c.getTripEstimates(); + double[] actEst = c.getActEst(); + boolean[] noUsage = c.getNoRealUsage(); + + // Collect all estimates + for (int i = 0; i < planModel.trips(); i++) { + + List legs = planModel.getLegs(c.getMode(), i); + + // This mode could not be applied + if (legs == null || legs == EstimateRouter.UN_ROUTED) { + legEstimates[i] = Double.NEGATIVE_INFINITY; + continue; + } + + TripEstimator tripEst = tripEstimator.get(c.getMode()); + + // some options may produce equivalent results, but are re-estimated + // however, the more expensive computation is routing and only done once + boolean realUsage = planModel.hasModeForTrip(c.getMode(), i); + noUsage[i] = !realUsage; + + double estimate = 0; + if (tripEst != null && realUsage) { + MinMaxEstimate minMax = tripEst.estimate(context, c.getMode(), planModel, legs, c.getOption()); + double tripEstimate = c.isMin() ? minMax.getMin() : minMax.getMax(); + + // Only store if required + if (tValues != null) + tValues[i] = tripEstimate; + } + + double tt = 0; + + for (Leg leg : legs) { + String legMode = leg.getMode(); + + tt += timeInterpretation.decideOnLegTravelTime(leg).orElse(0); + + // Already scored with the trip estimator + if (tripEst != null && legMode.equals(c.getMode())) + continue; + + LegEstimator legEst = legEstimators.get(legMode); + + if (legEst == null) + throw new IllegalStateException("No leg estimator defined for mode: " + legMode); + + // TODO: add delay estimate? (e.g waiting time for drt, currently not respected) + estimate += legEst.estimate(context, legMode, leg, c.getOption()); + } + + TripStructureUtils.Trip trip = planModel.getTrip(i); + + double arrivalTime = planModel.getStartTimes()[i] + tt; + + // early or late arrival can also have an effect on the activity scores which is potentially considered here + actEst[i] = actEstimator.estimate(context, arrivalTime, trip.getDestinationActivity()); + + for (TripScoreEstimator tripScore : tripScores) { + estimate += tripScore.estimate(context, c.getMode(), trip); + } + + legEstimates[i] = estimate; + } + } + } + } +} diff --git a/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/EstimateRouter.java b/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/EstimateRouter.java index 27e68b0c2f7..883af868614 100644 --- a/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/EstimateRouter.java +++ b/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/EstimateRouter.java @@ -13,15 +13,23 @@ import org.matsim.facilities.FacilitiesUtils; import org.matsim.facilities.Facility; +import java.util.Arrays; import java.util.Collection; import java.util.List; +import java.util.Objects; import java.util.stream.Collectors; +import java.util.stream.Stream; /** * Router responsible for routing all possible modes between trips. */ public final class EstimateRouter { + /** + * Initial value for unrouted trips. + */ + static final List UN_ROUTED = List.of(); + private final TripRouter tripRouter; private final ActivityFacilities facilities; private final AnalysisMainModeIdentifier mmi; @@ -41,31 +49,48 @@ public EstimateRouter(TripRouter tripRouter, ActivityFacilities facilities, /** * Route all modes that are relevant with all possible options. */ - public void routeModes(PlanModel model, Collection modes) { + public void routeModes(PlanModel model, Collection modes, TripModeFilter filter) { double[] startTimes = model.getStartTimes(); TimeTracker timeTracker = new TimeTracker(timeInterpretation); calcStartTimes(model, timeTracker, startTimes, model.trips()); - for (String mode : modes) { + String[] currentMode = model.getCurrentModesMutable(); - List[] legs = new List[model.trips()]; + List considerModes = Stream.concat(Arrays.stream(currentMode), modes.stream()) + .filter(Objects::nonNull) + .distinct() + .toList(); - int i = 0; - for (TripStructureUtils.Trip oldTrip : model) { + for (String mode : considerModes) { - final String routingMode = TripStructureUtils.identifyMainMode(oldTrip.getTripElements()); + // Null may be put into the collection, which will just be ignored here + if (mode == null) + continue; - timeTracker.setTime(startTimes[i]); + // Legs will be updated in-place + List[] legs = model.getLegs(mode, UN_ROUTED); - // Ignored mode - if (!modes.contains(routingMode)) { - legs[i++] = null; + int i = 0; + for (TripStructureUtils.Trip oldTrip : model) { + + // The current modes are always routed, regardless of filter. + // they are always needed to score whole plan + if (!filter.accept(mode, i) && !mode.equals(currentMode[i])) { + i++; + continue; + } + + // Skip entries that are already routed + if (legs[i] != UN_ROUTED) { + i++; continue; } + timeTracker.setTime(startTimes[i]); + Facility from = FacilitiesUtils.toFacility(oldTrip.getOriginActivity(), facilities); Facility to = FacilitiesUtils.toFacility(oldTrip.getDestinationActivity(), facilities); @@ -97,49 +122,6 @@ public void routeModes(PlanModel model, Collection modes) { legs[i++] = ll; } - - model.setLegs(mode, legs); - } - - model.setFullyRouted(true); - } - - /** - * Route a single trip with certain index. - */ - public void routeSingleTrip(PlanModel model, Collection modes, int idx) { - - double[] startTimes = model.getStartTimes(); - TimeTracker timeTracker = new TimeTracker(timeInterpretation); - - // Plus one so that start time idx is calculated - calcStartTimes(model, timeTracker, startTimes, idx + 1); - - TripStructureUtils.Trip oldTrip = model.getTrip(idx); - - Facility from = FacilitiesUtils.toFacility(oldTrip.getOriginActivity(), facilities); - Facility to = FacilitiesUtils.toFacility(oldTrip.getDestinationActivity(), facilities); - - for (String mode : modes) { - - timeTracker.setTime(startTimes[idx]); - - final List newTrip = tripRouter.calcRoute( - mode, from, to, - oldTrip.getOriginActivity().getEndTime().orElse(timeTracker.getTime().seconds()), - model.getPerson(), - oldTrip.getTripAttributes() - ); - - List ll = newTrip.stream() - .filter(el -> el instanceof Leg) - .map(el -> (Leg) el) - .collect(Collectors.toList()); - - List[] legs = new List[model.trips()]; - legs[idx] = ll; - - model.setLegs(mode, legs); } } diff --git a/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/InformedModeChoiceConfigGroup.java b/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/InformedModeChoiceConfigGroup.java index 822d78b1c12..49184162264 100644 --- a/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/InformedModeChoiceConfigGroup.java +++ b/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/InformedModeChoiceConfigGroup.java @@ -29,8 +29,8 @@ public class InformedModeChoiceConfigGroup extends ReflectiveConfigGroup { @Parameter @PositiveOrZero - @Comment("1/beta parameter to trade-off of exploration for alternatives. Parameter of 0 is equal to best choice." + - " POSITIVE_INFINITY will select randomly from the best k.") + @Comment("1/beta parameter (or temperature tau) to trade-off of exploration for alternatives. Parameter of 0 is equal to best choice." + + " POSITIVE_INFINITY will select randomly.") private double invBeta = Double.POSITIVE_INFINITY; @Parameter @@ -55,7 +55,7 @@ public class InformedModeChoiceConfigGroup extends ReflectiveConfigGroup { @Parameter @PositiveOrZero - @Comment("Probability to re-estimate an existing plan model.") + @Comment("Probability to drop all estimates for a plan.") private double probaEstimate = 0; public InformedModeChoiceConfigGroup() { diff --git a/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/InformedModeChoiceModule.java b/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/InformedModeChoiceModule.java index cad6cb65579..690662f2487 100644 --- a/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/InformedModeChoiceModule.java +++ b/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/InformedModeChoiceModule.java @@ -47,9 +47,11 @@ private InformedModeChoiceModule(Builder builder) { /** * Replaces a strategy in the config. Can be used to enable one of the strategies in this module programmatically. + * + * @param weight if not null, the weight of the strategy is set to this value. */ public static void replaceReplanningStrategy(Config config, String subpopulation, - String existing, String replacement) { + String existing, String replacement, Double weight) { // Copy list because it is unmodifiable List strategies = new ArrayList<>(config.replanning().getStrategySettings()); @@ -66,11 +68,23 @@ public static void replaceReplanningStrategy(Config config, String subpopulation found.forEach(s -> s.setStrategyName(replacement)); + if (weight != null) { + found.forEach(s -> s.setWeight(weight)); + } + // reset und set new strategies config.replanning().clearStrategySettings(); strategies.forEach(s -> config.replanning().addStrategySettings(s)); } + /** + * Replaces a strategy in the config. + */ + public static void replaceReplanningStrategy(Config config, String subpopulation, + String existing, String replacement) { + replaceReplanningStrategy(config, subpopulation, existing, replacement, null); + } + /** * Replace a strategy in the config for all subpoopulations. * @see #replaceReplanningStrategy(Config, String, String, String) @@ -99,6 +113,7 @@ public void install() { bind(BestChoiceGenerator.class); bind(SingleTripChoicesGenerator.class); bind(GeneratorContext.class); + bind(EstimateCalculator.class); bind(PlanModelService.class).asEagerSingleton(); addControlerListenerBinding().to(PlanModelService.class).asEagerSingleton(); diff --git a/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/ModeEstimate.java b/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/ModeEstimate.java index 078b649985f..9c2c2922165 100644 --- a/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/ModeEstimate.java +++ b/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/ModeEstimate.java @@ -1,10 +1,5 @@ package org.matsim.modechoice; -import com.google.common.collect.Lists; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; import java.util.Objects; /** @@ -15,8 +10,9 @@ public final class ModeEstimate { private final String mode; private final ModeAvailability option; - private final double[] est; + private final double[] legEst; private final double[] tripEst; + private final double[] actEst; /** * Mark trips with no real usage. E.g pt trips that consist only of walk legs. @@ -46,8 +42,9 @@ public final class ModeEstimate { this.option = option; this.min = isMin; this.usable = isUsable; - this.est = usable ? new double[n] : null; + this.legEst = usable ? new double[n] : null; this.tripEst = storeTripEst ? new double[n] : null; + this.actEst = usable ? new double[n] : null; this.noRealUsage = usable ? new boolean[n] : null; } @@ -67,8 +64,12 @@ public boolean isMin() { return min; } - public double[] getEstimates() { - return est; + public double[] getLegEstimates() { + return legEst; + } + + public double[] getActEst() { + return actEst; } public double[] getTripEstimates() { diff --git a/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/PlanModel.java b/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/PlanModel.java index 6d85a756d99..2630b9fda26 100644 --- a/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/PlanModel.java +++ b/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/PlanModel.java @@ -12,6 +12,7 @@ import javax.annotation.Nullable; import java.util.*; import java.util.function.Predicate; +import java.util.stream.IntStream; /** * A coarse model of the daily plan containing the trips and legs for using each mode. @@ -43,11 +44,6 @@ public final class PlanModel implements Iterable, HasPe */ private final Map> estimates; - /** - * Flag to indicate all routes have been computed; - */ - private boolean fullyRouted; - /** * Create a new plan model instance from an existing plan. */ @@ -217,29 +213,6 @@ public List getTrips() { return Arrays.asList(trips); } - void setLegs(String mode, List[] legs) { - mode = mode.intern(); - - List[] existing = this.legs.putIfAbsent(mode, legs); - - if (existing != null) { - - if (legs.length != existing.length) - throw new IllegalArgumentException(String.format("Existing legs have different length than the newly provided: %d vs. %d", existing.length, legs.length)); - - // Copy existing non-null legs - for (int i = 0; i < legs.length; i++) { - List l = legs[i]; - if (l != null) - existing[i] = l; - } - } - } - - void setFullyRouted(boolean value) { - this.fullyRouted = value; - } - void putEstimate(String mode, List options) { this.estimates.put(mode, options); } @@ -264,17 +237,6 @@ public Set filterModes(Predicate predicate) { return modes; } - /** - * Check io estimates are present. Otherwise call {@link PlanModelService} - */ - public boolean hasEstimates() { - return !this.estimates.isEmpty(); - } - - public boolean isFullyRouted() { - return fullyRouted; - } - /** * Return all possible choice combinations. */ @@ -297,6 +259,11 @@ public List getLegs(String mode, int i) { return legs[i]; } + @SuppressWarnings("unchecked") + List[] getLegs(String mode, List def) { + return this.legs.computeIfAbsent(mode.intern(), k -> IntStream.range(0, trips.length).mapToObj(i -> def).toArray(List[]::new)); + } + /** * Check whether a mode is available for a trip. * If for instance not pt option is found in the legs this will return false. @@ -317,7 +284,6 @@ public boolean hasModeForTrip(String mode, int i) { public void reset() { legs.clear(); estimates.clear(); - fullyRouted = false; } @Override diff --git a/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/PlanModelService.java b/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/PlanModelService.java index 96ad71e0af8..ddf547a55a6 100644 --- a/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/PlanModelService.java +++ b/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/PlanModelService.java @@ -2,65 +2,99 @@ import com.google.common.collect.Iterables; import com.google.inject.Inject; -import org.matsim.api.core.v01.population.Leg; +import it.unimi.dsi.fastutil.doubles.DoubleArrayList; +import it.unimi.dsi.fastutil.doubles.DoubleList; +import org.apache.commons.math3.stat.descriptive.DescriptiveStatistics; +import org.apache.commons.math3.stat.descriptive.SummaryStatistics; +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.api.core.v01.population.Population; import org.matsim.core.api.experimental.events.EventsManager; import org.matsim.core.controler.ControlerListenerManager; +import org.matsim.core.controler.OutputDirectoryHierarchy; +import org.matsim.core.controler.events.IterationEndsEvent; +import org.matsim.core.controler.events.ShutdownEvent; import org.matsim.core.controler.events.StartupEvent; import org.matsim.core.controler.listener.ControlerListener; +import org.matsim.core.controler.listener.IterationEndsListener; +import org.matsim.core.controler.listener.ShutdownListener; import org.matsim.core.controler.listener.StartupListener; import org.matsim.core.events.handler.EventHandler; -import org.matsim.core.router.TripStructureUtils; -import org.matsim.core.utils.timing.TimeInterpretation; +import org.matsim.core.gbl.MatsimRandom; +import org.matsim.core.scoring.functions.ScoringParametersForPerson; +import org.matsim.core.utils.io.IOUtils; import org.matsim.modechoice.constraints.TripConstraint; -import org.matsim.modechoice.estimators.*; +import org.matsim.modechoice.estimators.LegEstimator; +import org.matsim.modechoice.estimators.TripEstimator; +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.UncheckedIOException; import java.util.*; +import java.util.concurrent.ConcurrentHashMap; import java.util.function.Predicate; -import java.util.stream.Collectors; /** * A service for working with {@link PlanModel} and creating estimates. */ @SuppressWarnings("unchecked") -public final class PlanModelService implements StartupListener { +public final class PlanModelService implements StartupListener, IterationEndsListener, ShutdownListener { - @Inject - private Map legEstimators; + private final InformedModeChoiceConfigGroup config; + private final Map options; + /** + * A memory cache for plan models. This is used to avoid re-routing and re-estimation of the same plans. + */ + private final Map, PlanModel> memory = new ConcurrentHashMap<>(); + private final Map, DoubleList> diffs = new ConcurrentHashMap<>(); - @Inject - private Map tripEstimator; + /** + * Write estimate statistics. + */ + private final BufferedWriter out; @Inject - private Set tripScores; + private EventsManager eventsManager; @Inject - private Set> constraints; + private ControlerListenerManager controlerListenerManager; @Inject - private ActivityEstimator actEstimator; + private ScoringParametersForPerson params; @Inject - private TimeInterpretation timeInterpretation; + private Map tripEstimator; @Inject - private EventsManager eventsManager; + private Map legEstimators; @Inject - private ControlerListenerManager controlerListenerManager; - - private final InformedModeChoiceConfigGroup config; - private final Map options; + private Set> constraints; @Inject - private PlanModelService(InformedModeChoiceConfigGroup config, Map options) { + private PlanModelService(InformedModeChoiceConfigGroup config, OutputDirectoryHierarchy io, + Map options) { this.config = config; this.options = options; for (String mode : config.getModes()) { - if (!options.containsKey(mode)) throw new IllegalArgumentException(String.format("No estimators configured for mode %s", mode)); } + + this.out = IOUtils.getBufferedWriter(io.getOutputFilename("score_estimates_stats.csv")); + try { + this.out.write("iteration,mean_estimate_mae,median_estimate_mae,p95_estimate_mae,p99_estimate_mae,mean_corrected_estimate_mae,p95_corrected_estimate_mae,p99_corrected_estimate_mae,mean_estimate_bias\n"); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + @Override + public double priority() { + // Run after scoring listeners + return -10; } @Override @@ -85,10 +119,120 @@ public void notifyStartup(StartupEvent event) { } } + @Override + public void notifyIterationEnds(IterationEndsEvent event) { + + if (config.getProbaEstimate() >= 1) { + memory.clear(); + } else if (config.getProbaEstimate() > 0) { + + Random rnd = MatsimRandom.getLocalInstance(); + + // Remove plans from memory with a certain probability + memory.keySet().removeIf(id -> rnd.nextDouble() < config.getProbaEstimate()); + } + + Population population = event.getServices().getScenario().getPopulation(); + + SummaryStatistics bias = new SummaryStatistics(); + DescriptiveStatistics mae = new DescriptiveStatistics(); + + // The corrected mae subtracts the systematically bias from the estimate + DescriptiveStatistics correctedMae = new DescriptiveStatistics(); + + for (Person person : population.getPersons().values()) { + + Plan executedPlan = person.getSelectedPlan(); + + // Remove the estimate so it is only used once + Object estimate = executedPlan.getAttributes().removeAttribute(PlanCandidate.ESTIMATE_ATTR); + Double score = executedPlan.getScore(); + + DoubleList diffs = this.diffs.computeIfAbsent(person.getId(), k -> new DoubleArrayList()); + + if (estimate instanceof Double est && score != null) { + double diff = est - score; + bias.addValue(diff); + mae.addValue(Math.abs(diff)); + + diffs.add(diff); + + if (diffs.size() > event.getServices().getConfig().replanning().getMaxAgentPlanMemorySize()) { + diffs.removeDouble(0); + } + } + + if (diffs.size() >= event.getServices().getConfig().replanning().getMaxAgentPlanMemorySize()) { + + double minDiff = Double.POSITIVE_INFINITY; + for (double d : diffs) { + if (d < minDiff) + minDiff = d; + } + + double sumDiff = 0; + for (double d : diffs) { + sumDiff += Math.abs(d - minDiff); + } + + correctedMae.addValue(sumDiff / diffs.size()); + } + } + + this.writeStats(event.getIteration(), mae, correctedMae, bias); + } + + private void writeStats(int iteration, DescriptiveStatistics mae, DescriptiveStatistics correctedMae, SummaryStatistics bias) { + try { + out.write(String.format(Locale.ENGLISH, "%d,%f,%f,%f,%f,%f,%f,%f,%f\n", + iteration, + mae.getMean(), + mae.getPercentile(50), + mae.getPercentile(95), + mae.getPercentile(99), + correctedMae.getMean(), + correctedMae.getPercentile(95), + correctedMae.getPercentile(99), + bias.getMean() + )); + out.flush(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + @Override + public void notifyShutdown(ShutdownEvent event) { + try { + out.close(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + /** + * Get a plan model from memory for a person. If not present, a new one is created. + */ + public PlanModel getPlanModel(Plan plan) { + + Id id = plan.getPerson().getId(); + PlanModel model = memory.get(id); + if (model == null) { + model = PlanModel.newInstance(plan); + initEstimates(model); + memory.put(id, model); + } else + model.setPlan(plan); + + return model; + } + /** * Initialized {@link ModeEstimate} for all available options in the {@link PlanModel}. No routing or estimation is performed yet. */ - public void initEstimates(EstimatorContext context, PlanModel planModel) { + void initEstimates(PlanModel planModel) { + + EstimatorContext context = new EstimatorContext(planModel.getPerson(), params.getScoringParameters(planModel.getPerson())); for (String mode : config.getModes()) { @@ -111,7 +255,7 @@ public void initEstimates(EstimatorContext context, PlanModel planModel) { } else { - c.add(new ModeEstimate(mode, modeOption, planModel.trips(), usable, false, false)); + c.add(new ModeEstimate(mode, modeOption, planModel.trips(), usable, te != null, false)); } } @@ -120,16 +264,6 @@ public void initEstimates(EstimatorContext context, PlanModel planModel) { } } - /** - * Return the modes an estimator was registered for. - */ - public List modesForEstimator(LegEstimator est) { - return legEstimators.entrySet().stream().filter(e -> e.getValue().equals(est)) - .map(Map.Entry::getKey) - .distinct() - .collect(Collectors.toList()); - } - /** * Allowed modes of a plan. */ @@ -154,89 +288,6 @@ public List allowedModes(PlanModel planModel) { return modes; } - /** - * Calculate the estimates for all options. Note that plan model has to be routed before computing estimates. - */ - @SuppressWarnings("rawtypes") - public void calculateEstimates(EstimatorContext context, PlanModel planModel) { - - for (Map.Entry> e : planModel.getEstimates().entrySet()) { - - for (ModeEstimate c : e.getValue()) { - - if (!c.isUsable()) - continue; - - // All estimates are stored within the objects and modified directly here - double[] values = c.getEstimates(); - double[] tValues = c.getTripEstimates(); - boolean[] noUsage = c.getNoRealUsage(); - - // Collect all estimates - for (int i = 0; i < planModel.trips(); i++) { - - List legs = planModel.getLegs(c.getMode(), i); - - // This mode could not be applied - if (legs == null) { - values[i] = Double.NEGATIVE_INFINITY; - continue; - } - - TripEstimator tripEst = tripEstimator.get(c.getMode()); - - // some options may produce equivalent results, but are re-estimated - // however, the more expensive computation is routing and only done once - boolean realUsage = planModel.hasModeForTrip(c.getMode(), i); - noUsage[i] = !realUsage; - - double estimate = 0; - if (tripEst != null && realUsage) { - MinMaxEstimate minMax = tripEst.estimate(context, c.getMode(), planModel, legs, c.getOption()); - double tripEstimate = c.isMin() ? minMax.getMin() : minMax.getMax(); - - // Only store if required - if (tValues != null) - tValues[i] = tripEstimate; - - estimate += tripEstimate; - } - - double tt = 0; - - for (Leg leg : legs) { - String legMode = leg.getMode(); - - tt += timeInterpretation.decideOnLegTravelTime(leg).orElse(0); - - // Already scored with the trip estimator - if (tripEst != null && legMode.equals(c.getMode())) - continue; - - LegEstimator legEst = legEstimators.get(legMode); - - if (legEst == null) - throw new IllegalStateException("No leg estimator defined for mode: " + legMode); - - // TODO: add delay estimate? (e.g waiting time for drt, currently not respected) - estimate += legEst.estimate(context, legMode, leg, c.getOption()); - } - - TripStructureUtils.Trip trip = planModel.getTrip(i); - - // early or late arrival can also have an effect on the activity scores which is potentially considered here - estimate += actEstimator.estimate(context, planModel.getStartTimes()[i] + tt, trip.getDestinationActivity()); - - for (TripScoreEstimator tripScore : tripScores) { - estimate += tripScore.estimate(context, c.getMode(), trip); - } - - values[i] = estimate; - } - } - } - } - /** * Return whether any constraints are registered. */ @@ -246,6 +297,7 @@ public boolean hasConstraints() { /** * Check whether all registered constraints are met. + * * @param modes array of used modes for each trip of the day */ public boolean isValidOption(PlanModel model, String[] modes) { @@ -257,9 +309,9 @@ public boolean isValidOption(PlanModel model, String[] modes) { EstimatorContext context = new EstimatorContext(model.getPerson(), null); for (TripConstraint c : this.constraints) { - ConstraintHolder h = new ConstraintHolder<>( - (TripConstraint) c, - c.getContext(context, model) + PlanModelService.ConstraintHolder h = new PlanModelService.ConstraintHolder<>( + (TripConstraint) c, + c.getContext(context, model) ); if (!h.test(modes)) diff --git a/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/TripModeFilter.java b/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/TripModeFilter.java new file mode 100644 index 00000000000..8c5f49e7d6a --- /dev/null +++ b/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/TripModeFilter.java @@ -0,0 +1,20 @@ +package org.matsim.modechoice; + + +/** + * Functional interface to filter mode and trip combinations. + */ +@FunctionalInterface +public interface TripModeFilter { + + /** + * Don't filter any mode. + */ + static final TripModeFilter ACCEPT_ALL = (mode, tripIdx) -> true; + + /** + * Decide whether mode on trip is accepted. + */ + boolean accept(String mode, int tripIdx); + +} diff --git a/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/estimators/ActivityEstimator.java b/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/estimators/ActivityEstimator.java index 80f3d892f26..99fa6d4f210 100644 --- a/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/estimators/ActivityEstimator.java +++ b/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/estimators/ActivityEstimator.java @@ -13,6 +13,13 @@ public interface ActivityEstimator { */ double estimate(EstimatorContext context, double arrivalTime, Activity act); + /** + * Estimate the obtained score for the last and first activity in a plan. This special case can be used for overnight activities. + * If not implemented, the normal estimation for a single activity will be used. + */ + default double estimateLastAndFirstOfDay(EstimatorContext context, double arrivalTime, Activity last, Activity first) { + return estimate(context, arrivalTime, last); + } final class None implements ActivityEstimator { diff --git a/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/estimators/DefaultActivityEstimator.java b/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/estimators/DefaultActivityEstimator.java index 5d61f7f8dda..52744fa2fdb 100644 --- a/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/estimators/DefaultActivityEstimator.java +++ b/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/estimators/DefaultActivityEstimator.java @@ -24,6 +24,28 @@ public DefaultActivityEstimator(TimeInterpretation timeInterpretation) { @Override public double estimate(EstimatorContext context, double arrivalTime, Activity act) { + double departureTime = Math.max(arrivalTime, timeInterpretation.decideOnActivityEndTime(act, arrivalTime).orElse(context.scoring.simulationPeriodInDays * 24 * 3600) ); + return estScore(context, arrivalTime, departureTime, act); + } + + @Override + public double estimateLastAndFirstOfDay(EstimatorContext context, double arrivalTime, Activity last, Activity first) { + + double firstEnd = timeInterpretation.decideOnActivityEndTime(first, 0).orElse(0); + + if (last.getType().equals(first.getType())) { + // Shift end time to the next day + double endTime = firstEnd + 24 * 3600; + return estScore(context, arrivalTime, endTime, last); + } + + // Score first and last separately + return estScore(context, 0, firstEnd, first) + + estScore(context, arrivalTime, context.scoring.simulationPeriodInDays * 24 * 3600, last); + } + + private double estScore(EstimatorContext context, double arrivalTime, double departureTime, Activity act) { + ActivityUtilityParameters actParams = context.scoring.utilParams.get(act.getType()); if (!actParams.isScoreAtAll()) @@ -33,8 +55,6 @@ public double estimate(EstimatorContext context, double arrivalTime, Activity ac OptionalTime closingTime = actParams.getClosingTime(); double activityStart = arrivalTime; - double departureTime = Math.max(arrivalTime, timeInterpretation.decideOnActivityEndTime(act, arrivalTime).orElse(context.scoring.simulationPeriodInDays * 24 * 3600) ); - double activityEnd = departureTime; if (openingTime.isDefined() && arrivalTime < openingTime.seconds()) { @@ -44,7 +64,7 @@ public double estimate(EstimatorContext context, double arrivalTime, Activity ac activityEnd = closingTime.seconds(); } if (openingTime.isDefined() && closingTime.isDefined() - && (openingTime.seconds() > departureTime || closingTime.seconds() < arrivalTime)) { + && (openingTime.seconds() > departureTime || closingTime.seconds() < arrivalTime)) { // agent could not perform action activityStart = departureTime; activityEnd = departureTime; @@ -74,7 +94,7 @@ public double estimate(EstimatorContext context, double arrivalTime, Activity ac if ( duration >= 3600.*actParams.getZeroUtilityDuration_h() ) { double utilPerf = context.scoring.marginalUtilityOfPerforming_s * typicalDuration - * Math.log((duration / 3600.0) / actParams.getZeroUtilityDuration_h()); + * Math.log((duration / 3600.0) / actParams.getZeroUtilityDuration_h()); // also removing the "wait" alternative scoring. score += utilPerf; } else { @@ -102,6 +122,6 @@ public double estimate(EstimatorContext context, double arrivalTime, Activity ac } return score; - } + } } diff --git a/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/estimators/TripEstimator.java b/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/estimators/TripEstimator.java index 853a5da61a4..f8c9392a998 100644 --- a/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/estimators/TripEstimator.java +++ b/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/estimators/TripEstimator.java @@ -37,7 +37,7 @@ public interface TripEstimator { * @param option mode availability * @return Estimated utility */ - default double estimate(EstimatorContext context, String mode, String[] modes, PlanModel plan, ModeAvailability option) { + default double estimatePlan(EstimatorContext context, String mode, String[] modes, PlanModel plan, ModeAvailability option) { throw new UnsupportedOperationException("providesMinEstimate returned true, but estimate function for the whole plan is not implemented yet."); } diff --git a/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/replanning/GeneratorContext.java b/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/replanning/GeneratorContext.java index 0af30edd383..337dfc9a525 100644 --- a/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/replanning/GeneratorContext.java +++ b/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/replanning/GeneratorContext.java @@ -1,6 +1,7 @@ package org.matsim.modechoice.replanning; import org.matsim.core.router.PlanRouter; +import org.matsim.modechoice.PlanModelService; import org.matsim.modechoice.pruning.CandidatePruner; import org.matsim.modechoice.search.SingleTripChoicesGenerator; import org.matsim.modechoice.search.TopKChoicesGenerator; @@ -16,6 +17,7 @@ public final class GeneratorContext { public final TopKChoicesGenerator generator; public final SingleTripChoicesGenerator singleGenerator; + public final PlanModelService service; public final PlanSelector selector; public final PlanRouter planRouter; @@ -23,9 +25,11 @@ public final class GeneratorContext { public final CandidatePruner pruner; @Inject - public GeneratorContext(TopKChoicesGenerator generator, SingleTripChoicesGenerator singleGenerator, PlanSelector selector, PlanRouter planRouter, Provider pruner) { + public GeneratorContext(TopKChoicesGenerator generator, SingleTripChoicesGenerator singleGenerator, + PlanModelService service, PlanSelector selector, PlanRouter planRouter, Provider pruner) { this.generator = generator; this.singleGenerator = singleGenerator; + this.service = service; this.selector = selector; this.planRouter = planRouter; this.pruner = pruner.get(); diff --git a/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/replanning/SelectBestKPlanModesStrategyProvider.java b/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/replanning/SelectBestKPlanModesStrategyProvider.java index 1278ee3c22a..e592910c1f8 100644 --- a/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/replanning/SelectBestKPlanModesStrategyProvider.java +++ b/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/replanning/SelectBestKPlanModesStrategyProvider.java @@ -29,15 +29,12 @@ public class SelectBestKPlanModesStrategyProvider implements Provider generator; - @Inject - private Provider selector; - @Override public PlanStrategy get() { PlanStrategyImpl.Builder builder = new PlanStrategyImpl.Builder(new RandomPlanSelector<>()); - builder.addStrategyModule(new SimplePlanSelectionStrategy(globalConfigGroup, generator, selector)); + builder.addStrategyModule(new SimplePlanSelectionStrategy(globalConfigGroup, generator)); builder.addStrategyModule(new ReRoute(facilities, tripRouterProvider, globalConfigGroup, timeInterpretation)); return builder.build(); diff --git a/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/replanning/SelectSingleTripModeStrategy.java b/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/replanning/SelectSingleTripModeStrategy.java index eceec8767c8..6526374e7c6 100644 --- a/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/replanning/SelectSingleTripModeStrategy.java +++ b/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/replanning/SelectSingleTripModeStrategy.java @@ -28,7 +28,7 @@ public class SelectSingleTripModeStrategy extends AbstractMultithreadedModule { private static final Logger log = LogManager.getLogger(SelectSingleTripModeStrategy.class); - private final Provider generator; + private final Provider generator; private final Provider selector; private final Set modes; @@ -38,7 +38,7 @@ public class SelectSingleTripModeStrategy extends AbstractMultithreadedModule { public SelectSingleTripModeStrategy(GlobalConfigGroup globalConfigGroup, Set modes, - Provider generator, + Provider generator, Provider selector, Provider pruner, boolean requireDifferentModes) { super(globalConfigGroup); @@ -55,21 +55,21 @@ public PlanAlgorithm getPlanAlgoInstance() { } - public static Algorithm newAlgorithm(SingleTripChoicesGenerator generator, PlanSelector selector, CandidatePruner pruner, Collection modes, boolean requireDifferentModes) { + public static Algorithm newAlgorithm(GeneratorContext generator, PlanSelector selector, CandidatePruner pruner, Collection modes, boolean requireDifferentModes) { return new Algorithm(generator, selector, pruner, modes, requireDifferentModes); } public static final class Algorithm implements PlanAlgorithm { - private final SingleTripChoicesGenerator generator; + private final GeneratorContext ctx; private final PlanSelector selector; private final CandidatePruner pruner; private final Set modes; private final Random rnd; private final boolean requireDifferentModes; - public Algorithm(SingleTripChoicesGenerator generator, PlanSelector selector, CandidatePruner pruner, Collection modes, boolean requireDifferentModes) { - this.generator = generator; + public Algorithm(GeneratorContext generator, PlanSelector selector, CandidatePruner pruner, Collection modes, boolean requireDifferentModes) { + this.ctx = generator; this.selector = selector; this.pruner = pruner; this.modes = new HashSet<>(modes); @@ -79,7 +79,7 @@ public Algorithm(SingleTripChoicesGenerator generator, PlanSelector selector, Ca @Override public void run(Plan plan) { - PlanModel model = PlanModel.newInstance(plan); + PlanModel model = ctx.service.getPlanModel(plan); PlanCandidate c = chooseCandidate(model); @@ -124,7 +124,7 @@ public PlanCandidate chooseCandidate(PlanModel model) { // Set it to be modifiable mask[idx] = true; - List candidates = generator.generate(model, modes, mask); + List candidates = ctx.singleGenerator.generate(model, modes, mask); // Remove based on threshold if (pruner != null) { diff --git a/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/replanning/SelectSingleTripModeStrategyProvider.java b/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/replanning/SelectSingleTripModeStrategyProvider.java index 87970ec9e8d..1692ce65f9a 100644 --- a/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/replanning/SelectSingleTripModeStrategyProvider.java +++ b/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/replanning/SelectSingleTripModeStrategyProvider.java @@ -18,7 +18,7 @@ import java.util.HashSet; /** - * Provider for {@link SelectFromGeneratorStrategy}. + * Provider for {@link SelectSingleTripModeStrategy}. */ public class SelectSingleTripModeStrategyProvider implements Provider { @@ -33,7 +33,7 @@ public class SelectSingleTripModeStrategyProvider implements Provider generator; + private Provider ctx; @Inject private InformedModeChoiceConfigGroup config; @@ -49,7 +49,7 @@ public PlanStrategy get() { PlanStrategyImpl.Builder builder = new PlanStrategyImpl.Builder(new RandomPlanSelector<>()); - builder.addStrategyModule(new SelectSingleTripModeStrategy(globalConfigGroup, new HashSet<>(config.getModes()), generator, selector, pruner, config.isRequireDifferentModes())); + builder.addStrategyModule(new SelectSingleTripModeStrategy(globalConfigGroup, new HashSet<>(config.getModes()), ctx, selector, pruner, config.isRequireDifferentModes())); builder.addStrategyModule(new ReRoute(facilities, tripRouterProvider, globalConfigGroup, timeInterpretation)); diff --git a/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/replanning/SelectSubtourModeStrategy.java b/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/replanning/SelectSubtourModeStrategy.java index 5e64ab5a241..7914851a03c 100644 --- a/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/replanning/SelectSubtourModeStrategy.java +++ b/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/replanning/SelectSubtourModeStrategy.java @@ -32,12 +32,11 @@ public class SelectSubtourModeStrategy extends AbstractMultithreadedModule { private final InformedModeChoiceConfigGroup config; private final SubtourModeChoiceConfigGroup smc; private final Provider generator; - private final IdMap models; private final Set nonChainBasedModes; private final Set switchModes; - public SelectSubtourModeStrategy(Config config, Scenario scenario, Provider generator) { + public SelectSubtourModeStrategy(Config config, Provider generator) { super(config.global()); this.config = ConfigUtils.addOrGetModule(config, InformedModeChoiceConfigGroup.class); @@ -49,19 +48,13 @@ public SelectSubtourModeStrategy(Config config, Scenario scenario, Provider(this.config.getModes()); - - this.models = new IdMap<>(Person.class, scenario.getPopulation().getPersons().size()); - - for (Person value : scenario.getPopulation().getPersons().values()) { - this.models.put(value.getId(), PlanModel.newInstance(value.getSelectedPlan())); - } } @Override public PlanAlgorithm getPlanAlgoInstance() { GeneratorContext context = generator.get(); - return new Algorithm(context, SelectSingleTripModeStrategy.newAlgorithm(context.singleGenerator, context.selector, context.pruner, nonChainBasedModes, config.isRequireDifferentModes())); + return new Algorithm(context, SelectSingleTripModeStrategy.newAlgorithm(context, context.selector, context.pruner, nonChainBasedModes, config.isRequireDifferentModes())); } /** @@ -92,15 +85,12 @@ public Algorithm(GeneratorContext ctx, SelectSingleTripModeStrategy.Algorithm si @Override public void run(Plan plan) { - PlanModel model = models.get(plan.getPerson().getId()); + PlanModel model = ctx.service.getPlanModel(plan); model.setPlan(plan); if (model.trips() == 0) return; - // Force re-estimation - if (rnd.nextDouble() < config.getProbaEstimate()) - model.reset(); // Do change single trip on non-chain based modes with certain probability if (rnd.nextDouble() < smc.getProbaForRandomSingleTripMode() && hasSingleTripChoice(model, nonChainBasedModes)) { diff --git a/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/replanning/SelectSubtourModeStrategyProvider.java b/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/replanning/SelectSubtourModeStrategyProvider.java index 0a23d43ad7c..3604abc03cf 100644 --- a/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/replanning/SelectSubtourModeStrategyProvider.java +++ b/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/replanning/SelectSubtourModeStrategyProvider.java @@ -31,15 +31,13 @@ public class SelectSubtourModeStrategyProvider implements Provider private Provider generator; @Inject private Config config; - @Inject - private Scenario scenario; @Override public PlanStrategy get() { PlanStrategyImpl.Builder builder = new PlanStrategyImpl.Builder(new RandomPlanSelector<>()); - builder.addStrategyModule(new SelectSubtourModeStrategy(config, scenario, generator)); + builder.addStrategyModule(new SelectSubtourModeStrategy(config, generator)); builder.addStrategyModule(new ReRoute(facilities, tripRouterProvider, globalConfigGroup, timeInterpretation)); diff --git a/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/replanning/SimplePlanSelectionStrategy.java b/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/replanning/SimplePlanSelectionStrategy.java index 37f48e67cad..515aadb83d3 100644 --- a/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/replanning/SimplePlanSelectionStrategy.java +++ b/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/replanning/SimplePlanSelectionStrategy.java @@ -19,14 +19,11 @@ public class SimplePlanSelectionStrategy extends AbstractMultithreadedModule { private final Provider generator; - private final Provider selector; public SimplePlanSelectionStrategy(GlobalConfigGroup globalConfigGroup, - Provider generator, - Provider selector) { + Provider generator) { super(globalConfigGroup); this.generator = generator; - this.selector = selector; } @Override @@ -47,7 +44,7 @@ public Algorithm(GeneratorContext ctx) { @Override public void run(Plan plan) { - PlanModel planModel = PlanModel.newInstance(plan); + PlanModel planModel = ctx.service.getPlanModel(plan); List candidates = ctx.generator.generate(planModel); if (ctx.pruner != null) { diff --git a/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/search/AbstractCandidateGenerator.java b/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/search/AbstractCandidateGenerator.java index fe1c92afee2..1bc2d62a546 100644 --- a/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/search/AbstractCandidateGenerator.java +++ b/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/search/AbstractCandidateGenerator.java @@ -18,12 +18,6 @@ */ abstract class AbstractCandidateGenerator implements CandidateGenerator { - @Inject - protected Map tripEstimator; - - @Inject - protected Map fixedCosts; - @Inject protected ScoringParametersForPerson params; @@ -36,6 +30,9 @@ abstract class AbstractCandidateGenerator implements CandidateGenerator { @Inject protected PlanModelService service; + @Inject + protected EstimateCalculator calculator; + @Inject protected Provider pruner; @@ -47,18 +44,4 @@ protected AbstractCandidateGenerator(InformedModeChoiceConfigGroup config) { this.allModes = new HashSet<>(config.getModes()); } - @SuppressWarnings("unchecked") - protected final List> buildConstraints(EstimatorContext context, PlanModel planModel) { - - List> constraints = new ArrayList<>(); - for (TripConstraint c : this.constraints) { - constraints.add(new ConstraintHolder<>( - (TripConstraint) c, - c.getContext(context, planModel) - )); - } - - return constraints; - } - } diff --git a/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/search/SingleTripChoicesGenerator.java b/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/search/SingleTripChoicesGenerator.java index 7f5afad42f4..c5aee7d6659 100644 --- a/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/search/SingleTripChoicesGenerator.java +++ b/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/search/SingleTripChoicesGenerator.java @@ -34,21 +34,15 @@ public List generate(PlanModel planModel, @Nullable Set c throw new IllegalArgumentException("Mask needs length " + planModel.trips() + ": " + Arrays.toString(mask)); EstimatorContext context = new EstimatorContext(planModel.getPerson(), params.getScoringParameters(planModel.getPerson())); - - if (!planModel.hasEstimates()) { - service.initEstimates(context, planModel); - } + List> constraints = calculator.buildConstraints(context, planModel); Predicate considerMode = m -> consideredModes == null || consideredModes.contains(m.getMode()); - - router.routeSingleTrip(planModel, planModel.filterModes(considerMode.and(ModeEstimate::isUsable)), idx); - - service.calculateEstimates(context, planModel); + calculator.prepareEstimates(planModel, context, considerMode.and(ModeEstimate::isUsable), idx); List candidates = new ArrayList<>(); // construct candidates - for (List value : planModel.getEstimates().values()) { + gen: for (List value : planModel.getEstimates().values()) { Optional opt = value.stream() .filter(ModeEstimate::isUsable) @@ -68,11 +62,17 @@ public List generate(PlanModel planModel, @Nullable Set c String[] modes = planModel.getCurrentModes(); modes[idx] = est.getMode(); - double estimate = est.getEstimates()[idx]; + double estimate = calculator.calculatePlanEstimate(context, planModel, modes); if (estimate == Double.NEGATIVE_INFINITY) continue; + for (PlanModelService.ConstraintHolder c : constraints) { + if (!c.test(modes)) { + continue gen; + } + } + candidates.add(new PlanCandidate(modes, estimate)); } diff --git a/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/search/TopKChoicesGenerator.java b/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/search/TopKChoicesGenerator.java index 7641c3f16f7..f1b9d5709a0 100644 --- a/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/search/TopKChoicesGenerator.java +++ b/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/search/TopKChoicesGenerator.java @@ -1,17 +1,18 @@ package org.matsim.modechoice.search; import com.google.inject.Inject; -import it.unimi.dsi.fastutil.doubles.DoubleIterator; -import it.unimi.dsi.fastutil.objects.*; +import it.unimi.dsi.fastutil.objects.Object2ObjectMap; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.matsim.modechoice.*; -import org.matsim.modechoice.estimators.FixedCostsEstimator; -import org.matsim.modechoice.estimators.TripEstimator; import org.matsim.modechoice.pruning.CandidatePruner; import javax.annotation.Nullable; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Set; import java.util.stream.Collectors; import static org.matsim.modechoice.PlanModelService.ConstraintHolder; @@ -24,7 +25,6 @@ public class TopKChoicesGenerator extends AbstractCandidateGenerator { private static final Logger log = LogManager.getLogger(TopKChoicesGenerator.class); - @Inject TopKChoicesGenerator(InformedModeChoiceConfigGroup config) { super(config); @@ -57,34 +57,19 @@ public List generate(PlanModel planModel, @Nullable Set c if (mask != null && mask.length != planModel.trips()) throw new IllegalArgumentException("Mask must be same length as trips."); - prepareModel(planModel, context); + calculator.prepareEstimates(planModel, context, consideredModes, mask); if (planModel.trips() == 0) return new ArrayList<>(); - // modes that need to be re-estimated with the full plan - Set consolidateModes = planModel.filterModes(ModeEstimate::isMin); - - List> constraints = buildConstraints(context, planModel); + List> constraints = calculator.buildConstraints(context, planModel); - return generateCandidate(context, planModel, mask, topK, diffThreshold, absThreshold, consideredModes, consolidateModes, constraints); - } - - protected final void prepareModel(PlanModel planModel, EstimatorContext context) { - - if (!planModel.hasEstimates()) { - service.initEstimates(context, planModel); - } - - if (!planModel.isFullyRouted()) { - router.routeModes(planModel, planModel.filterModes(ModeEstimate::isUsable)); - service.calculateEstimates(context, planModel); - } + return generateCandidate(context, planModel, mask, topK, diffThreshold, absThreshold, consideredModes, constraints); } private List generateCandidate(EstimatorContext context, PlanModel planModel, boolean[] mask, int topK, double diffThreshold, double absThreshold, - Set consideredModes, Set consolidateModes, List> constraints) { + Set consideredModes, List> constraints) { ModeChoiceSearch search = new ModeChoiceSearch(planModel.trips(), planModel.modes()); @@ -111,8 +96,14 @@ private List generateCandidate(EstimatorContext context, PlanMode continue m; } + double[] est = new double[mode.getLegEstimates().length]; + Arrays.setAll(est, i -> mode.getLegEstimates()[i] + mode.getActEst()[i]); + + if (mode.getTripEstimates() != null) + Arrays.setAll(est, i -> est[i] + mode.getTripEstimates()[i]); + // Only add estimates for desired modes and those that have actual usage - search.addEstimates(mode.getMode(), mode.getEstimates(), mask, mode.getNoRealUsage()); + search.addEstimates(mode.getMode(), est, mask, mode.getNoRealUsage()); } if (search.isEmpty()) @@ -140,27 +131,6 @@ private List generateCandidate(EstimatorContext context, PlanMode } } - // Estimation of already existing modes that were predetermined - // the search will not add them, so they need to be calculated here - double preDeterminedEstimate = 0; - if (mask != null) { - - for (int i = 0; i < mask.length; i++) { - - // only select the unmasked modes - if (mask[i] || result[i] == null) - continue; - - for (ModeEstimate option : options) { - if (option.getMode().equals(result[i])) - preDeterminedEstimate += option.getEstimates()[i]; - } - } - } - - // store which modes have been used for one solution - ReferenceSet usedModes = new ReferenceOpenHashSet<>(); - int k = 0; int n = 0; ModeIterator it = search.iter(result); @@ -168,7 +138,7 @@ private List generateCandidate(EstimatorContext context, PlanMode outer: while (it.hasNext() && k < topK) { - double estimate = preDeterminedEstimate + it.nextDouble(); + double estimate = it.nextDouble(); if (n++ > it.maxIters()) { log.warn("Maximum number of iterations reached for person {}", context.person.getId()); @@ -180,43 +150,15 @@ private List generateCandidate(EstimatorContext context, PlanMode continue outer; } - - Collections.addAll(usedModes, result); - - estimate += computePlanEstimate(context, planModel, result, usedModes, consolidateModes, options); - - usedModes.clear(); - if (estimate > best) best = estimate; if (!Double.isNaN(diffThreshold) && diffThreshold >= 0 && best - estimate > diffThreshold) break; - // absolute threshold - if (!Double.isNaN(absThreshold) && estimate < absThreshold) - break; - - PlanCandidate c = new PlanCandidate(Arrays.copyOf(result, planModel.trips()), estimate); - - if (candidates.containsKey(c)) { - if (Math.abs(candidates.get(c).getUtility() - c.getUtility()) > 1e-5) { - - // TODO: probably needs to be investigated - // Only occurs with certain configurations, usually including the pt mode - // Not big issue probably, might remove the warning - -// throw new IllegalStateException("Candidates estimates differ: " + candidates.get(c) + " | " + c); - log.warn("Estimates differ for person {}: {} vs. {}", planModel.getPerson().getId(), candidates.get(c), c.getUtility()); - } - - // Put the candidate with the higher utility in the map - if (c.getUtility() > candidates.get(c).getUtility()) { - candidates.put(c, c); - } - - } else - candidates.put(c, c); + // Store the whole plan estimate, which may differ from the partial estimate generated during search + PlanCandidate c = new PlanCandidate(Arrays.copyOf(result, planModel.trips()), calculator.calculatePlanEstimate(context, planModel, result)); + candidates.put(c, c); k++; } @@ -235,56 +177,12 @@ private List generateCandidate(EstimatorContext context, PlanMode result.removeIf(c -> c.getUtility() < best - diffThreshold); } - return result; - } - - /** - * Handles fixed costs and plan estimators. - */ - @SuppressWarnings("StringEquality") - private double computePlanEstimate(EstimatorContext context, PlanModel planModel, String[] result, - ReferenceSet usedModes, - Set consolidateModes, Collection options) { - - double estimate = 0; - - // Add the fixed costs estimate if a mode has been used - for (ModeEstimate mode : options) { - - FixedCostsEstimator f = fixedCosts.get(mode.getMode()); - - // Fixed costs are not required for each mode - if (f == null) - continue; - - if (usedModes.contains(mode.getMode())) { - estimate += f.usageUtility(context, mode.getMode(), mode.getOption()); - } - - estimate += f.fixedUtility(context, mode.getMode(), mode.getOption()); - } - - for (String consolidateMode : consolidateModes) { - // search all options for the mode to consolidate - for (ModeEstimate mode : options) { - if (mode.getMode() == consolidateMode && usedModes.contains(consolidateMode)) { - - TripEstimator f = tripEstimator.get(mode.getMode()); - - // subtract all the trip estimates that have been made before - for (int i = 0; i < result.length; i++) { - if (result[i] == mode.getMode()) - estimate -= mode.getTripEstimates()[i]; - } - - // Add the estimate for the whole plan - estimate += f.estimate(context, mode.getMode(), result, planModel, mode.getOption()); - - } - } + // absolute threshold + if (!Double.isNaN(absThreshold)) { + result.removeIf(c -> c.getUtility() < absThreshold); } - return estimate; + return result; } /** @@ -299,25 +197,10 @@ public List generatePredefined(PlanModel planModel, List candidates = new ArrayList<>(); EstimatorContext context = new EstimatorContext(planModel.getPerson(), params.getScoringParameters(planModel.getPerson())); - prepareModel(planModel, context); - - Map singleOptions = new HashMap<>(); - - // reduce to single options that are usable - for (Map.Entry> e : planModel.getEstimates().entrySet()) { - for (ModeEstimate o : e.getValue()) { - // check if a mode can be used at all - if (!o.isUsable() || o.isMin()) - continue; - singleOptions.put(e.getKey(), o); - break; - } - } + calculator.prepareEstimates(planModel, context, modes); - List> constraints = buildConstraints(context, planModel); - - Set consolidateModes = planModel.filterModes(ModeEstimate::isMin); + List> constraints = calculator.buildConstraints(context, planModel); Set usableModes = planModel.filterModes(ModeEstimate::isUsable); // Same Logic as the top k estimator @@ -326,40 +209,16 @@ public List generatePredefined(PlanModel planModel, List usedModes = new ReferenceOpenHashSet<>(); - - // Collect estimates for all entries - for (int i = 0; i < result.length; i++) { - - if (result[i] == null) - continue; - - result[i] = result[i].intern(); - - String mode = result[i]; + double estimate = calculator.calculatePlanEstimate(context, planModel, result); - ModeEstimate opt = singleOptions.get(mode); - if (opt == null) { - - // Violates constraints by using a non-allowed mode - if (allModes.contains(mode) && !usableModes.contains(mode)) { - estimate = Double.NEGATIVE_INFINITY; - break; - } else - continue; + for (String mode : result) { + // Violates constraints by using a non-allowed mode + if (allModes.contains(mode) && !usableModes.contains(mode)) { + estimate = Double.NEGATIVE_INFINITY; + break; } - - estimate += opt.getEstimates()[i]; - - // Store modes that have been used - if (!opt.getNoRealUsage()[i]) - usedModes.add(mode); } - estimate += computePlanEstimate(context, planModel, result, usedModes, consolidateModes, singleOptions.values()); - for (ConstraintHolder c : constraints) { if (!c.test(result)) estimate = Double.NEGATIVE_INFINITY; diff --git a/contribs/informed-mode-choice/src/test/java/org/matsim/modechoice/EstimateRouterTest.java b/contribs/informed-mode-choice/src/test/java/org/matsim/modechoice/EstimateRouterTest.java index b91a3b8ea45..99b02063aa1 100644 --- a/contribs/informed-mode-choice/src/test/java/org/matsim/modechoice/EstimateRouterTest.java +++ b/contribs/informed-mode-choice/src/test/java/org/matsim/modechoice/EstimateRouterTest.java @@ -50,7 +50,7 @@ void routing() { Plan plan = persons.get(TestScenario.Agents.get(1)).getSelectedPlan(); PlanModel planModel = PlanModel.newInstance(plan); - router.routeModes(planModel, group.getModes()); + router.routeModes(planModel, group.getModes(), TripModeFilter.ACCEPT_ALL); assertThat(planModel) .isNotNull(); diff --git a/contribs/informed-mode-choice/src/test/java/org/matsim/modechoice/estimators/DefaultActivityEstimatorTest.java b/contribs/informed-mode-choice/src/test/java/org/matsim/modechoice/estimators/DefaultActivityEstimatorTest.java index 53ba481d56c..c82b60ca29f 100644 --- a/contribs/informed-mode-choice/src/test/java/org/matsim/modechoice/estimators/DefaultActivityEstimatorTest.java +++ b/contribs/informed-mode-choice/src/test/java/org/matsim/modechoice/estimators/DefaultActivityEstimatorTest.java @@ -45,4 +45,4 @@ void person() { .isEqualTo(-4, Offset.offset(0.1)); } -} \ No newline at end of file +} diff --git a/contribs/informed-mode-choice/src/test/java/org/matsim/modechoice/search/SingleTripChoicesGeneratorTest.java b/contribs/informed-mode-choice/src/test/java/org/matsim/modechoice/search/SingleTripChoicesGeneratorTest.java index f9896ac0c2f..2dc0e474e17 100644 --- a/contribs/informed-mode-choice/src/test/java/org/matsim/modechoice/search/SingleTripChoicesGeneratorTest.java +++ b/contribs/informed-mode-choice/src/test/java/org/matsim/modechoice/search/SingleTripChoicesGeneratorTest.java @@ -28,10 +28,10 @@ void choices() { Collection candidates = generator.generate(model, null, new boolean[]{true, false, false, false}); assertThat(candidates) - .first().matches(c -> c.getMode(0).equals(TransportMode.car)); + .first().matches(c -> c.getMode(0).equals(TransportMode.bike)); assertThat(candidates) - .last().matches(c -> c.getMode(0).equals(TransportMode.ride)); + .last().matches(c -> c.getMode(0).equals(TransportMode.car)); } @@ -67,7 +67,7 @@ void subset() { assertThat(candidates) .hasSize(2) - .first().matches(c -> c.getMode(0).equals(TransportMode.car)); + .first().matches(c -> c.getMode(0).equals(TransportMode.walk)); } -} \ No newline at end of file +} diff --git a/contribs/informed-mode-choice/src/test/java/org/matsim/modechoice/search/TopKMinMaxTest.java b/contribs/informed-mode-choice/src/test/java/org/matsim/modechoice/search/TopKMinMaxTest.java index 4ee5e1b2562..494a89eb199 100644 --- a/contribs/informed-mode-choice/src/test/java/org/matsim/modechoice/search/TopKMinMaxTest.java +++ b/contribs/informed-mode-choice/src/test/java/org/matsim/modechoice/search/TopKMinMaxTest.java @@ -18,6 +18,7 @@ import org.matsim.core.config.ConfigUtils; import org.matsim.core.config.groups.PlansConfigGroup; import org.matsim.core.controler.ControlerListenerManager; +import org.matsim.core.controler.OutputDirectoryHierarchy; import org.matsim.core.population.PopulationUtils; import org.matsim.core.router.DefaultAnalysisMainModeIdentifier; import org.matsim.core.router.TripRouter; @@ -183,6 +184,7 @@ protected void configure() { bind(EventsManager.class).toInstance(em); bind(ControlerListenerManager.class).toInstance(cl); + bind(OutputDirectoryHierarchy.class).toInstance(new OutputDirectoryHierarchy(config)); bind(TimeInterpretation.class).toInstance(TimeInterpretation.create(PlansConfigGroup.ActivityDurationInterpretation.tryEndTimeThenDuration, PlansConfigGroup.TripDurationHandling.shiftActivityEndTimes, 0)); @@ -264,7 +266,7 @@ public MinMaxEstimate estimate(EstimatorContext context, String mode, PlanModel } @Override - public double estimate(EstimatorContext context, String mode, String[] modes, PlanModel plan, ModeAvailability option) { + public double estimatePlan(EstimatorContext context, String mode, String[] modes, PlanModel plan, ModeAvailability option) { double est = 0; for (String m : modes) { diff --git a/contribs/vsp/src/main/java/org/matsim/contrib/vsp/pt/fare/PtTripWithDistanceBasedFareEstimator.java b/contribs/vsp/src/main/java/org/matsim/contrib/vsp/pt/fare/PtTripWithDistanceBasedFareEstimator.java index b1d05244534..e92feee0902 100644 --- a/contribs/vsp/src/main/java/org/matsim/contrib/vsp/pt/fare/PtTripWithDistanceBasedFareEstimator.java +++ b/contribs/vsp/src/main/java/org/matsim/contrib/vsp/pt/fare/PtTripWithDistanceBasedFareEstimator.java @@ -74,7 +74,7 @@ public MinMaxEstimate estimate(EstimatorContext context, String mode, PlanModel @Override @SuppressWarnings("StringEquality") - public double estimate(EstimatorContext context, String mode, String[] modes, PlanModel plan, ModeAvailability option) { + public double estimatePlan(EstimatorContext context, String mode, String[] modes, PlanModel plan, ModeAvailability option) { double utility = 0; DoubleList fares = new DoubleArrayList(); diff --git a/contribs/vsp/src/test/java/org/matsim/contrib/vsp/pt/fare/PtTripFareEstimatorTest.java b/contribs/vsp/src/test/java/org/matsim/contrib/vsp/pt/fare/PtTripFareEstimatorTest.java index 7c7126b66f9..71f63ce523c 100644 --- a/contribs/vsp/src/test/java/org/matsim/contrib/vsp/pt/fare/PtTripFareEstimatorTest.java +++ b/contribs/vsp/src/test/java/org/matsim/contrib/vsp/pt/fare/PtTripFareEstimatorTest.java @@ -95,7 +95,7 @@ private List estimateAgent(Id personId) { PlanModel model = PlanModel.newInstance(plan); - router.routeModes(model, Set.of(TransportMode.pt, TransportMode.walk, TransportMode.bike, TransportMode.car)); + router.routeModes(model, Set.of(TransportMode.pt, TransportMode.walk, TransportMode.bike, TransportMode.car), TripModeFilter.ACCEPT_ALL); List ests = new ArrayList<>(); @@ -150,7 +150,7 @@ void planEstimate() { PlanModel model = PlanModel.newInstance(plan); - router.routeModes(model, Set.of(TransportMode.pt, TransportMode.walk, TransportMode.bike, TransportMode.car)); + router.routeModes(model, Set.of(TransportMode.pt, TransportMode.walk, TransportMode.bike, TransportMode.car), TripModeFilter.ACCEPT_ALL); List singleTrips = estimateAgent(TestScenario.Agents.get(2)); @@ -160,7 +160,7 @@ void planEstimate() { System.out.println(singleTrips); // 2nd one does hat have a pt connection - double estimate = estimator.estimate(context, TransportMode.pt, new String[]{"pt", "car", "pt", "pt", "pt"}, model, ModeAvailability.YES); + double estimate = estimator.estimatePlan(context, TransportMode.pt, new String[]{"pt", "car", "pt", "pt", "pt"}, model, ModeAvailability.YES); assertThat(estimate) .isLessThanOrEqualTo(maxSum) @@ -168,7 +168,7 @@ void planEstimate() { .isCloseTo(-2738.72, Offset.offset(0.1)); - estimate = estimator.estimate(context, TransportMode.pt, new String[]{"pt", "car", "car", "car", "pt"}, model, ModeAvailability.YES); + estimate = estimator.estimatePlan(context, TransportMode.pt, new String[]{"pt", "car", "car", "car", "pt"}, model, ModeAvailability.YES); assertThat(estimate) .isLessThanOrEqualTo(maxSum) @@ -176,7 +176,7 @@ void planEstimate() { .isCloseTo(-1222.91, Offset.offset(0.1)); // Essentially single trip - estimate = estimator.estimate(context, TransportMode.pt, new String[]{"pt", "car", "car", "car", "car"}, model, ModeAvailability.YES); + estimate = estimator.estimatePlan(context, TransportMode.pt, new String[]{"pt", "car", "car", "car", "car"}, model, ModeAvailability.YES); assertThat(estimate) .isCloseTo(singleTrips.get(0).getMin(), Offset.offset(0.1));