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}