001/* *********************************************************************** * 002 * project: org.matsim.* 003 * RandomGroupLevelSelector.java 004 * * 005 * *********************************************************************** * 006 * * 007 * copyright : (C) 2013 by the members listed in the COPYING, * 008 * LICENSE and WARRANTY file. * 009 * email : info at matsim dot org * 010 * * 011 * *********************************************************************** * 012 * * 013 * This program is free software; you can redistribute it and/or modify * 014 * it under the terms of the GNU General Public License as published by * 015 * the Free Software Foundation; either version 2 of the License, or * 016 * (at your option) any later version. * 017 * See also COPYING, LICENSE and WARRANTY file * 018 * * 019 * *********************************************************************** */ 020package org.matsim.contrib.socnetsim.framework.replanning.selectors.highestweightselection; 021 022import java.util.*; 023 024import org.apache.log4j.Logger; 025import org.matsim.api.core.v01.Id; 026import org.matsim.api.core.v01.population.Person; 027import org.matsim.api.core.v01.population.Plan; 028 029import org.matsim.contrib.socnetsim.framework.population.JointPlan; 030import org.matsim.contrib.socnetsim.framework.population.JointPlans; 031import org.matsim.contrib.socnetsim.framework.replanning.grouping.GroupPlans; 032import org.matsim.contrib.socnetsim.framework.replanning.grouping.ReplanningGroup; 033import org.matsim.contrib.socnetsim.framework.replanning.selectors.GroupLevelPlanSelector; 034import org.matsim.contrib.socnetsim.framework.replanning.selectors.IncompatiblePlansIdentifier; 035import org.matsim.contrib.socnetsim.framework.replanning.selectors.IncompatiblePlansIdentifierFactory; 036 037/** 038 * @author thibautd 039 */ 040public class RandomGroupLevelSelector implements GroupLevelPlanSelector { 041 private static final Logger log = Logger.getLogger( RandomGroupLevelSelector.class ); 042 private final Random random; 043 private final IncompatiblePlansIdentifierFactory incompFactory; 044 045 public RandomGroupLevelSelector( 046 final Random random, 047 final IncompatiblePlansIdentifierFactory incompFactory) { 048 this.random = random; 049 this.incompFactory = incompFactory; 050 } 051 052 // ///////////////////////////////////////////////////////////////////////// 053 // interface and abstract method 054 // ///////////////////////////////////////////////////////////////////////// 055 @Override 056 public final GroupPlans selectPlans( 057 final JointPlans jointPlans, 058 final ReplanningGroup group) { 059 final IncompatiblePlansIdentifier incompatiblePlansIdentifier = 060 incompFactory.createIdentifier( jointPlans , group ); 061 final Map<Id, PersonRecord> personRecords = 062 getPersonRecords( 063 jointPlans, 064 group ); 065 final IncompatiblePlanRecords incompatibleRecords = 066 new IncompatiblePlanRecords( 067 incompatiblePlansIdentifier, 068 personRecords ); 069 070 final GroupPlans plans = searchForRandomCombination( 071 incompatibleRecords, 072 new ArrayList<>( 073 personRecords.values() ) ); 074 075 if ( plans == null ) { 076 throw new RuntimeException( "could not find combination for group "+group ); 077 } 078 079 assert plans.getAllIndividualPlans().size() == group.getPersons().size() : plans.getAllIndividualPlans()+" != "+group.getPersons(); 080 081 return plans; 082 } 083 084 private static Map<Id, PersonRecord> getPersonRecords( 085 final JointPlans jointPlans, 086 final ReplanningGroup group) { 087 final Map<Id, PersonRecord> map = new LinkedHashMap<Id, PersonRecord>(); 088 089 final Map<JointPlan, Collection<PlanRecord>> recordsPerJp = new HashMap<>(); 090 for (Person person : group.getPersons()) { 091 final List<PlanRecord> plans = new ArrayList<>(); 092 for (Plan plan : person.getPlans()) { 093 final JointPlan jp = jointPlans.getJointPlan( plan ); 094 095 final PlanRecord r = new PlanRecord( 096 plan, 097 jp, 098 0 ); 099 plans.add( r ); 100 if ( jp != null ) { 101 Collection<PlanRecord> rs = recordsPerJp.get( jp ); 102 if ( rs == null ) { 103 rs = new ArrayList<>(); 104 recordsPerJp.put( jp , rs ); 105 } 106 rs.add( r ); 107 } 108 } 109 final PersonRecord pr = new PersonRecord( person , plans ); 110 map.put( 111 person.getId(), 112 pr ); 113 for ( PlanRecord p : plans ) { 114 p.person = pr; 115 } 116 } 117 118 for (PersonRecord personRecord : map.values()) { 119 for ( PlanRecord pr : personRecord.plans ) { 120 if ( pr.jointPlan == null ) continue; 121 pr.linkedPlans.addAll( recordsPerJp.get( pr.jointPlan ) ); 122 pr.linkedPlans.remove( pr ); 123 } 124 } 125 126 return map; 127 } 128 129 private GroupPlans searchForRandomCombination( 130 final IncompatiblePlanRecords incompatibleRecords, 131 final List<PersonRecord> persons) { 132 final Queue<AnswerNode> plansStack = getInitialNodes( persons.get(0).plans ); 133 134 if ( log.isTraceEnabled() ) { 135 log.trace( "search for random combination for persons "+persons ); 136 } 137 138 while ( !plansStack.isEmpty() ) { 139 final AnswerNode currentPlan = plansStack.remove(); 140 141 if ( log.isTraceEnabled() ) { 142 log.trace( "examine "+currentPlan ); 143 } 144 145 if ( currentPlan.type == AnswerNodeType.feasibility ) { 146 currentPlan.resetFeasibilities(); 147 continue; 148 } 149 150 if ( currentPlan.record.isStillFeasible ) { 151 152 final List<PersonRecord> actuallyRemainingPersons = remainingPersons(persons, currentPlan); 153 154 if (!actuallyRemainingPersons.isEmpty()) { 155 final FeasibilityChanger feasibilityChanger = new FeasibilityChanger(); 156 SelectorUtils.tagIncompatiblePlansAsInfeasible( 157 currentPlan.record, 158 incompatibleRecords, 159 feasibilityChanger); 160 markPlansOfCotravelersAsInfeasible( currentPlan , feasibilityChanger ); 161 markJointPlansAsInfeasible( currentPlan.record , feasibilityChanger ); 162 163 final PersonRecord nextPerson = actuallyRemainingPersons.get(0); 164 plansStack.add( new AnswerNode( feasibilityChanger ) ); 165 for (PlanRecord r : shuffle(nextPerson.plans)) { 166 // TODO: only add one plan per planComposition-incompatibility "branch" 167 if (!r.isStillFeasible) continue; 168 169 final AnswerNode newNode = new AnswerNode( r, currentPlan ); 170 if ( log.isTraceEnabled() ) { 171 log.trace( "add to stack: "+newNode ); 172 } 173 assert !containsSamePersonTwice( newNode ) : "plans="+ plansStack +"\n answer="+ newNode; 174 plansStack.add( newNode ); 175 176 } 177 } 178 else { 179 final GroupPlans constructedPlan = new GroupPlans(); 180 for ( AnswerNode p : currentPlan ) { 181 SelectorUtils.add( constructedPlan, p.record ); 182 } 183 return constructedPlan; 184 } 185 } 186 } 187 188 return null; 189 } 190 191 private void markJointPlansAsInfeasible( 192 final PlanRecord currentRecord, 193 final FeasibilityChanger feasibilityChanger ) { 194 for ( PlanRecord otherRecord : currentRecord.person.plans ) { 195 if ( currentRecord == otherRecord ) continue; 196 for ( PlanRecord linked : otherRecord.linkedPlans ) { 197 feasibilityChanger.changeIfNecessary( linked ); 198 } 199 } 200 } 201 202 private void markPlansOfCotravelersAsInfeasible( 203 final AnswerNode currentPlan, 204 final FeasibilityChanger feasibilityChanger ) { 205 final Collection<PlanRecord> linkedPlans = currentPlan.record.linkedPlans; 206 for ( PlanRecord linkedPlan : linkedPlans ) { 207 for ( PlanRecord planOfCotraveler : linkedPlan.person.plans ) { 208 if ( linkedPlan == planOfCotraveler ) continue; 209 feasibilityChanger.changeIfNecessary( planOfCotraveler ); 210 markJointPlansAsInfeasible( planOfCotraveler , feasibilityChanger ); 211 } 212 } 213 } 214 215 private Queue<AnswerNode> getInitialNodes(List<PlanRecord> plans ) { 216 final Queue<AnswerNode> queue = Collections.asLifoQueue( new ArrayDeque<AnswerNode>() ); 217 for ( PlanRecord p : plans ) queue.add( new AnswerNode( p , null ) ); 218 return queue; 219 } 220 221 private boolean containsSamePersonTwice(AnswerNode answerStack) { 222 final Set<Id<Person>> ps = new HashSet<>(); 223 224 for ( AnswerNode p : answerStack ) { 225 if ( !ps.add( p.record.person.person.getId() ) ) return true; 226 for ( PlanRecord l : p.record.linkedPlans ) { 227 if ( !ps.add( l.person.person.getId() ) ) return true; 228 } 229 } 230 231 return false; 232 } 233 234 private List<PersonRecord> remainingPersons(List<PersonRecord> persons, AnswerNode plansStack) { 235 final List<PersonRecord> rem = new ArrayList<>( persons ); 236 for ( AnswerNode p : plansStack ) { 237 rem.remove( p.record.person ); 238 for ( PlanRecord l : p.record.linkedPlans ) rem.remove( l.person ); 239 } 240 return rem; 241 } 242 243 private <T> Collection<T> shuffle(List<T> plans) { 244 Collections.shuffle( plans , random ); 245 return plans; 246 } 247 248 private enum AnswerNodeType {record, feasibility} 249 250 private static class AnswerNode implements Iterable<AnswerNode> { 251 private final PlanRecord record; 252 private final AnswerNode parent; 253 private final FeasibilityChanger feasibilityChanger; 254 private final AnswerNodeType type; 255 256 private AnswerNode(PlanRecord record, AnswerNode parent) { 257 this.record = record; 258 this.parent = parent; 259 this.feasibilityChanger = null; 260 this.type = AnswerNodeType.record; 261 } 262 263 private AnswerNode(FeasibilityChanger feasibilityChanger) { 264 this.record = null; 265 this.parent = null; 266 this.feasibilityChanger = feasibilityChanger; 267 this.type = AnswerNodeType.feasibility; 268 } 269 270 @Override 271 public Iterator<AnswerNode> iterator() { 272 return new Iterator<AnswerNode>() { 273 @Override 274 public void remove() { 275 throw new UnsupportedOperationException(); 276 } 277 278 AnswerNode current = AnswerNode.this; 279 280 @Override 281 public boolean hasNext() { 282 return current != null; 283 } 284 285 @Override 286 public AnswerNode next() { 287 AnswerNode n = current; 288 current = current.parent; 289 return n; 290 } 291 }; 292 } 293 294 public void resetFeasibilities() { 295 if ( feasibilityChanger == null ) return; 296 feasibilityChanger.resetFeasibilities(); 297 } 298 299 @Override 300 public String toString() { 301 final StringBuilder s = new StringBuilder( "AnswerNode{ " ); 302 for ( AnswerNode n : this ) s.append( n.record.toString() ); 303 if ( feasibilityChanger != null ) s.append( "; feasibilityChanging" ); 304 s.append( " } " ); 305 return s.toString(); 306 } 307 } 308}