Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

IMC contrib maintenance #3648

Merged
merged 17 commits into from
Dec 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
package org.matsim.modechoice;

import com.google.inject.Inject;
import org.matsim.api.core.v01.TransportMode;
import org.matsim.api.core.v01.population.Leg;
import org.matsim.api.core.v01.population.PlanElement;
import org.matsim.core.config.groups.PlansConfigGroup;
import org.matsim.core.router.AnalysisMainModeIdentifier;
import org.matsim.core.router.TripRouter;
import org.matsim.core.router.TripStructureUtils;
import org.matsim.core.utils.timing.TimeInterpretation;
Expand All @@ -23,14 +24,18 @@ public final class EstimateRouter {

private final TripRouter tripRouter;
private final ActivityFacilities facilities;
private final AnalysisMainModeIdentifier mmi;
private final TimeInterpretation timeInterpretation;

@Inject
public EstimateRouter(TripRouter tripRouter, ActivityFacilities facilities,
TimeInterpretation timeInterpretation) {
AnalysisMainModeIdentifier mmi) {
this.tripRouter = tripRouter;
this.facilities = facilities;
this.timeInterpretation = timeInterpretation;
this.mmi = mmi;
// ignore the travel times of individual legs
this.timeInterpretation = TimeInterpretation.create(PlansConfigGroup.ActivityDurationInterpretation.tryEndTimeThenDuration,
PlansConfigGroup.TripDurationHandling.ignoreDelays);
}

/**
Expand Down Expand Up @@ -73,14 +78,14 @@ public void routeModes(PlanModel model, Collection<String> modes) {
}
*/

// Use the end-time of an activity or the time tracker if not available
final List<? extends PlanElement> newTrip = tripRouter.calcRoute(
mode, from, to,
oldTrip.getOriginActivity().getEndTime().orElse(timeTracker.getTime().seconds()),
model.getPerson(),
oldTrip.getTripAttributes()
);

// update time tracker, however it will be updated before each iteration
timeTracker.addElements(newTrip);

// store and increment
Expand All @@ -89,21 +94,6 @@ public void routeModes(PlanModel model, Collection<String> modes) {
.map(el -> (Leg) el)
.collect(Collectors.toList());

// The PT router can return walk only trips that don't actually use pt
// this one special case is handled here, it is unclear if similar behaviour might be present in other modes
if (mode.equals(TransportMode.pt) && ll.stream().noneMatch(l -> l.getMode().equals(TransportMode.pt))) {
legs[i++] = null;
continue;
}

// TODO: might consider access agress walk modes

// Filters all kind of modes that did return only walk legs when they could not be used (e.g. drt)
if (!mode.equals(TransportMode.walk) && ll.stream().allMatch(l -> l.getMode().equals(TransportMode.walk))) {
legs[i++] = null;
continue;
}

legs[i++] = ll;

}
Expand Down Expand Up @@ -146,12 +136,6 @@ public void routeSingleTrip(PlanModel model, Collection<String> modes, int idx)
.map(el -> (Leg) el)
.collect(Collectors.toList());

// not a real pt trip, see reasoning above
if (mode.equals(TransportMode.pt) && ll.stream().noneMatch(l -> l.getMode().equals(TransportMode.pt))) {
model.setLegs(mode, new List[model.trips()]);
continue;
}

List<Leg>[] legs = new List[model.trips()];
legs[idx] = ll;

Expand Down Expand Up @@ -183,10 +167,13 @@ private double advanceTimetracker(TimeTracker timeTracker, TripStructureUtils.Tr
List<Leg> oldLegs = oldTrip.getLegsOnly();
boolean undefined = oldLegs.stream().anyMatch(l -> timeInterpretation.decideOnLegTravelTime(l).isUndefined());

// If no time is known the previous trips need to be routed
// If no time is known the previous trips needs to be routed
if (undefined) {
String routingMode = TripStructureUtils.getRoutingMode(oldLegs.get(0));
List<? extends PlanElement> legs = routeTrip(oldTrip, plan, routingMode != null ? routingMode : oldLegs.get(0).getMode(), timeTracker);
String routingMode = TripStructureUtils.getRoutingMode(oldLegs.getFirst());
if (routingMode == null)
routingMode = mmi.identifyMainMode(oldLegs);

List<? extends PlanElement> legs = routeTrip(oldTrip, plan, routingMode, timeTracker);
timeTracker.addElements(legs);

} else
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ public class InformedModeChoiceConfigGroup extends ReflectiveConfigGroup {
" POSITIVE_INFINITY will select randomly from the best k.")
private double invBeta = Double.POSITIVE_INFINITY;

@Parameter
@Comment("Normalize utility values when selecting")
private boolean normalizeUtility = false;

@Parameter
@Comment("Name of the candidate pruner to apply, needs to be bound with guice.")
private String pruning = null;
Expand Down Expand Up @@ -142,6 +146,14 @@ public Map<String, String> getComments() {
return comments;
}

public boolean isNormalizeUtility() {
return normalizeUtility;
}

public void setNormalizeUtility(boolean normalizeUtility) {
this.normalizeUtility = normalizeUtility;
}

public enum Schedule {
off,
linear,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,24 +54,31 @@ public static void replaceReplanningStrategy(Config config, String subpopulation
// Copy list because it is unmodifiable
List<ReplanningConfigGroup.StrategySettings> strategies = new ArrayList<>(config.replanning().getStrategySettings());
List<ReplanningConfigGroup.StrategySettings> found = strategies.stream()
.filter(s -> s.getSubpopulation().equals(subpopulation))
.filter(s -> subpopulation == null || Objects.equals(s.getSubpopulation(), subpopulation))
.filter(s -> s.getStrategyName().equals(existing))
.toList();

if (found.isEmpty())
throw new IllegalArgumentException("No strategy %s found for subpopulation %s".formatted(existing, subpopulation));

if (found.size() > 1)
if (subpopulation != null && found.size() > 1)
throw new IllegalArgumentException("Multiple strategies %s found for subpopulation %s".formatted(existing, subpopulation));

ReplanningConfigGroup.StrategySettings old = found.getFirst();
old.setStrategyName(replacement);
found.forEach(s -> s.setStrategyName(replacement));

// reset und set new strategies
config.replanning().clearStrategySettings();
strategies.forEach(s -> config.replanning().addStrategySettings(s));
}

/**
* Replace a strategy in the config for all subpoopulations.
* @see #replaceReplanningStrategy(Config, String, String, String)
*/
public static void replaceReplanningStrategy(Config config, String existing, String replacement) {
replaceReplanningStrategy(config, null, existing, replacement);
}

public static Builder newBuilder() {
return new Builder();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.matsim.modechoice;

import com.google.inject.Inject;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.matsim.core.config.Config;
Expand Down Expand Up @@ -30,14 +31,16 @@ public final class ModeChoiceWeightScheduler implements StartupListener, Iterati

private InformedModeChoiceConfigGroup.Schedule anneal;

@Override
public void notifyStartup(StartupEvent event) {

Config config = event.getServices().getConfig();
@Inject
public ModeChoiceWeightScheduler(Config config) {
InformedModeChoiceConfigGroup imc = ConfigUtils.addOrGetModule(config, InformedModeChoiceConfigGroup.class);

startBeta = currentBeta = imc.getInvBeta();
anneal = imc.getAnneal();
}

@Override
public void notifyStartup(StartupEvent event) {
Config config = event.getServices().getConfig();

// The first iteration does not do any replanning
n = config.controller().getLastIteration() - 1;
Expand All @@ -52,7 +55,7 @@ public void notifyStartup(StartupEvent event) {
@Override
public void notifyIterationStarts(IterationStartsEvent event) {

if (anneal == InformedModeChoiceConfigGroup.Schedule.off || event.getIteration() == 0)
if (anneal == InformedModeChoiceConfigGroup.Schedule.off || event.getIteration() == 0 || currentBeta == Double.POSITIVE_INFINITY)
return;

// anneal target is 0, iterations are offset by 1 because first iteration does not do replanning
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ public final class ModeEstimate {
private final double[] est;
private final double[] tripEst;

/**
* Mark trips with no real usage. E.g pt trips that consist only of walk legs.
* These trips will not be considered during estimation.
*/
private final boolean[] noRealUsage;

/**
* Whether this should be for a minimum estimate. Otherwise, maximum is assumed.
*/
Expand All @@ -42,6 +48,7 @@ public final class ModeEstimate {
this.usable = isUsable;
this.est = usable ? new double[n] : null;
this.tripEst = storeTripEst ? new double[n] : null;
this.noRealUsage = usable ? new boolean[n] : null;
}

public String getMode() {
Expand All @@ -68,6 +75,10 @@ public double[] getTripEstimates() {
return tripEst;
}

public boolean[] getNoRealUsage() {
return noRealUsage;
}

@Override
public String toString() {
return mode + "=" + option + (min ? " (min) " : "");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,6 @@ public final class PlanModel implements Iterable<TripStructureUtils.Trip>, HasPe
*/
private boolean fullyRouted;

/**
* Original plan.
*/
private Plan plan;

/**
* Create a new plan model instance from an existing plan.
*/
Expand All @@ -66,7 +61,6 @@ private PlanModel(Plan plan) {
List<TripStructureUtils.Trip> tripList = TripStructureUtils.getTrips(plan);

this.trips = tripList.toArray(new TripStructureUtils.Trip[0]);
this.plan = plan;
this.legs = new HashMap<>();
this.estimates = new HashMap<>();
this.currentModes = new String[trips.length];
Expand All @@ -80,11 +74,6 @@ public Person getPerson() {
return person;
}

public Plan getPlan() {
// TODO: This should better be removed, memory usage by keeping these plans is increased
return plan;
}

public int trips() {
return trips.length;
}
Expand Down Expand Up @@ -123,8 +112,6 @@ public double[] getStartTimes() {
* Update current plan an underlying modes.
*/
public void setPlan(Plan plan) {
this.plan = plan;

List<TripStructureUtils.Trip> newTrips = TripStructureUtils.getTrips(plan);

if (newTrips.size() != this.trips.length)
Expand Down Expand Up @@ -223,6 +210,13 @@ public TripStructureUtils.Trip getTrip(int i) {
return trips[i];
}

/**
* Get all trips of the day.
*/
public List<TripStructureUtils.Trip> getTrips() {
return Arrays.asList(trips);
}

void setLegs(String mode, List<Leg>[] legs) {
mode = mode.intern();

Expand Down Expand Up @@ -257,6 +251,9 @@ public Map<String, List<ModeEstimate>> getEstimates() {
return estimates;
}

/**
* Iterate over estimates and collect modes that match the predicate.
*/
public Set<String> filterModes(Predicate<? super ModeEstimate> predicate) {
Set<String> modes = new HashSet<>();
for (Map.Entry<String, List<ModeEstimate>> e : estimates.entrySet()) {
Expand Down Expand Up @@ -300,6 +297,20 @@ public List<Leg> getLegs(String mode, int i) {
return legs[i];
}

/**
* Check whether a mode is available for a trip.
* If for instance not pt option is found in the legs this will return false.
*/
public boolean hasModeForTrip(String mode, int i) {

List<Leg>[] legs = this.legs.get(mode);
if (legs == null)
return false;

List<Leg> ll = legs[i];
return ll.stream().anyMatch(l -> l.getMode().equals(mode));
}

/**
* Delete stored routes and estimates.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,8 +167,10 @@ public void calculateEstimates(EstimatorContext context, PlanModel planModel) {
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++) {
Expand All @@ -181,13 +183,15 @@ public void calculateEstimates(EstimatorContext context, PlanModel planModel) {
continue;
}

TripEstimator tripEst = tripEstimator.get(c.getMode());
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) {
if (tripEst != null && realUsage) {
MinMaxEstimate minMax = tripEst.estimate(context, c.getMode(), planModel, legs, c.getOption());
double tripEstimate = c.isMin() ? minMax.getMin() : minMax.getMax();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public RelaxedMassConservationConstraint(SubtourModeChoiceConfigGroup config) {
@Override
public Context getContext(EstimatorContext context, PlanModel model) {

Collection<TripStructureUtils.Subtour> subtours = TripStructureUtils.getSubtours(model.getPlan(), coordDistance);
Collection<TripStructureUtils.Subtour> subtours = TripStructureUtils.getSubtoursFromTrips(model.getTrips(), coordDistance);

Object2IntMap<Object> facilities = new Object2IntArrayMap<>();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public RelaxedSubtourConstraint(SubtourModeChoiceConfigGroup config) {
@Override
public int[] getContext(EstimatorContext context, PlanModel model) {

Collection<TripStructureUtils.Subtour> subtours = TripStructureUtils.getSubtours(model.getPlan(), coordDistance);
Collection<TripStructureUtils.Subtour> subtours = TripStructureUtils.getSubtoursFromTrips(model.getTrips(), coordDistance);

// ids will contain unique identifier to which subtour a trip belongs.
int[] ids = new int[model.trips()];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,12 @@ default double planThreshold(PlanModel planModel) {
return -1;
}


/**
* Calculate threshold to be applied on a single trip. Modes worse than this threshold on this trip will be discarded.
*
* @return positive threshold, if negative it will not be applied
*/
default double tripThreshold(PlanModel planModel, int idx) {
return -1;
return planThreshold(planModel);
}
}
Loading
Loading