diff --git a/src/main/java/org/matsim/prepare/choices/BestKPlanGenerator.java b/src/main/java/org/matsim/prepare/choices/BestKPlanGenerator.java new file mode 100644 index 00000000..1a21209c --- /dev/null +++ b/src/main/java/org/matsim/prepare/choices/BestKPlanGenerator.java @@ -0,0 +1,40 @@ +package org.matsim.prepare.choices; + +import org.jetbrains.annotations.Nullable; +import org.matsim.modechoice.CandidateGenerator; +import org.matsim.modechoice.PlanCandidate; +import org.matsim.modechoice.PlanModel; +import org.matsim.modechoice.search.TopKChoicesGenerator; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +/** + * Keeps selected plan as the first candidate and adds the rest with best options. + */ +public class BestKPlanGenerator implements CandidateGenerator { + private final int topK; + private final TopKChoicesGenerator generator; + + public BestKPlanGenerator(int topK, TopKChoicesGenerator generator) { + this.topK = topK; + this.generator = generator; + } + + @Override + public List generate(PlanModel planModel, @Nullable Set consideredModes, @Nullable boolean[] mask) { + + List chosen = new ArrayList<>(); + chosen.add(planModel.getCurrentModes()); + + // Chosen candidate from data + PlanCandidate existing = generator.generatePredefined(planModel, chosen).get(0); + + List result = new ArrayList<>(); + result.add(existing); + result.addAll(generator.generate(planModel, consideredModes, mask)); + + return result.stream().distinct().limit(topK).toList(); + } +} diff --git a/src/main/java/org/matsim/prepare/choices/ComputePlanChoices.java b/src/main/java/org/matsim/prepare/choices/ComputePlanChoices.java index 1f7dee4c..0d1a0ed4 100644 --- a/src/main/java/org/matsim/prepare/choices/ComputePlanChoices.java +++ b/src/main/java/org/matsim/prepare/choices/ComputePlanChoices.java @@ -26,8 +26,8 @@ import org.matsim.core.utils.timing.TimeInterpretation; import org.matsim.modechoice.*; import org.matsim.modechoice.constraints.RelaxedMassConservationConstraint; -import org.matsim.modechoice.estimators.DefaultActivityEstimator; import org.matsim.modechoice.estimators.DefaultLegScoreEstimator; +import org.matsim.modechoice.estimators.FixedCostsEstimator; import org.matsim.modechoice.search.TopKChoicesGenerator; import picocli.CommandLine; @@ -50,7 +50,6 @@ public class ComputePlanChoices implements MATSimAppCommand, PersonAlgorithm { * Rows for the result table. */ private final Queue> rows = new ConcurrentLinkedQueue<>(); - private final MainModeIdentifier mmi = new DefaultAnalysisMainModeIdentifier(); @CommandLine.Mixin private ScenarioOptions scenario; @CommandLine.Mixin @@ -67,6 +66,8 @@ public class ComputePlanChoices implements MATSimAppCommand, PersonAlgorithm { private Path output; private ThreadLocal thread; private ProgressBar pb; + private MainModeIdentifier mmi = new DefaultAnalysisMainModeIdentifier(); + public static void main(String[] args) { new ComputePlanChoices().execute(args); @@ -93,10 +94,10 @@ public Integer call() throws Exception { Controler controler = this.scenario.createControler(); controler.addOverridingModule(InformedModeChoiceModule.newBuilder() + .withFixedCosts(FixedCostsEstimator.DailyConstant.class, "car") .withLegEstimator(DefaultLegScoreEstimator.class, ModeOptions.ConsiderIfCarAvailable.class, "car") .withLegEstimator(DefaultLegScoreEstimator.class, ModeOptions.AlwaysAvailable.class, "bike", "walk", "pt", "ride") .withConstraint(RelaxedMassConservationConstraint.class) - .withActivityEstimator(DefaultActivityEstimator.class) .build()); InformedModeChoiceConfigGroup imc = ConfigUtils.addOrGetModule(config, InformedModeChoiceConfigGroup.class); @@ -110,26 +111,23 @@ public Integer call() throws Exception { PlanBuilder.addVehiclesToScenario(injector.getInstance(Scenario.class)); - Population population = PopulationUtils.createPopulation(config); - - PlanBuilder builder = new PlanBuilder(shp, new ShpOptions(facilities, null, null), population.getFactory()); - - builder.createPlans(input).forEach(population::addPerson); - thread = ThreadLocal.withInitial(() -> new Ctx( new PlanRouter(injector.getInstance(TripRouter.class), TimeInterpretation.create(PlansConfigGroup.ActivityDurationInterpretation.tryEndTimeThenDuration, PlansConfigGroup.TripDurationHandling.ignoreDelays)), - new DiversePlanCandidateGenerator(topK, injector.getInstance(TopKChoicesGenerator.class)), - new PseudoScorer(injector, population) - ) + new BestKPlanGenerator(topK, injector.getInstance(TopKChoicesGenerator.class))) ); + Population population = PopulationUtils.createPopulation(config); + + PlanBuilder builder = new PlanBuilder(shp, new ShpOptions(facilities, null, null), population.getFactory()); + + builder.createPlans(input).forEach(population::addPerson); pb = new ProgressBar("Computing plan choices", population.getPersons().size()); - ParallelPersonAlgorithmUtils.run(population, Runtime.getRuntime().availableProcessors() - 1, this); + ParallelPersonAlgorithmUtils.run(population, Runtime.getRuntime().availableProcessors(), this); pb.close(); @@ -149,8 +147,6 @@ public Integer call() throws Exception { header.add(String.format("plan_%d_%s_ride_hours", i, mode)); header.add(String.format("plan_%d_%s_n_switches", i, mode)); } - - header.add(String.format("plan_%d_act_util", i)); header.add(String.format("plan_%d_valid", i)); } @@ -191,28 +187,18 @@ public void run(Person person) { // TODO: apply method might also shift times to better fit the schedule candidate.applyTo(plan); ctx.router.run(plan); - - row.addAll(convert(plan, ctx.scorer)); + row.addAll(convert(plan)); // available choice row.add(1); i++; } for (int j = i; j < topK; j++) { - row.addAll(convert(null, ctx.scorer)); + row.addAll(convert(null)); // not available row.add(0); } - // Clean up routes, which use a lot of memory - plan.getPlanElements().forEach(el -> { - if (el instanceof Leg leg) { - leg.setRoute(null); - } - }); - - model.reset(); - rows.add(row); pb.step(); @@ -221,7 +207,7 @@ public void run(Person person) { /** * Create one csv entry row for a plan. */ - private List convert(@Nullable Plan plan, PseudoScorer scorer) { + private List convert(@Nullable Plan plan) { List row = new ArrayList<>(); if (plan == null) { @@ -229,14 +215,11 @@ private List convert(@Nullable Plan plan, PseudoScorer scorer) { row.addAll(List.of(0, 0, 0, 0, 0)); } - row.add(0); - return row; } Map stats = collect(plan); - for (String mode : modes) { ModeStats modeStats = stats.get(mode); row.add(modeStats.usage); @@ -246,11 +229,6 @@ private List convert(@Nullable Plan plan, PseudoScorer scorer) { row.add(modeStats.numSwitches); } - // pseudo scorer commented out currently -// Object2DoubleMap scores = scorer.score(plan); -// row.add(scores.getDouble("score")); - row.add(0); - return row; } @@ -294,6 +272,6 @@ private Map collect(Plan plan) { private record ModeStats(int usage, double travelTime, double travelDistance, double rideTime, long numSwitches) { } - private record Ctx(PlanRouter router, DiversePlanCandidateGenerator generator, PseudoScorer scorer) { + private record Ctx(PlanRouter router, CandidateGenerator generator) { } } diff --git a/src/main/java/org/matsim/prepare/choices/PlanBuilder.java b/src/main/java/org/matsim/prepare/choices/PlanBuilder.java index 5be82c17..7c32dced 100644 --- a/src/main/java/org/matsim/prepare/choices/PlanBuilder.java +++ b/src/main/java/org/matsim/prepare/choices/PlanBuilder.java @@ -17,9 +17,7 @@ import org.matsim.api.core.v01.population.Plan; import org.matsim.api.core.v01.population.PopulationFactory; import org.matsim.application.options.ShpOptions; -import org.matsim.application.prepare.population.SplitActivityTypesDuration; import org.matsim.core.population.PersonUtils; -import org.matsim.core.population.PopulationUtils; import org.matsim.core.utils.geometry.CoordUtils; import org.matsim.prepare.population.InitLocationChoice; import org.matsim.vehicles.Vehicle; @@ -54,8 +52,6 @@ public class PlanBuilder { */ private final Object2LongMap features = new Object2LongOpenHashMap<>(); - private final SplitActivityTypesDuration splitDuration = new SplitActivityTypesDuration(); - private final SplittableRandom rnd = new SplittableRandom(); private final PopulationFactory f; @@ -150,7 +146,6 @@ private Person createPerson(String id, int seq, List trips) { Person person = f.createPerson(Id.createPersonId(id + "_" + seq)); - PopulationUtils.putSubpopulation(person, "person"); PersonUtils.setCarAvail(person, trips.get(0).getInt("p_age") >= 18 ? "always" : "never"); VehicleUtils.insertVehicleIdsIntoPersonAttributes(person, Map.of("car", Id.createVehicleId("car"), @@ -163,19 +158,14 @@ private Person createPerson(String id, int seq, List trips) { if (trip == null) return null; - // source-destination purpose - String sd = trips.get(0).getString("sd_group"); - - Activity act = f.createActivityFromCoord(sd.startsWith("home") ? "home": "other", trip.first()); - int departure = trips.get(0).getInt("departure") * 60; - act.setEndTime(departure); + Activity act = f.createActivityFromCoord("act", trip.first()); + act.setEndTime(trips.get(0).getInt("departure") * 60); act.getAttributes().putAttribute("n", trips.get(0).getInt("n")); plan.addActivity(act); plan.addLeg(f.createLeg(trips.get(0).getString("main_mode"))); - act = f.createActivityFromCoord(trips.get(0).getString("purpose"), trip.second()); - act.setStartTime(departure + trips.get(0).getInt("duration") * 60); + act = f.createActivityFromCoord("act", trip.second()); plan.addActivity(act); for (int i = 1; i < trips.size(); i++) { @@ -186,14 +176,12 @@ private Person createPerson(String id, int seq, List trips) { if (dest == null) return null; - departure = row.getInt("departure") * 60; - act.setEndTime(departure); + act.setEndTime(row.getInt("departure") * 60); act.getAttributes().putAttribute("n", row.getInt("n")); plan.addLeg(f.createLeg(row.getString("main_mode"))); - act = f.createActivityFromCoord(row.getString("purpose"), dest); - act.setStartTime(departure + row.getInt("duration") * 60); + act = f.createActivityFromCoord("act", dest); plan.addActivity(act); } @@ -202,8 +190,6 @@ private Person createPerson(String id, int seq, List trips) { person.addPlan(plan); person.setSelectedPlan(plan); - splitDuration.run(person); - return person; } diff --git a/src/main/java/org/matsim/prepare/choices/RandomPlanGenerator.java b/src/main/java/org/matsim/prepare/choices/RandomPlanGenerator.java new file mode 100644 index 00000000..97e4ee90 --- /dev/null +++ b/src/main/java/org/matsim/prepare/choices/RandomPlanGenerator.java @@ -0,0 +1,53 @@ +package org.matsim.prepare.choices; + +import org.jetbrains.annotations.Nullable; +import org.matsim.modechoice.CandidateGenerator; +import org.matsim.modechoice.ModeEstimate; +import org.matsim.modechoice.PlanCandidate; +import org.matsim.modechoice.PlanModel; +import org.matsim.modechoice.search.TopKChoicesGenerator; + +import java.util.*; + +/** + * Generates random candidates. + */ +public class RandomPlanGenerator implements CandidateGenerator { + + private final int topK; + private final TopKChoicesGenerator gen; + private final SplittableRandom rnd = new SplittableRandom(0); + + public RandomPlanGenerator(int topK, TopKChoicesGenerator generator) { + this.topK = topK; + this.gen = generator; + } + + @Override + public List generate(PlanModel planModel, @Nullable Set consideredModes, @Nullable boolean[] mask) { + + List chosen = new ArrayList<>(); + chosen.add(planModel.getCurrentModes()); + + // Chosen candidate from data + PlanCandidate existing = gen.generatePredefined(planModel, chosen).get(0); + + // This changes the internal state to randomize the estimates + for (Map.Entry> entry : planModel.getEstimates().entrySet()) { + for (ModeEstimate est : entry.getValue()) { + double[] utils = est.getEstimates(); + if (utils != null) + for (int i = 0; i < utils.length; i++) { + utils[i] = -rnd.nextDouble(); + } + } + } + + List result = new ArrayList<>(); + result.add(existing); + result.addAll(gen.generate(planModel, consideredModes, mask)); + + return result.stream().distinct().limit(topK).toList(); + } + +}