Skip to content

Commit

Permalink
feat: add configurable policies (#254)
Browse files Browse the repository at this point in the history
* feat: add configurable policies

* add person filter

* add option to provide link list for LTZ

* bugfix for city tax

* switch to factor-based penalty for LTZ

* fix for link list path

* simplify factor penalty

* update logging

* fix bug for link list

* fix imports

* fix
  • Loading branch information
sebhoerl authored Nov 9, 2024
1 parent 8dc3328 commit b338bf8
Show file tree
Hide file tree
Showing 31 changed files with 1,045 additions and 0 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ included in the (note yet determined) next version number.

**Development version**

- add configurable policies for IDF
- Introduce `travelTimeRecordingInterval` config option that decouples travel time writing from general analysis
- Add eqasim_activities.csv for analysis
- The cutters now take a GeoPackage file as an alterative to a ShapeFile
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
package org.eqasim.ile_de_france;

import org.eqasim.core.simulation.EqasimConfigurator;
import org.eqasim.ile_de_france.policies.PoliciesConfigGroup;

public class IDFConfigurator extends EqasimConfigurator {
public IDFConfigurator() {
super();

registerConfigGroup(new PoliciesConfigGroup(), true);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import org.eqasim.core.simulation.analysis.EqasimAnalysisModule;
import org.eqasim.core.simulation.mode_choice.EqasimModeChoiceModule;
import org.eqasim.ile_de_france.mode_choice.IDFModeChoiceModule;
import org.eqasim.ile_de_france.policies.PolicyExtension;
import org.matsim.api.core.v01.Scenario;
import org.matsim.core.config.CommandLine;
import org.matsim.core.config.CommandLine.ConfigurationException;
Expand All @@ -25,6 +26,9 @@ static public void main(String[] args) throws ConfigurationException {
cmd.applyConfiguration(config);
VehiclesValidator.validate(config);

PolicyExtension policies = new PolicyExtension();
policies.adaptConfiguration(config);

Scenario scenario = ScenarioUtils.createScenario(config);
configurator.configureScenario(scenario);
ScenarioUtils.loadScenario(scenario);
Expand All @@ -35,6 +39,7 @@ static public void main(String[] args) throws ConfigurationException {
controller.addOverridingModule(new EqasimAnalysisModule());
controller.addOverridingModule(new EqasimModeChoiceModule());
controller.addOverridingModule(new IDFModeChoiceModule(cmd));
controller.addOverridingModule(policies);
controller.run();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package org.eqasim.ile_de_france.policies;

import org.eqasim.ile_de_france.policies.mode_choice.UtilityPenalty;
import org.eqasim.ile_de_france.policies.routing.RoutingPenalty;

public class DefaultPolicy implements Policy {
private final RoutingPenalty routingPenalty;
private final UtilityPenalty utilityPenalty;

public DefaultPolicy(RoutingPenalty routingPenalty, UtilityPenalty utilityPenalty) {
this.routingPenalty = routingPenalty;
this.utilityPenalty = utilityPenalty;
}

@Override
public RoutingPenalty getRoutingPenalty() {
return routingPenalty;
}

@Override
public UtilityPenalty getUtilityPenalty() {
return utilityPenalty;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package org.eqasim.ile_de_france.policies;

import org.eqasim.ile_de_france.policies.city_tax.CityTaxConfigGroup;
import org.eqasim.ile_de_france.policies.city_tax.CityTaxPolicyFactory;
import org.eqasim.ile_de_france.policies.limited_traffic_zone.LimitedTrafficZoneConfigGroup;
import org.eqasim.ile_de_france.policies.limited_traffic_zone.LimitedTrafficZonePolicyFactory;
import org.eqasim.ile_de_france.policies.transit_discount.TransitDiscountConfigGroup;
import org.eqasim.ile_de_france.policies.transit_discount.TransitDiscountPolicyFactory;
import org.matsim.core.config.Config;
import org.matsim.core.config.ConfigGroup;
import org.matsim.core.config.ReflectiveConfigGroup;

public class PoliciesConfigGroup extends ReflectiveConfigGroup {
static public final String CONFIG_NAME = "eqasim:policies";

public PoliciesConfigGroup() {
super(CONFIG_NAME);
}

@Override
public ConfigGroup createParameterSet(String type) {
switch (type) {
case CityTaxPolicyFactory.POLICY_NAME:
return new CityTaxConfigGroup();
case LimitedTrafficZonePolicyFactory.POLICY_NAME:
return new LimitedTrafficZoneConfigGroup();
case TransitDiscountPolicyFactory.POLICY_NAME:
return new TransitDiscountConfigGroup();
default:
throw new IllegalStateException();
}
}

static public PoliciesConfigGroup get(Config config) {
return (PoliciesConfigGroup) config.getModules().get(CONFIG_NAME);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package org.eqasim.ile_de_france.policies;

import org.eqasim.ile_de_france.policies.mode_choice.UtilityPenalty;
import org.eqasim.ile_de_france.policies.routing.RoutingPenalty;

public interface Policy {
RoutingPenalty getRoutingPenalty();

UtilityPenalty getUtilityPenalty();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package org.eqasim.ile_de_france.policies;

import org.matsim.core.config.ReflectiveConfigGroup;

public abstract class PolicyConfigGroup extends ReflectiveConfigGroup {
protected PolicyConfigGroup(String name) {
super(name);
}

@Parameter
public String policyName;

@Parameter
public boolean active = true;

@Parameter
public String personFilter;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
package org.eqasim.ile_de_france.policies;

import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

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.eqasim.ile_de_france.policies.city_tax.CityTaxPolicyExtension;
import org.eqasim.ile_de_france.policies.city_tax.CityTaxPolicyFactory;
import org.eqasim.ile_de_france.policies.limited_traffic_zone.LimitedTrafficZonePolicyExtension;
import org.eqasim.ile_de_france.policies.limited_traffic_zone.LimitedTrafficZonePolicyFactory;
import org.eqasim.ile_de_france.policies.mode_choice.PolicyUtilityEstimator;
import org.eqasim.ile_de_france.policies.mode_choice.SumUtilityPenalty;
import org.eqasim.ile_de_france.policies.mode_choice.UtilityPenalty;
import org.eqasim.ile_de_france.policies.routing.PolicyTravelDisutilityFactory;
import org.eqasim.ile_de_france.policies.routing.RoutingPenalty;
import org.eqasim.ile_de_france.policies.routing.SumRoutingPenalty;
import org.eqasim.ile_de_france.policies.transit_discount.TransitDiscountPolicyExtension;
import org.eqasim.ile_de_france.policies.transit_discount.TransitDiscountPolicyFactory;
import org.matsim.api.core.v01.TransportMode;
import org.matsim.api.core.v01.population.Population;
import org.matsim.core.config.Config;
import org.matsim.core.router.costcalculators.OnlyTimeDependentTravelDisutilityFactory;

import com.google.common.base.Verify;
import com.google.inject.Key;
import com.google.inject.Provider;
import com.google.inject.Provides;
import com.google.inject.Singleton;
import com.google.inject.multibindings.MapBinder;
import com.google.inject.name.Named;
import com.google.inject.name.Names;

public class PolicyExtension extends AbstractEqasimExtension {
private final static String ESTIMATOR_PREFIX = "policy:";

private String delegateCarEstimator;
private String delegateTransitEstimator;

public void adaptConfiguration(Config config) {
EqasimConfigGroup eqasimConfig = EqasimConfigGroup.get(config);

delegateCarEstimator = eqasimConfig.getEstimators().get(TransportMode.car);
delegateTransitEstimator = eqasimConfig.getEstimators().get(TransportMode.pt);

delegateCarEstimator = delegateCarEstimator.replace(ESTIMATOR_PREFIX, "");
delegateTransitEstimator = delegateTransitEstimator.replace(ESTIMATOR_PREFIX, "");

eqasimConfig.setEstimator(TransportMode.car, ESTIMATOR_PREFIX + delegateCarEstimator);
eqasimConfig.setEstimator(TransportMode.pt, ESTIMATOR_PREFIX + delegateTransitEstimator);
}

@Override
protected void installEqasimExtension() {
Verify.verifyNotNull(delegateCarEstimator, "Need to run PolicyExtension.adaptConfiguration first");
Verify.verifyNotNull(delegateTransitEstimator, "Need to run PolicyExtension.adaptConfiguration first");

// set up travel disutility for routing
addTravelDisutilityFactoryBinding(TransportMode.car).to(PolicyTravelDisutilityFactory.class);
addTravelDisutilityFactoryBinding("car_passenger").to(OnlyTimeDependentTravelDisutilityFactory.class);

install(new CityTaxPolicyExtension());
install(new LimitedTrafficZonePolicyExtension());
install(new TransitDiscountPolicyExtension());

var policyBinder = MapBinder.newMapBinder(binder(), String.class, PolicyFactory.class);
policyBinder.addBinding(CityTaxPolicyFactory.POLICY_NAME).to(CityTaxPolicyFactory.class);
policyBinder.addBinding(LimitedTrafficZonePolicyFactory.POLICY_NAME).to(LimitedTrafficZonePolicyFactory.class);
policyBinder.addBinding(TransitDiscountPolicyFactory.POLICY_NAME).to(TransitDiscountPolicyFactory.class);

bindUtilityEstimator(ESTIMATOR_PREFIX + delegateCarEstimator)
.to(Key.get(PolicyUtilityEstimator.class, Names.named(TransportMode.car)));

bindUtilityEstimator(ESTIMATOR_PREFIX + delegateTransitEstimator)
.to(Key.get(PolicyUtilityEstimator.class, Names.named(TransportMode.pt)));
}

@Provides
@Singleton
Map<String, Policy> providePolicies(Map<String, PolicyFactory> factories, Population population) {
PoliciesConfigGroup policyConfig = PoliciesConfigGroup.get(getConfig());
Map<String, Policy> policies = new HashMap<>();

Set<String> names = new HashSet<>();

if (policyConfig != null) {
for (var collection : policyConfig.getParameterSets().values()) {
for (var raw : collection) {
PolicyConfigGroup policy = (PolicyConfigGroup) raw;

if (policy.active) {
Verify.verify(policy.policyName != null && policy.policyName.length() > 0,
"Policy names must be set");

if (!names.add(policy.policyName)) {
throw new IllegalStateException("Duplicate policy name: " + policy.policyName);
}

PolicyPersonFilter filter = PolicyPersonFilter.create(population, policy);

policies.put(policy.policyName,
factories.get(policy.getName()).createPolicy(policy.policyName, filter));
}
}
}
}

return policies;
}

@Provides
@Singleton
PolicyTravelDisutilityFactory providePolicyTravelDisutilityFactory(RoutingPenalty linkPenalty) {
return new PolicyTravelDisutilityFactory(linkPenalty);
}

@Provides
@Named(TransportMode.car)
PolicyUtilityEstimator providePolicyUtilityEstimatorForCar(Map<String, Provider<UtilityEstimator>> providers,
UtilityPenalty penalty) {
UtilityEstimator delegate = providers.get(delegateCarEstimator).get();
return new PolicyUtilityEstimator(delegate, penalty, TransportMode.car);
}

@Provides
@Named(TransportMode.pt)
PolicyUtilityEstimator providePolicyUtilityEstimatorForTransit(Map<String, Provider<UtilityEstimator>> providers,
UtilityPenalty penalty) {
UtilityEstimator delegate = providers.get(delegateTransitEstimator).get();
return new PolicyUtilityEstimator(delegate, penalty, TransportMode.pt);
}

@Provides
UtilityPenalty provideUtilityPenalty(Map<String, Policy> policies) {
List<UtilityPenalty> penalties = new LinkedList<>();

for (Policy policy : policies.values()) {
UtilityPenalty penalty = policy.getUtilityPenalty();

if (penalty != null) {
penalties.add(penalty);
}
}

return new SumUtilityPenalty(penalties);
}

@Provides
RoutingPenalty provideRoutingPenalty(Map<String, Policy> policies) {
List<RoutingPenalty> penalties = new LinkedList<>();

for (Policy policy : policies.values()) {
RoutingPenalty penalty = policy.getRoutingPenalty();

if (penalty != null) {
penalties.add(penalty);
}
}

return new SumRoutingPenalty(penalties);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package org.eqasim.ile_de_france.policies;

public interface PolicyFactory {
Policy createPolicy(String name, PolicyPersonFilter personFilter);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package org.eqasim.ile_de_france.policies;

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

public class PolicyPersonFilter {
private final IdSet<Person> selection;

PolicyPersonFilter(IdSet<Person> selection) {
this.selection = selection;
}

public boolean applies(Id<Person> personId) {
return selection == null ? true : selection.contains(personId);
}

static public PolicyPersonFilter create(Population population, PolicyConfigGroup policy) {
if (policy.personFilter != null && policy.personFilter.length() > 0) {
IdSet<Person> selection = new IdSet<>(Person.class);

for (Person person : population.getPersons().values()) {
Boolean indicator = (Boolean) person.getAttributes().getAttribute(policy.personFilter);

if (indicator != null && indicator) {
selection.add(person.getId());
}
}

return new PolicyPersonFilter(selection);
} else {
return new PolicyPersonFilter(null);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package org.eqasim.ile_de_france.policies.city_tax;

import org.eqasim.ile_de_france.policies.PolicyConfigGroup;
import org.matsim.core.config.ReflectiveConfigGroup.Parameter;

public class CityTaxConfigGroup extends PolicyConfigGroup {
public CityTaxConfigGroup() {
super(CityTaxPolicyFactory.POLICY_NAME);
}

@Parameter
public double tax_EUR = 0.0;

@Parameter
public String perimetersPath;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package org.eqasim.ile_de_france.policies.city_tax;

import org.eqasim.core.simulation.mode_choice.AbstractEqasimExtension;
import org.eqasim.ile_de_france.mode_choice.parameters.IDFModeParameters;
import org.matsim.api.core.v01.network.Network;

import com.google.inject.Provides;
import com.google.inject.Singleton;

public class CityTaxPolicyExtension extends AbstractEqasimExtension {
@Override
protected void installEqasimExtension() {
}

@Provides
@Singleton
CityTaxPolicyFactory provideCityTaxPolicyFactory(Network network, IDFModeParameters modeParameters) {
return new CityTaxPolicyFactory(getConfig(), network, modeParameters);
}
}
Loading

0 comments on commit b338bf8

Please sign in to comment.