Skip to content

Commit

Permalink
feat: generalize pseudo random errors (#290)
Browse files Browse the repository at this point in the history
* feat: generalize pseudo random errors

* make prefix private
  • Loading branch information
sebhoerl authored Jan 20, 2025
1 parent 55e4dc8 commit d4af27e
Show file tree
Hide file tree
Showing 8 changed files with 72 additions and 99 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ public class EqasimConfigGroup extends ReflectiveConfigGroup {

private final static String USE_SCHEDULE_BASED_TRANSPORT = "useScheduleBasedTransport";

private final static String USE_PSEUDO_RANDOM_ERRORS = "usePseudoRandomErrors";

private double sampleSize = 1.0;
private DistanceUnit distanceUnit = DistanceUnit.meter;

Expand All @@ -42,6 +44,8 @@ public class EqasimConfigGroup extends ReflectiveConfigGroup {

private boolean useScheduleBasedTransport = true;

private boolean usePseudoRandomErrors = false;

public EqasimConfigGroup() {
super(GROUP_NAME);
}
Expand All @@ -66,6 +70,16 @@ public void setSampleSize(double sampleSize) {
this.sampleSize = sampleSize;
}

@StringGetter(USE_PSEUDO_RANDOM_ERRORS)
public boolean getUsePseudoRandomErrors() {
return usePseudoRandomErrors;
}

@StringSetter(USE_PSEUDO_RANDOM_ERRORS)
public void setUsePseudoRandomErrors(boolean usePseudoRandomErrors) {
this.usePseudoRandomErrors = usePseudoRandomErrors;
}

@Override
public ConfigGroup createParameterSet(String type) {
switch (type) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@
import org.eqasim.core.simulation.mode_choice.constraints.PassengerConstraint;
import org.eqasim.core.simulation.mode_choice.cost.CostModel;
import org.eqasim.core.simulation.mode_choice.cost.ZeroCostModel;
import org.eqasim.core.simulation.mode_choice.epsilon.EpsilonModule;
import org.eqasim.core.simulation.mode_choice.epsilon.EpsilonProvider;
import org.eqasim.core.simulation.mode_choice.filters.OutsideFilter;
import org.eqasim.core.simulation.mode_choice.filters.TourLengthFilter;
import org.eqasim.core.simulation.mode_choice.utilities.ModalUtilityEstimator;
import org.eqasim.core.simulation.mode_choice.utilities.EqasimUtilityEstimator;
import org.eqasim.core.simulation.mode_choice.utilities.UtilityEstimator;
import org.eqasim.core.simulation.mode_choice.utilities.estimators.BikeUtilityEstimator;
import org.eqasim.core.simulation.mode_choice.utilities.estimators.CarUtilityEstimator;
Expand Down Expand Up @@ -76,7 +78,7 @@ protected void installEqasimExtension() {
bindTourFilter(TOUR_LENGTH_FILTER_NAME).to(TourLengthFilter.class);
bindTourFilter(OUTSIDE_FILTER_NAME).to(OutsideFilter.class);

bindTripEstimator(UTILITY_ESTIMATOR_NAME).to(ModalUtilityEstimator.class);
bindTripEstimator(UTILITY_ESTIMATOR_NAME).to(EqasimUtilityEstimator.class);

bind(CarPredictor.class);
bind(PtPredictor.class);
Expand All @@ -96,12 +98,14 @@ protected void installEqasimExtension() {

bindTourConstraintFactory(VEHICLE_TOUR_CONSTRAINT).to(EqasimVehicleTourConstraint.Factory.class);
bindHomeFinder(HOME_FINDER).to(EqasimHomeFinder.class);

install(new EpsilonModule());
}

@Provides
public ModalUtilityEstimator provideModularUtilityEstimator(TripRouter tripRouter, ActivityFacilities facilities,
public EqasimUtilityEstimator provideModularUtilityEstimator(TripRouter tripRouter, ActivityFacilities facilities,
Map<String, Provider<UtilityEstimator>> factory, EqasimConfigGroup config,
TimeInterpretation timeInterpretation, DiscreteModeChoiceConfigGroup dmcConfig) {
TimeInterpretation timeInterpretation, DiscreteModeChoiceConfigGroup dmcConfig, EpsilonProvider epsilonProvider) {
Map<String, UtilityEstimator> estimators = new HashMap<>();

for (Map.Entry<String, String> entry : config.getEstimators().entrySet()) {
Expand All @@ -115,8 +119,8 @@ public ModalUtilityEstimator provideModularUtilityEstimator(TripRouter tripRoute
}
}

return new ModalUtilityEstimator(tripRouter, facilities, estimators, timeInterpretation,
Collections.emptySet()); // Here we may add "pt" etc. as pre-routed modes.
return new EqasimUtilityEstimator(tripRouter, facilities, estimators, timeInterpretation,
Collections.emptySet(), epsilonProvider); // Here we may add "pt" etc. as pre-routed modes.
}

@Provides
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@
import org.matsim.core.config.Config;
import org.matsim.core.config.ConfigUtils;

import java.util.Map;

public class AdaptConfigForEpsilon {

public static void main(String[] args) throws CommandLine.ConfigurationException {
Expand All @@ -21,15 +19,8 @@ public static void main(String[] args) throws CommandLine.ConfigurationException
discreteModeChoiceConfigGroup.setSelector(SelectorModule.MAXIMUM);

EqasimConfigGroup eqasimConfigGroup = (EqasimConfigGroup) config.getModules().get(EqasimConfigGroup.GROUP_NAME);


for(Map.Entry<String, String> entry: eqasimConfigGroup.getEstimators().entrySet()) {
if(entry.getValue().startsWith(EpsilonModule.EPSILON_UTILITY_PREFIX)) {
continue;
}
eqasimConfigGroup.setEstimator(entry.getKey(), EpsilonModule.EPSILON_UTILITY_PREFIX + entry.getValue());
}

eqasimConfigGroup.setUsePseudoRandomErrors(true);

ConfigUtils.writeConfig(config, commandLine.getOptionStrict("output-config-path"));
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,59 +1,45 @@
package org.eqasim.core.simulation.mode_choice.epsilon;

import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Provides;
import java.util.Map;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.eqasim.core.components.config.EqasimConfigGroup;
import org.eqasim.core.simulation.mode_choice.AbstractEqasimExtension;
import org.eqasim.core.simulation.mode_choice.utilities.UtilityEstimator;
import org.matsim.core.config.groups.GlobalConfigGroup;

import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import com.google.inject.Provides;

public class EpsilonModule extends AbstractEqasimExtension {

public static final Logger logger = LogManager.getLogger(EpsilonModule.class);

public static final String EPSILON_UTILITY_PREFIX = "epsilon_";
// made private to avoid use
private static final String EPSILON_UTILITY_PREFIX = "epsilon_";

@Provides
public GumbelEpsilonProvider provideGumbelEpsilonProvider(GlobalConfigGroup config) {
return new GumbelEpsilonProvider(config.getRandomSeed(), 1.0);
}

@Override
protected void installEqasimExtension() {
bind(EpsilonProvider.class).to(GumbelEpsilonProvider.class);
EqasimConfigGroup eqasimConfigGroup = EqasimConfigGroup.get(getConfig());

if (eqasimConfigGroup.getUsePseudoRandomErrors()) {
bind(EpsilonProvider.class).to(GumbelEpsilonProvider.class);
} else {
bind(EpsilonProvider.class).toInstance(NoopEpsilonProvider.INSTANCE);
}

EqasimConfigGroup eqasimConfigGroup = (EqasimConfigGroup) getConfig().getModules().get(EqasimConfigGroup.GROUP_NAME);
Set<String> processed = new HashSet<>();
for(Map.Entry<String, String > entry: eqasimConfigGroup.getEstimators().entrySet()) {
for (Map.Entry<String, String> entry : eqasimConfigGroup.getEstimators().entrySet()) {
String mode = entry.getKey();
String utilityEstimator = entry.getValue();
if(utilityEstimator.startsWith(EPSILON_UTILITY_PREFIX)) {
if(processed.contains(utilityEstimator)) {
logger.warn(String.format("The epsilon utility estimator '%s' is used for more than one mode. The seed of the epsilon generator will rely on the first mode", utilityEstimator));
continue;
}
processed.add(utilityEstimator);
String baseEstimator = utilityEstimator.substring(EPSILON_UTILITY_PREFIX.length());
bindUtilityEstimator(utilityEstimator).toProvider(new Provider<>() {
@Inject
private Map<String, Provider<UtilityEstimator>> factory;

@Inject
private EpsilonProvider epsilonProvider;

@Override
public UtilityEstimator get() {
UtilityEstimator delegate = factory.get(baseEstimator).get();
return new EpsilonAdapter(mode, delegate, epsilonProvider);
}
});

if (utilityEstimator.startsWith(EPSILON_UTILITY_PREFIX)) {
throw new IllegalStateException(String.format(
"Estimator for %s is prefixed with %s. Use eqasim.usePseudoRandomErrors = true instead now.",
mode, EPSILON_UTILITY_PREFIX));
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package org.eqasim.core.simulation.mode_choice.epsilon;

import org.matsim.api.core.v01.Id;
import org.matsim.api.core.v01.population.Person;

public class NoopEpsilonProvider implements EpsilonProvider {
final static public NoopEpsilonProvider INSTANCE = new NoopEpsilonProvider();

@Override
public double getEpsilon(Id<Person> personId, int tripIndex, String mode) {
return 0.0;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import java.util.List;
import java.util.Map;

import org.eqasim.core.simulation.mode_choice.epsilon.EpsilonProvider;
import org.matsim.api.core.v01.population.Person;
import org.matsim.api.core.v01.population.PlanElement;
import org.matsim.contribs.discrete_mode_choice.components.estimators.AbstractTripRouterEstimator;
Expand All @@ -13,14 +14,16 @@
import org.matsim.core.utils.timing.TimeInterpretation;
import org.matsim.facilities.ActivityFacilities;

public class ModalUtilityEstimator extends AbstractTripRouterEstimator {
public class EqasimUtilityEstimator extends AbstractTripRouterEstimator {
private final Map<String, UtilityEstimator> estimators;
private final EpsilonProvider epsilonProvider;

public ModalUtilityEstimator(TripRouter tripRouter, ActivityFacilities facilities,
public EqasimUtilityEstimator(TripRouter tripRouter, ActivityFacilities facilities,
Map<String, UtilityEstimator> estimators, TimeInterpretation timeInterpretation,
Collection<String> preroutedModes) {
Collection<String> preroutedModes, EpsilonProvider epsilonProvider) {
super(tripRouter, facilities, timeInterpretation, preroutedModes);
this.estimators = estimators;
this.epsilonProvider = epsilonProvider;
}

@Override
Expand All @@ -31,7 +34,9 @@ protected double estimateTrip(Person person, String mode, DiscreteModeChoiceTrip
if (estimator == null) {
throw new IllegalStateException(String.format("No estimator registered for mode '%s'", mode));
} else {
return estimator.estimateUtility(person, trip, elements);
double utility = estimator.estimateUtility(person, trip, elements);
utility += epsilonProvider.getEpsilon(person.getId(), trip.getIndex(), mode);
return utility;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
package org.eqasim.core.simulation.modes.feeder_drt.mode_choice;

import com.google.inject.Provider;
import com.google.inject.Provides;
import java.util.Map;
import java.util.stream.Collectors;

import org.eqasim.core.components.config.EqasimConfigGroup;
import org.eqasim.core.simulation.mode_choice.AbstractEqasimExtension;
import org.eqasim.core.simulation.mode_choice.epsilon.EpsilonAdapter;
import org.eqasim.core.simulation.mode_choice.utilities.UtilityEstimator;
import org.eqasim.core.simulation.modes.feeder_drt.mode_choice.constraints.FeederDrtConstraint;
import org.eqasim.core.simulation.modes.feeder_drt.config.MultiModeFeederDrtConfigGroup;
import org.eqasim.core.simulation.modes.feeder_drt.mode_choice.constraints.FeederDrtConstraint;
import org.eqasim.core.simulation.modes.feeder_drt.mode_choice.utilities.estimator.DefaultFeederDrtUtilityEstimator;

import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import com.google.inject.Provider;
import com.google.inject.Provides;

public class EqasimFeederDrtModeChoiceModule extends AbstractEqasimExtension {

Expand All @@ -28,14 +27,6 @@ protected void installEqasimExtension() {
public DefaultFeederDrtUtilityEstimator provideDefaultFeederDrtUtilityEstimator(EqasimConfigGroup eqasimConfigGroup, MultiModeFeederDrtConfigGroup multiModeFeederDrtConfigGroup, Map<String, Provider<UtilityEstimator>> utilityEstimatorProviders) {
Map<String, UtilityEstimator> ptEstimators = multiModeFeederDrtConfigGroup.getModalElements().stream().collect(Collectors.toMap(cfg -> cfg.mode, cfg -> utilityEstimatorProviders.get(eqasimConfigGroup.getEstimators().get(cfg.ptModeName)).get()));
Map<String, UtilityEstimator> drtEstimators = multiModeFeederDrtConfigGroup.getModalElements().stream().collect(Collectors.toMap(cfg -> cfg.mode, cfg -> utilityEstimatorProviders.get(eqasimConfigGroup.getEstimators().get(cfg.accessEgressModeName)).get()));
// When we use the Epsilon adapter, we do not want to sum the pseudo-random errors of each sub-mode but rather only use one pseudo-error specific to the current mode
for(Map<String, UtilityEstimator> map: List.of(ptEstimators, drtEstimators)) {
for(String mode: map.keySet()) {
if(map.get(mode) instanceof EpsilonAdapter epsilonAdapter) {
map.put(mode, epsilonAdapter.getDelegate());
}
}
}
return new DefaultFeederDrtUtilityEstimator(ptEstimators, drtEstimators);
}

Expand Down

0 comments on commit d4af27e

Please sign in to comment.