001/* *********************************************************************** * 002 * project: org.matsim.* * 003 * * 004 * *********************************************************************** * 005 * * 006 * copyright : (C) 2014 by the members listed in the COPYING, * 007 * LICENSE and WARRANTY file. * 008 * email : info at matsim dot org * 009 * * 010 * *********************************************************************** * 011 * * 012 * This program is free software; you can redistribute it and/or modify * 013 * it under the terms of the GNU General Public License as published by * 014 * the Free Software Foundation; either version 2 of the License, or * 015 * (at your option) any later version. * 016 * See also COPYING, LICENSE and WARRANTY file * 017 * * 018 * *********************************************************************** */ 019package playground.vsp.planselectors; 020 021import java.util.HashMap; 022import java.util.Iterator; 023import java.util.List; 024import java.util.Map; 025 026import org.matsim.api.core.v01.network.Network; 027import org.matsim.api.core.v01.population.Activity; 028import org.matsim.api.core.v01.population.Person; 029import org.matsim.api.core.v01.population.Plan; 030import org.matsim.core.replanning.selectors.AbstractPlanSelector; 031import org.matsim.core.replanning.selectors.WorstPlanForRemovalSelector; 032import org.matsim.core.router.TripStructureUtils; 033import org.matsim.core.router.TripStructureUtils.StageActivityHandling; 034import org.matsim.core.utils.misc.OptionalTime; 035import org.matsim.pt.PtConstants; 036 037/** 038 * This class aims to increase the diversity of plans in an agent's choice set. This is achieved by removing a plan once 039 * it is found to be <i>similar</i> to another plan of the choice set. If no plan is considered <i>similar</i> to another plan 040 * the standard MATSim behavior is preserved, i.e. as of May 2014 the worst scored plan will be removed from the choice set. 041 * As first tests indicate, this effectively prevents agents from over-adapting, i.e. arriving at a stop only seconds before 042 * the bus departs. 043 * <p> 044 * Details:<br> 045 * A plan is considered <i>similar</i> to another plan if all of the similarity checkers consider the plan <i>similar</i>. 046 * For example, two plans with the same activity end times are <i>similar</i> if they also use the same mode of transport. 047 * If one of the similarity checkers fails, the plans are considered <i>dissimilar</i>. Note that currently only activity 048 * end times are checked for. See {@link DiversityGeneratingPlansRemoverANIK#similarity(Plan, Plan, StageActivityTypes, double)} 049 * for information on adding further similarity checkers. From two plans considered as <i>similar</i> the older one is preserved 050 * and the newer one will be deleted. The comparison stops at this point and further plans are not checked for. Note that this 051 * class can only delete one plan at a time. Multiple iterations are required in case more than two plans would be considered 052 * as <i>similar</i>. Note that the similarity checks depend on the plans of the choice set and may thus yield different results 053 * once a plan is removed. Further note that the order in which plans are compared to each other depends on the List implementation 054 * in which the plans are stored. As of May 2014 this is an ArrayList ({@link Person#plans}). 055 * 056 * @author aneumann 057 * @author ikaddoura 058 */ 059public final class DiversityGeneratingPlansRemoverANIK extends AbstractPlanSelector { 060 061 private final double similarTimeInterval; 062 063 /** 064 * Private - use the {@link DiversityGeneratingPlansRemoverANIK.Builder} instead. 065 */ 066 private DiversityGeneratingPlansRemoverANIK(Network network, double similarTimeInterval) { 067 this.similarTimeInterval = similarTimeInterval; 068 } 069 070 public static final class Builder { 071 // Defining default values 072 private double similarTimeInterval = 5.0 * 60.; 073 074 public final void setSimilarTimeInterval(double setSimilarTimeInterval) { 075 this.similarTimeInterval = setSimilarTimeInterval; 076 } 077 078 public final DiversityGeneratingPlansRemoverANIK build(Network network) { 079 return new DiversityGeneratingPlansRemoverANIK(network, this.similarTimeInterval); 080 } 081 } 082 083 /** 084 * @return Map giving a weight for each plan. Higher weights have a higher probability of being selected. 085 */ 086 @Override 087 protected Map<Plan, Double> calcWeights(List<? extends Plan> plans) { 088 if ( plans.isEmpty() ) { 089 throw new RuntimeException("empty plans set; this will not work ...") ; 090 } 091 092 Map<Plan,Double> map = new HashMap<Plan,Double>() ; 093 // Initialize all plans with a weight of zero. 094 for (Plan plan : plans) { 095 map.put(plan, 0.); 096 } 097 098 // Compare each plan with each other. If two plans are similar. The newer one gets dropped. 099 for ( Plan plan1 : plans ) { 100 for ( Plan plan2 : plans ) { 101 102 if (plan1 == plan2){ 103 // same plan, those are definitely similar. So, ignore them. 104 } else { 105 // check two plans for similarity TODO Should be a kind of builder passed further down that configures all similarity checkers 106 if (similarity( plan1, plan2, this.similarTimeInterval)) { 107 // This one is similar. Tag it as to be removed and return. We only can remove one plan. So we remove the newer one. 108 map.put(plan2, 1.); 109 return map; 110 } else { 111 // Not similar. Proceed with the next plan. 112 } 113 } 114 } 115 } 116 117 // In case there is no similar plan, fall back to the standard behavior of MATSim (as of May, 2014). Remove worst plan from choice set. 118 Plan plan = new WorstPlanForRemovalSelector().selectPlan(plans.get(0).getPerson()); 119 map.put(plan, 1.); 120 121 return map ; 122 } 123 124 /** 125 * Compare two plans for similarity. A plan is considered similar to another plan, if all similarity checkers consider it being similar.<br> 126 * TODO The checkers should be implemented as individual classes being configured once with a builder passed to them. 127 * 128 * @param plan1 First plan to be compared. 129 * @param plan2 Second plan to be compared. 130 * @param stageActivities Type of activities that should be ignored, e.g. {@link PtConstants#TRANSIT_ACTIVITY_TYPE}. 131 * @param similarTimeInterval The interval in which two activities are considered as being the same. TODO Builder implemenation 132 * @return <code>true</code> if both plans are considered similar, otherwise <code>false</code>. 133 */ 134 private boolean similarity( Plan plan1, Plan plan2, double similarTimeInterval) { 135 136 // Check for the first dimension 137 boolean similarTimes = checkActivityTimes(plan1, plan2, similarTimeInterval); 138 139 // Further checks can be implemented the same way. 140 // boolean similarModes = checkTransportModes(plan1, plan2); 141 142 // 143 if (similarTimes /* && similarRoutes && similarModes */) { 144 return true; 145 } else { 146 return false; 147 } 148 } 149 150 /** 151 * Compare two plans for similarity. A plan is considered similar to another plan, if all activities are within the specified interval. 152 * 153 * @param plan1 First plan to be compared. 154 * @param plan2 Second plan to be compared. 155 * @param stageActivities Type of activities that should be ignored, e.g. {@link PtConstants#TRANSIT_ACTIVITY_TYPE}. 156 * @param similarTimeInterval The interval in which two activities are considered as being the same. 157 * @return <code>true</code> if both plans are considered similar, otherwise <code>false</code>. 158 */ 159 private boolean checkActivityTimes(Plan plan1, Plan plan2, double similarTimeInterval) { 160 161 List<Activity> activities1 = TripStructureUtils.getActivities(plan1, StageActivityHandling.ExcludeStageActivities) ; 162 List<Activity> activities2 = TripStructureUtils.getActivities(plan2, StageActivityHandling.ExcludeStageActivities) ; 163 164 Iterator<Activity> it1 = activities1.iterator() ; 165 Iterator<Activity> it2 = activities2.iterator() ; 166 167 for ( ; it1.hasNext() && it2.hasNext() ; ) { 168 OptionalTime endTime1 = it1.next().getEndTime() ; 169 OptionalTime endTime2 = it2.next().getEndTime() ; 170 171 if ( endTime1.isUndefined() && endTime2.isUndefined()) { 172 // both activities have no end time, no need to compute a similarity penalty 173 } else if (endTime1.isUndefined() ^ endTime2.isUndefined()) { 174 // One of the end times is undefined -> 175 // Those two are not similar. Thus, the whole plan is considered as being not similar. 176 return false; 177 } 178 else { 179 // both activities have an end time, comparing the end times 180 181 // Calculate the difference of both activities' end times. 182 double delta = Math.abs(endTime1.seconds() - endTime2.seconds()) ; 183 if (delta <= similarTimeInterval) { 184 // This one is similar. Proceed with the next activity. 185 } else { 186 // Those two are not similar. Thus, the whole plan is considered as being not similar. 187 return false; 188 } 189 } 190 } 191 // We never found two dissimilar activities. So, both plans are considered as being similar. 192 return true; 193 } 194 195}