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

feat: add configurable policies #254

Merged
merged 14 commits into from
Nov 9, 2024
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
Loading