From 18d1f5010d70b2df69cfa4f2dbc826edd05d815a Mon Sep 17 00:00:00 2001 From: rakow Date: Sun, 31 Mar 2024 08:29:46 +0200 Subject: [PATCH] fixed issues, removed todos --- .../org/matsim/modechoice/PlanCandidate.java | 78 ++++++++------- .../modechoice/ScheduledModeChoiceModule.java | 6 +- .../WorstNotSelctedPlanSelector.java | 98 +++++++++++++++++++ .../scheduled/ScheduledStrategyChooser.java | 3 - .../scheduled/solver/AgentSchedule.java | 8 +- .../solver/ModeSchedulingProblem.java | 8 +- .../solver/ModeSchedulingSolver.java | 78 +++++++++++---- .../scheduled/solver/ScoreCalculator.java | 36 +++---- .../solver/ShufflePlansSelector.java | 2 + 9 files changed, 237 insertions(+), 80 deletions(-) create mode 100644 contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/replanning/WorstNotSelctedPlanSelector.java diff --git a/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/PlanCandidate.java b/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/PlanCandidate.java index dbc4c29d04a..adc6b635d72 100644 --- a/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/PlanCandidate.java +++ b/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/PlanCandidate.java @@ -30,6 +30,37 @@ public PlanCandidate(String[] modes, double utility) { this.utility = utility; } + /** + * Return features vector with number of occurrences per mode. + */ + public static double[] occurrences(List modes, String type) { + + double[] ft = new double[modes.size()]; + + for (int i = 0; i < modes.size(); i++) { + int count = StringUtils.countMatches(type, modes.get(i)); + ft[i] = count; + } + + return ft; + } + + /** + * Creates array of selected modes from plan type. + */ + public static String[] createModeArray(String planType) { + String[] modes = planType.split("-"); + for (int i = 0; i < modes.length; i++) { + + if (modes[i].equals("null")) + modes[i] = null; + else + modes[i] = modes[i].intern(); + } + + return modes; + } + /** * Total estimated utility. */ @@ -39,6 +70,7 @@ public double getUtility() { /** * Get mode for trip i. Indexing starts at 0. + * * @see #size() */ public String getMode(int i) { @@ -71,25 +103,10 @@ public int size() { return modes.length; } - /** - * Return features vector with number of occurrences per mode. - */ - public static double[] occurrences(List modes, String type) { - - double[] ft = new double[modes.size()]; - - for (int i = 0; i < modes.size(); i++) { - int count = StringUtils.countMatches(type, modes.get(i)); - ft[i] = count; - } - - return ft; - } - /** * Applies the routing modes of this candidate to a plan. * - * @param plan plan to apply the modes to + * @param plan plan to apply the modes to * @param partial if true, only trips that have changed mode will be replaced */ public void applyTo(Plan plan, boolean partial) { @@ -119,15 +136,16 @@ public void applyTo(Plan plan, boolean partial) { continue; // don't update the trip if it has the same mode - if (currentType != null && currentType.length > k && currentType[k].equals(mode)) { + // k-1, because k was already incremented + if (currentType != null && currentType.length > k - 1 && currentType[k - 1].equals(mode)) { continue; } // Replaces all trip elements and inserts single leg final List fullTrip = - planElements.subList( - planElements.indexOf(trip.getOriginActivity()) + 1, - planElements.indexOf(trip.getDestinationActivity())); + planElements.subList( + planElements.indexOf(trip.getOriginActivity()) + 1, + planElements.indexOf(trip.getDestinationActivity())); fullTrip.clear(); Leg leg = PopulationUtils.createLeg(mode); @@ -171,22 +189,6 @@ public String getPlanType() { return b.toString().intern(); } - /** - * Creates array of selected modes from plan type. - */ - public static String[] createModeArray(String planType) { - String[] modes = planType.split("-"); - for (int i = 0; i < modes.length; i++) { - - if (modes[i].equals("null")) - modes[i] = null; - else - modes[i] = modes[i].intern(); - } - - return modes; - } - @Override public int compareTo(PlanCandidate o) { return Double.compare(o.utility, utility); @@ -209,7 +211,7 @@ public int hashCode() { @Override public String toString() { return "PlanCandidate{" + - "modes=" + Arrays.toString(modes) + - ", utility=" + utility + '}'; + "modes=" + Arrays.toString(modes) + + ", utility=" + utility + '}'; } } diff --git a/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/ScheduledModeChoiceModule.java b/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/ScheduledModeChoiceModule.java index 43fa7038d85..7fc006af4ec 100644 --- a/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/ScheduledModeChoiceModule.java +++ b/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/ScheduledModeChoiceModule.java @@ -10,6 +10,7 @@ import org.matsim.core.controler.AbstractModule; import org.matsim.core.replanning.choosers.StrategyChooser; import org.matsim.core.replanning.strategies.DefaultPlanStrategiesModule; +import org.matsim.modechoice.replanning.WorstNotSelctedPlanSelector; import org.matsim.modechoice.replanning.scheduled.AllBestPlansStrategyProvider; import org.matsim.modechoice.replanning.scheduled.ReRouteSelectedStrategyProvider; import org.matsim.modechoice.replanning.scheduled.ScheduledStrategyChooser; @@ -101,11 +102,10 @@ public void install() { int target = (int) Math.ceil(iters / getConfig().replanning().getFractionOfIterationsToDisableInnovation()); log.info("Adjusting number of iterations from {} to {}.", getConfig().controller().getLastIteration(), target); - getConfig().controller().setLastIteration(iters); + getConfig().controller().setLastIteration(target); } -// bindPlanSelectorForRemoval().to() -// getConfig().replanning().setPlanSelectorForRemoval(); + bindPlanSelectorForRemoval().to(WorstNotSelctedPlanSelector.class); } diff --git a/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/replanning/WorstNotSelctedPlanSelector.java b/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/replanning/WorstNotSelctedPlanSelector.java new file mode 100644 index 00000000000..e2e2a8e5284 --- /dev/null +++ b/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/replanning/WorstNotSelctedPlanSelector.java @@ -0,0 +1,98 @@ +/* *********************************************************************** * + * project: org.matsim.* + * WorstPlanSelector.java + * * + * *********************************************************************** * + * * + * copyright : (C) 2009 by the members listed in the COPYING, * + * LICENSE and WARRANTY file. * + * email : info at matsim dot org * + * * + * *********************************************************************** * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * See also COPYING, LICENSE and WARRANTY file * + * * + * *********************************************************************** */ + +package org.matsim.modechoice.replanning; + +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import org.matsim.api.core.v01.population.HasPlansAndId; +import org.matsim.api.core.v01.population.Person; +import org.matsim.api.core.v01.population.Plan; +import org.matsim.core.replanning.selectors.PlanSelector; + +/** + * See {@link org.matsim.core.replanning.selectors.WorstPlanForRemovalSelector}. + * This class is the same except the selected plan is not removed. + */ +public class WorstNotSelctedPlanSelector implements PlanSelector { + + private static final String UNDEFINED_TYPE = "undefined"; + + @Override + public Plan selectPlan(HasPlansAndId person) { + + // hashmap that returns "Integer" count for given plans type: + Object2IntMap typeCounts = new Object2IntOpenHashMap<>(); + + // count how many plans per type an agent has: + for (Plan plan : person.getPlans()) { + String type = plan.getType(); + if ( type==null ) { + type = UNDEFINED_TYPE ; + } + typeCounts.merge( type, 1, Integer::sum); + } + + Plan worst = null; + double worstScore = Double.POSITIVE_INFINITY; + for (Plan plan : person.getPlans()) { + + String type = plan.getType(); + if ( type==null ) { + type = UNDEFINED_TYPE; + } + if ( person.getSelectedPlan() != plan && typeCounts.getInt( type ) > 1) { + // (if we have more than one plan of the same type:) + + // if this plan has no score yet: + if (plan.getScore() == null || plan.getScore().isNaN() ) { + // say that the plan without score now is the "worst": + worst = plan; + + // make sure that this one remains the selected plan: + worstScore = Double.NEGATIVE_INFINITY; + + // otherwise do the usual logic to find the plan with the minimum score: + } else if ( plan.getScore() < worstScore) { + worst = plan; + worstScore = plan.getScore(); + } + } + // (otherwise we just keep "worst=null") + + } + + if (worst == null) { + // there is exactly one plan, or we have of each plan-type exactly one. + // select the one with worst score globally, or the first one with score=null + for (Plan plan : person.getPlans()) { + if (plan.getScore() == null || plan.getScore().isNaN() ) { + return plan; + } + if (plan != person.getSelectedPlan() && plan.getScore() < worstScore) { + worst = plan; + worstScore = plan.getScore(); + } + } + } + return worst; + } + +} diff --git a/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/replanning/scheduled/ScheduledStrategyChooser.java b/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/replanning/scheduled/ScheduledStrategyChooser.java index e4bcc721da3..5d2860688b1 100644 --- a/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/replanning/scheduled/ScheduledStrategyChooser.java +++ b/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/replanning/scheduled/ScheduledStrategyChooser.java @@ -78,9 +78,6 @@ public GenericPlanStrategy chooseStrategy(HasPlansAndId 4) - total = 0; - int iterType = isScheduledIteration(replanningContext, scheduleConfig); for (int i = 0; i < weights.size(); i++) { diff --git a/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/replanning/scheduled/solver/AgentSchedule.java b/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/replanning/scheduled/solver/AgentSchedule.java index e6a2dbba390..9fabebbd17a 100644 --- a/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/replanning/scheduled/solver/AgentSchedule.java +++ b/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/replanning/scheduled/solver/AgentSchedule.java @@ -27,6 +27,10 @@ public final class AgentSchedule { */ final String[] planCategories; + /** + * Aggregated weights for each plan category. + */ + final byte[] weights; /** * Number of trips in each plan. */ @@ -37,15 +41,17 @@ public final class AgentSchedule { */ int currentPlan = -1; - AgentSchedule(Id id, String[] planCategories, int length) { + AgentSchedule(Id id, String[] planCategories, byte[] weights, int length) { this.id = id; this.planCategories = planCategories; + this.weights = weights; this.length = length; } private AgentSchedule(AgentSchedule other) { this.id = other.id; this.planCategories = other.planCategories; + this.weights = other.weights; this.availablePlans.addAll(other.availablePlans); this.indices.addAll(other.indices); this.currentPlan = other.currentPlan; diff --git a/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/replanning/scheduled/solver/ModeSchedulingProblem.java b/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/replanning/scheduled/solver/ModeSchedulingProblem.java index af9b0b1238e..2dbefdea6cc 100644 --- a/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/replanning/scheduled/solver/ModeSchedulingProblem.java +++ b/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/replanning/scheduled/solver/ModeSchedulingProblem.java @@ -113,7 +113,13 @@ void printScore(int offset) { log.info("Targets: {} | Switches: {}", targets, switchTarget); for (int i = 0; i < windowSize; i++) { - log.info("Iteration: {} | Target: {} | Switches: {}", (offset + i), scorer.getObserved()[i], scorer.getSwitches()[i]); + + StringBuilder b = new StringBuilder(); + for (int k = 0; k < targets.size(); k++) { + b.append(scorer.getObserved()[i * targets.size() + k]).append(" "); + } + + log.info("Iteration: {} | Target: {}| Switches: {}", (offset + i), b.toString(), scorer.getSwitches()[i]); } } diff --git a/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/replanning/scheduled/solver/ModeSchedulingSolver.java b/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/replanning/scheduled/solver/ModeSchedulingSolver.java index 87dc95a280a..f4d1fb6ff8c 100644 --- a/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/replanning/scheduled/solver/ModeSchedulingSolver.java +++ b/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/replanning/scheduled/solver/ModeSchedulingSolver.java @@ -1,8 +1,10 @@ package org.matsim.modechoice.replanning.scheduled.solver; +import it.unimi.dsi.fastutil.ints.IntArrayList; import it.unimi.dsi.fastutil.ints.IntIntPair; +import it.unimi.dsi.fastutil.ints.IntList; +import it.unimi.dsi.fastutil.objects.Reference2ObjectLinkedOpenHashMap; import it.unimi.dsi.fastutil.objects.Reference2ObjectMap; -import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.matsim.api.core.v01.Id; @@ -49,6 +51,10 @@ public Map, List> solve(Map, List agents = new ArrayList<>(); + Reference2ObjectMap target = createTarget(plans); + + Random rnd = new Random(0); + for (Map.Entry, List> kv : plans.entrySet()) { // nothing to optimize for empty plans @@ -56,10 +62,10 @@ public Map, List> solve(Map, List, List> solve(Map, List Objects.equals(p, planCategories[0]))) continue; - AgentSchedule schedule = new AgentSchedule(kv.getKey(), planCategories, candidates.get(0).size()); + int length = candidates.get(0).size(); + byte[] weights = computePlanWeights(target, planCategories, candidates.size(), length); + + AgentSchedule schedule = new AgentSchedule(kv.getKey(), planCategories, weights, length); // All plans are available initially IntStream.range(0, topK).forEach(schedule.availablePlans::add); @@ -77,22 +86,21 @@ public Map, List> solve(Map, List target = createTarget(plans); int switchTarget = (int) (targetSwitchShare * agents.stream().mapToInt(a -> a.length).sum()); Map, List> result = new HashMap<>(); for (int k = 0; k < scheduleLength; k += WINDOW_SIZE) { + // Initialize agents + initialize(k, agents); + ModeSchedulingProblem problem = new ModeSchedulingProblem(WINDOW_SIZE, agents, target, switchTarget); ModeSchedulingProblem solution = solve(problem); solution.printScore(k); - // TODO: strange behaviour // underlying objects have been copied, need to be reassigned agents = solution.getAgents(); @@ -106,7 +114,6 @@ public Map, List> solve(Map, List createTarget(Map, Lis .mapToInt(p -> p.get(0).size()) .sum(); - Reference2ObjectMap target = new Reference2ObjectOpenHashMap<>(); + Reference2ObjectMap target = new Reference2ObjectLinkedOpenHashMap<>(); // TODO: fixed car share of 50%# // fixed deviation @@ -135,14 +142,28 @@ private Reference2ObjectMap createTarget(Map, Lis return target; } - private void init(AgentSchedule agent) { + /** + * Initialize agents with random assignment. + */ + private void initialize(int k, List agents) { + + // TODO: could choose plans such that error is minimized + + List copy = new ArrayList<>(agents); + + Random rnd = new Random(k); + Collections.shuffle(copy, rnd); + + for (AgentSchedule agent : copy) { + agent.indices.clear(); - // Add initial plans - agent.indices.clear(); - IntStream.range(0, topK) - .filter(agent.availablePlans::contains) - .limit(WINDOW_SIZE) - .forEach(agent.indices::add); + IntList avail = new IntArrayList(agent.availablePlans); + Collections.shuffle(avail, rnd); + + for (int i = 0; i < WINDOW_SIZE; i++) { + agent.indices.add(avail.getInt(i)); + } + } } @@ -163,6 +184,29 @@ private String[] categorizePlans(List candidates) { return categories; } + /** + * Aggregates categories against targets. + */ + @SuppressWarnings("StringEquality") + private byte[] computePlanWeights(Reference2ObjectMap target, String[] categories, int candidates, int trips) { + + byte[] aggr = new byte[target.size() * candidates]; + + for (int i = 0; i < candidates; i++) { + int k = 0; + for (String ref : target.keySet()) { + int idx = i * target.size() + k++; + for (int j = 0; j < trips; j++) { + + if (categories[i * trips + j] == ref) { + aggr[idx]++; + } + } + } + } + + return aggr; + } private ModeSchedulingProblem solve(ModeSchedulingProblem problem) { diff --git a/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/replanning/scheduled/solver/ScoreCalculator.java b/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/replanning/scheduled/solver/ScoreCalculator.java index d56dc0984d4..d22d1ef1b27 100644 --- a/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/replanning/scheduled/solver/ScoreCalculator.java +++ b/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/replanning/scheduled/solver/ScoreCalculator.java @@ -2,8 +2,6 @@ import it.unimi.dsi.fastutil.ints.IntIntPair; -import it.unimi.dsi.fastutil.objects.Reference2IntMap; -import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap; import org.optaplanner.core.api.score.buildin.hardsoftlong.HardSoftLongScore; import org.optaplanner.core.api.score.calculator.IncrementalScoreCalculator; @@ -15,19 +13,21 @@ public final class ScoreCalculator implements IncrementalScoreCalculator { private ModeSchedulingProblem problem; - private Reference2IntMap[] observed; + + /** + * Targets per window. + */ + private int[] observed; private int[] switches; + private int targets; @Override public void resetWorkingSolution(ModeSchedulingProblem problem) { - observed = new Reference2IntMap[problem.getWindowSize()]; + this.targets = problem.getTargets().size(); + this.observed = new int[problem.getWindowSize() * targets]; this.problem = problem; - for (int i = 0; i < problem.getWindowSize(); i++) { - observed[i] = new Reference2IntOpenHashMap<>(); - } - - switches = new int[problem.getWindowSize()]; + this.switches = new int[problem.getWindowSize()]; calcScoreInternal(); } @@ -48,13 +48,14 @@ private void updateAgentPlan(AgentSchedule agent, int diff) { for (int i = 0; i < agent.indices.size(); i++) { int k = agent.indices.getInt(i); + for (int j = 0; j < targets; j++) { + observed[i * targets + j] += diff * agent.weights[k * targets + j]; + } + // iterate all trips in the plan for (int j = 0; j < agent.length; j++) { - String type = agent.planCategories[k * agent.length + j]; - if (type != null) - observed[i].merge(type, diff, Integer::sum); - if (prevK >= 0) { + String type = agent.planCategories[k * agent.length + j]; String prevType = agent.planCategories[prevK * agent.length + j]; // All String are internal if (prevType != type) @@ -109,10 +110,11 @@ public HardSoftLongScore calculateScore() { for (int i = 0; i < problem.getWindowSize(); i++) { long score = 0; + int k = 0; for (Map.Entry kv : problem.getTargets().entrySet()) { IntIntPair bounds = kv.getValue(); - int obs = observed[i].getInt(kv.getKey()); + int obs = observed[i * targets + (k++)]; // check against bounds if (obs > bounds.rightInt()) @@ -123,7 +125,7 @@ else if (obs < bounds.leftInt()) // Difference between score levels is added to penalize uneven distributed variations if (prevHard != -1) { - hardScore += Math.abs(score - prevHard) / 3; + hardScore += Math.abs(score - prevHard) / 2; } // there is no current plan in very first iteration @@ -131,7 +133,7 @@ else if (obs < bounds.leftInt()) if (!problem.isFirstIteration() || i != 0) { soft = Math.abs(switches[i] - problem.getSwitchTarget()); if (prevSoft != -1) { - softScore += Math.abs(prevSoft - soft) / 3; + softScore += Math.abs(prevSoft - soft) / 2; } prevSoft = soft; @@ -146,7 +148,7 @@ else if (obs < bounds.leftInt()) return HardSoftLongScore.of(-hardScore, -softScore); } - public Reference2IntMap[] getObserved() { + public int[] getObserved() { return observed; } diff --git a/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/replanning/scheduled/solver/ShufflePlansSelector.java b/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/replanning/scheduled/solver/ShufflePlansSelector.java index 57daf97d622..716bac7063f 100644 --- a/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/replanning/scheduled/solver/ShufflePlansSelector.java +++ b/contribs/informed-mode-choice/src/main/java/org/matsim/modechoice/replanning/scheduled/solver/ShufflePlansSelector.java @@ -56,6 +56,8 @@ public AssignPlanMove next() { AgentSchedule agent = agents.get(random.nextInt(agents.size())); + // TODO: chose plan in the right direction (using score director) + int idx = random.nextInt(ModeSchedulingSolver.WINDOW_SIZE); int k = agent.availablePlans.getInt(random.nextInt(agent.availablePlans.size()));