MATSIM
PreplanningEngine.java
Go to the documentation of this file.
1 
2 /* *********************************************************************** *
3  * project: org.matsim.*
4  * PreplanningEngine.java
5  * *
6  * *********************************************************************** *
7  * *
8  * copyright : (C) 2019 by the members listed in the COPYING, *
9  * LICENSE and WARRANTY file. *
10  * email : info at matsim dot org *
11  * *
12  * *********************************************************************** *
13  * *
14  * This program is free software; you can redistribute it and/or modify *
15  * it under the terms of the GNU General Public License as published by *
16  * the Free Software Foundation; either version 2 of the License, or *
17  * (at your option) any later version. *
18  * See also COPYING, LICENSE and WARRANTY file *
19  * *
20  * *********************************************************************** */
21 
22 package org.matsim.core.mobsim.qsim;
23 
24 import static java.util.Comparator.comparing;
26 
27 import java.util.ArrayList;
28 import java.util.Collections;
29 import java.util.LinkedHashMap;
30 import java.util.List;
31 import java.util.Map;
32 import java.util.Optional;
33 import java.util.TreeMap;
34 
35 import org.apache.logging.log4j.LogManager;
36 import org.apache.logging.log4j.Logger;
37 import org.matsim.api.core.v01.Coord;
39 import org.matsim.api.core.v01.Scenario;
49 import org.matsim.core.gbl.Gbl;
71 
72 import com.google.inject.Inject;
73 
74 public final class PreplanningEngine implements MobsimEngine {
75  // Could implement this as a generalized version of the bdi-abm implementation: can send notifications to agent, and agent can react. Similar
76  // to the drive-to action. Notifications and corresponding handlers could then be registered.
77 
78  // On the other hand, it is easy to add an engine such as this one; how much does it help to have another layer of infrastructure?
79 
80  // Am currently leaning towards the second argument. kai, mar'19
81 
82  private static final Logger log = LogManager.getLogger(PreplanningEngine.class);
83 
85 
86  private final Map<String, TripInfo.Provider> tripInfoProviders = new LinkedHashMap<>();
87  // yyyy Is "linked" enough to be deterministic? kai, mar'19
88 
89  private final Population population;
90  private final Network network;
91  private final Scenario scenario;
92 
93  // (we are in the mobsim, so we don't need to play around with IDs)
94  private final Map<MobsimAgent, Optional<TripInfo>> tripInfoUpdatesMap = new TreeMap<>(comparing(Identifiable::getId ));
95  // yyyy not sure about possible race conditions here! kai, feb'19
96  // yyyyyy can't have non-sorted maps here because we will get non-deterministic results. kai, mar'19
97  // (haven't these two points be fixed by using the "comparing"? kai, apr'24)
98 
99  private final Map<MobsimAgent, TripInfo.Request> tripInfoRequestMap = new TreeMap<>(comparing(Identifiable::getId ));
100  // yyyyyy can't have non-sorted maps here because we will get non-deterministic results. kai, mar'19
101  // (hasn't this point be fixed by using the "comparing"? kai, apr'24)
102 
104 
105  private final TripRouter tripRouter;
108 
109  @Inject PreplanningEngine(TripRouter tripRouter, Scenario scenario, TimeInterpretation timeInterpretation) {
110  this.tripRouter = tripRouter;
111  this.population = scenario.getPopulation();
112  this.facilities = scenario.getActivityFacilities();
113  this.network = scenario.getNetwork();
114  this.scenario = scenario;
115  this.timeInterpretation = timeInterpretation;
116  }
117 
118  @Override public void onPrepareSim() {
119  log.warn( "running onPrepareSim");
120  for (DepartureHandler departureHandler : internalInterface.getDepartureHandlers()) {
121  if (departureHandler instanceof TripInfo.Provider) {
122  String mode = ((TripInfo.Provider)departureHandler).getMode();
123  log.warn("registering TripInfo.Provider for mode=" + mode);
124  this.tripInfoProviders.put(mode, (TripInfo.Provider)departureHandler);
125  }
126  }
127  }
128 
129  @Override public void afterSim() { }
130 
131  @Override public void setInternalInterface(InternalInterface internalInterface) {
132  this.editPlans = new EditPlans(internalInterface.getMobsim(), new EditTrips( tripRouter, scenario, internalInterface, timeInterpretation ) );
133  this.internalInterface = internalInterface;
134  }
135 
136  @Override public void doSimStep(double time) {
137  //first process requests and then infos --> trips without booking required can be processed in 1 time step
138  //booking confirmation always comes later (e.g. next time step)
139 
140  // (I have inlined the below methods since I find this for the time being easier to read. Can be extracted again at some later point in time .
141  // kai, jan'20)
142 
143  // the following goes through all requests (generated by the agent wakups), send them to the trip info providers, and decide based on
144  // the returned information:
145  for (Map.Entry<MobsimAgent, TripInfo.Request> entry : tripInfoRequestMap.entrySet()) {
146  final MobsimAgent mobsimAgent = entry.getKey();
147  final TripInfo.Request request = entry.getValue();
148 
149  List<TripInfo> allTripInfos = new ArrayList<>();
150  for (TripInfo.Provider provider : tripInfoProviders.values()) {
151  allTripInfos.addAll( provider.getTripInfos( request ) );
152  }
153 
154  // TODO add info for mode that is in agent plan, if not returned by trip info provider
155  // not sure if that is needed. kai, jan'20
156 
157  // the following method decides, and
158  // * puts it then into the tripInfoUpdatesMap (processed below); or
159  // * if the agent needs to wait for confirmation, the confirming method (currently only in PassengerEngineWithPrebooking) puts it into tripInfoUpdatesMap.
160  decide( mobsimAgent, allTripInfos );
161  }
162  tripInfoRequestMap.clear();
163 
164  // process the tripInfoUpdatesMap (see above):
165  for (Map.Entry<MobsimAgent, Optional<TripInfo>> entry : tripInfoUpdatesMap.entrySet()) {
166  MobsimAgent agent = entry.getKey();
167 
168  Optional<TripInfo> tripInfo = entry.getValue();
169 
170  if (tripInfo.isPresent()) {
171  TripInfo actualTripInfo = tripInfo.get();
172  updateAgentPlan(agent, actualTripInfo);
173  } else {
174  // this can e.g. happen if the booking failed. At first glance, I am not too happy about this. I think that if a
175  // provider is not able to process the trip, it should not return a TripInfo offer. At second glance, it may happen
176  // that no provider returns a TripInfo offer. Now evidently, this could be avoided by always allowing for a fallback
177  // mode, e.g. walk and/or pt. On the other hand, since the functionality is already here, we can as well try to keep
178  // it. However, need to make sure that it eventually gets resolved. Also see the questions below.
179 
180  // in principle, one could always give the pt TripOption. However, this would be fairly expensive to compute.
181 
182  TripInfo.Request request = null;
183  //TODO get it from where ??? from TripInfo???
184  //TODO agent should adapt trip info request given that the previous one got rejected??
185  //TODO or it should skip the rejected option during "accept()"
186  notifyTripInfoNeeded(agent, request);//start over again in the next time step
187  }
188  }
189  tripInfoUpdatesMap.clear();
190 
191  // (I have inlined the above methods since I find this for the time being easier to read. Can be extracted again at some later point in time . kai, jan'20)
192  }
193 
194  public synchronized void notifyChangedTripInformation( MobsimAgent agent, Optional<TripInfo> tripInfoUpdate ) {
195  // yyyy My IDE complains about "Optional" in method signatures. kai, jan'20
196  // It looks like it needs to be possible to return an "empty" tripInfoUpdate in order to notify that the "decided" (= selected) trip
197  // option did not work out. Or, alternatively, none of the providers returned an answer at all.
198 
199  tripInfoUpdatesMap.put(agent, tripInfoUpdate);
200  }
201 
202  private synchronized void notifyTripInfoNeeded( MobsimAgent agent, TripInfo.Request tripInfoRequest ) {
203  tripInfoRequestMap.put(agent, tripInfoRequest);
204  }
205 
206  private void decide(MobsimAgent agent, List<TripInfo> allTripInfos) {
207 
208  // for otfvis:
209  this.population.getPersons().get(agent.getId()).getAttributes().putAttribute(AgentSnapshotInfo.marker, true);
210 
211  if (allTripInfos.isEmpty()) {
212  return;
213  }
214 
215  // to get started, we assume that we are only getting one drt option back.
216  // yyyy TODO: evidently, this needs to be changed to mode choice between available modes. kai, apr'24
217  TripInfo tripInfo = allTripInfos.iterator().next();
218 
219  if (tripInfo instanceof TripInfoWithRequiredBooking) {
220 // tripInfoProviders.get(tripInfo.getMode())
221 // .bookTrip((MobsimPassengerAgent)agent, (TripInfoWithRequiredBooking)tripInfo);
222  // yyyy can't we really not use the tripInfo handle directly as I had it before? We may, e.g., have different providers of the same mode. kai, mar'19
223 
224  tripInfo.bookTrip( (MobsimPassengerAgent) agent );
225 
226  //to reduce number of possibilities, I would simply assume that notification always comes later
227  //
228  // --> yes, with DRT it will always come in the next time step, I adapted code accordingly (michal)
229 
230  // wait for notification:
231  ((Activity)WithinDayAgentUtils.getCurrentPlanElement(agent)).setEndTime(Double.MAX_VALUE);
232 // TripInfoRequestWithActivities tripInfoRequest = (TripInfoRequestWithActivities)tripInfo.getOriginalRequest();
233 // tripInfoRequest.getFromActivity().setEndTime(Double.MAX_VALUE);
234  // There is no guarantee that the activity that is in the tripInfoRequest is still the behavioral object of the agent. kai, jan'20
235 
236  // one corner case is that the sim start time is set to later than some agent activity end time. Then, depending on the order of the engines, it may happen
237  // that the agent departs. It will _then_ attempt to pre-book the trip, and up here, and then evidently cannot be cast into an activity.
238 
239  editPlans.rescheduleActivityEnd(agent);
240 
241  // (if the trip requires booking, the booking confirmation comes later, so we need to delay the call to
242  // notifyChangedTripInformation until we have confirmation. The "notifyChangedTripInfo" is actually called from within dvrp (PassengerEngineWithPrebooking). )
243  } else {
244  // if we do not have to wait for the booking confirmation, we can immediately compute and insert the trip. This is (once
245  // more) done by first collecting it into a container, and process the container later. To achieve thread safety, inserting
246  // it into the container is a "synchronized" method:
247  notifyChangedTripInformation(agent, Optional.of(tripInfo));
248  }
249 
250  log.warn("---");
251  }
252 
253  List<ActivityEngineWithWakeup.AgentEntry> generateWakeups( MobsimAgent agent, double now ) {
254  if (!(agent instanceof HasModifiablePlan)) {
255  // (we don't want to treat DvrpAgents, CarrierAgents, TransitVehicleDrivers etc. here)
256  return Collections.emptyList();
257  }
258 
259  final Double prebookingOffset_s = PreplanningUtils.getPrebookingOffset_s( ((PlanAgent) agent).getCurrentPlan() );
260 
261  if (prebookingOffset_s == null) {
262  log.warn("The " + "prebookingOffset_s" + " is not set in the agent. No wakeup for prebooking will be generated." );
263  return Collections.emptyList();
264  }
265 
266  List<ActivityEngineWithWakeup.AgentEntry> wakeups = new ArrayList<>();
267 
268  for (String mode : new String[] { TransportMode.drt, TransportMode.taxi } ) {
269  // (only do the following for drt and taxi yyyy which means it may fail for, say, "drt2". kai, apr'23)
270 
271  // (not doing this for, say, pt, is fine, though. we could still have pt as fallback mode for drt/taxi.)
272 
273  for (Leg drtLeg : EditPlans.findLegsWithModeInFuture(agent, mode )) {
274  // (find the corresponding legs)
275 
276  final double prebookingTime = drtLeg.getDepartureTime().seconds() - prebookingOffset_s;
277  if (prebookingTime < agent.getActivityEndTime()) {
278  // (yyyy and here one sees that having this in the activity engine is not very practical)
279 
280  // ### the following inserts the preplanLeg (--> preplanTrip??), to be executed at wakeup: ###
281  log.info("generating wakeup entry");
282  wakeups.add(new ActivityEngineWithWakeup.AgentEntry(agent, prebookingTime, (agent1, then) -> preplanLeg(agent1, then, drtLeg )) );
283  }
284 
285  Activity originActivity = EditTrips.findTripAtPlanElement(agent, drtLeg ).getOriginActivity();
286  if (originActivity.getEndTime().seconds() < now + 2.) {
287  originActivity.setEndTime(now + 2.);
288  WithinDayAgentUtils.resetCaches(agent); // !!!!!!!!
289  }
290 
291  // (we are still before "handleActivity" so we don't want to reschedule because then it will end
292  // up twice in the wakeup queue)
293 
294  // yyyyyy a possibly terrible quick fix to avoid that agents depart while they are thinking about pre-booking. A
295  // problem is that setting the activity end time to Double.MAX_VALUE means that the agent is forgetting the
296  // originally planned activity end time. Yes, we could leave it in the leg departure time, but this is really not
297  // very self-explanatory. kai, mar'19
298  // (Technically, we could re-schedule the wakeup time but keep the planned activity end time!)
299 
300  }
301  }
302 
303  return wakeups;
304  }
305 
306  private void preplanLeg( MobsimAgent agent, double now, Leg leg ) {
308 
309  // () search for drt trip corresponding to drt leg. Trick is using our own stage activities (drtStageActivities).
310  // () existing tests pass, but probably it is currently wrong after removing stage activity types
311  // and thereby losing the ability to only consider drtStageActivities as stage activities and nothing else
312  // () I think that this is now fixed. yyyy But there should also be a test for it. kai, jan'20
314  Gbl.assertNotNull(drtTrip);
315 
316  final double expectedEndTimeOfOriginActivity = timeInterpretation.decideOnActivityEndTime( drtTrip.getOriginActivity(), now ).seconds();
317 
318  final TripInfo.Request request = new TripInfoRequestWithActivities.Builder(scenario)
319  .setFromActivity( drtTrip.getOriginActivity() )
320  .setToActivity(drtTrip.getDestinationActivity())
321  .setTime( expectedEndTimeOfOriginActivity )
322  .setPlannedRoute( leg.getRoute() )
323  .createRequest();
324 
325  //first simulate ActivityEngineWithWakeup and then PreplanningEngine --> decision process
326  //in the same time step
327  this.notifyTripInfoNeeded(agent, request);
328 
329  // (this enters the request into tripInfoRequestMap ... would be easier to read this if it was inlined ... but the method needs to be
330  // threadsafe and this is easier to achieve with a separate method. kai, apr'24)
331 
332  // (the tripInfoRequestMap will be processed in every time step. Not sure if we can enforce that this happens after processing the
333  // wakeups, so it may happen in the following time step (--???). This would contradict the comment above the method call. kai, apr'24 )
334  }
335 
336  private void updateAgentPlan(MobsimAgent agent, TripInfo tripInfo) {
337  // Gbl.assertIf(WithinDayAgentUtils.getCurrentPlanElement(agent) instanceof Activity);
338 
340 
341  TripStructureUtils.Trip inputTrip = null;
342  Coord pickupCoord = FacilitiesUtils.decideOnCoord(tripInfo.getPickupLocation(), network, scenario.getConfig());
343  Coord dropoffCoord = FacilitiesUtils.decideOnCoord(tripInfo.getDropoffLocation(), network, scenario.getConfig());
344  // TODO: check: was PreplanningEngine.drtStageActivities, so drt* interaction only?
345  for (TripStructureUtils.Trip drtTrip : TripStructureUtils.getTrips(plan)) {
346  // recall that we have set the activity end time of the current activity to infinity, so we cannot use that any more. :-( ?!
347  // could instead use some kind of ID. Not sure if that would really be better.
348  // So here we are looking for a trip where origin and destination are close to pickup and dropoff:
349  Coord coordOrigin = PopulationUtils.decideOnCoordForActivity(drtTrip.getOriginActivity(), scenario);
350  if (CoordUtils.calcEuclideanDistance(coordOrigin, pickupCoord) > 1000.) {
351  continue;
352  }
353  Coord coordDestination = PopulationUtils.decideOnCoordForActivity(drtTrip.getDestinationActivity(), scenario);
354  if (CoordUtils.calcEuclideanDistance(coordDestination, dropoffCoord) > 1000.) {
355  continue;
356  }
357  inputTrip = drtTrip;
358  break;
359  }
360  Gbl.assertNotNull(inputTrip);
361 
362  inputTrip.getOriginActivity().setEndTime(tripInfo.getExpectedBoardingTime() - 900);
363  // yyyy means for time being we always depart 15min before pickup. kai, mar'19
365 
366  log.warn("agentId=" + agent.getId() + " | newActEndTime=" + inputTrip.getOriginActivity()
367  .getEndTime()
368  .seconds());
369 
370  final List<PlanElement> result = createDrtTripInclAccessEgress( tripInfo, inputTrip );
371 
372  TripRouter.insertTrip(plan, inputTrip.getOriginActivity(), result, inputTrip.getDestinationActivity());
373 
374  editPlans.rescheduleActivityEnd(agent);
375  // I don't think that this can ever do damage.
376 
377  log.warn("new plan for agentId=" + agent.getId());
378  for (PlanElement planElement : plan.getPlanElements()) {
379  log.warn(planElement.toString());
380  }
381  log.warn("---");
382 
383  }
384  private List<PlanElement> createDrtTripInclAccessEgress( TripInfo tripInfo, TripStructureUtils.Trip inputTrip ){
385  // code below currently has taxi hardcoded but this is not necessary IMO. kai, apr'24
386 
387  List<PlanElement> result = new ArrayList<>();
388 
389  PopulationFactory pf = population.getFactory();
390  {
391  Facility fromFacility = FacilitiesUtils.toFacility( inputTrip.getOriginActivity(), facilities );
392  Facility toFacility = tripInfo.getPickupLocation();
393  double departureTime = tripInfo.getExpectedBoardingTime() - 900.; // always depart 15min before pickup
394  List<? extends PlanElement> planElements = tripRouter.calcRoute(TransportMode.walk, fromFacility,
395  toFacility, departureTime, null, inputTrip.getTripAttributes() );
396  // not sure if this works for walk, but it should ...
397 
398  result.addAll(planElements);
399  }
400  {
401  Activity act = pf.createActivityFromLinkId(createStageActivityType(TransportMode.taxi),
402  tripInfo.getPickupLocation().getLinkId() );
403  act.setMaximumDuration(0.);
404  result.add(act);
405  }
406  {
407  Leg leg = pf.createLeg(TransportMode.taxi);
408  result.add(leg);
409  Route route = pf.getRouteFactories()
410  .createRoute(GenericRouteImpl.class, tripInfo.getPickupLocation().getLinkId(),
411  tripInfo.getDropoffLocation().getLinkId() );
412  leg.setRoute(route);
413  }
414  {
415  Activity act = pf.createActivityFromLinkId(createStageActivityType(TransportMode.taxi),
416  tripInfo.getDropoffLocation().getLinkId() );
417  act.setMaximumDuration(0.);
418  result.add(act);
419  }
420  {
421  Facility fromFacility = tripInfo.getDropoffLocation();
422  Facility toFacility = FacilitiesUtils.toFacility( inputTrip.getDestinationActivity(), facilities );
423  double expectedTravelTime;
424  try {
425  expectedTravelTime = tripInfo.getExpectedTravelTime();
426  } catch (Exception ee) {
427  expectedTravelTime = 15. * 60; // using 15min as quick fix since dvrp refuses to provide this. kai, mar'19
428  }
429  double departureTime = tripInfo.getExpectedBoardingTime() + expectedTravelTime;
430  List<? extends PlanElement> planElements = tripRouter.calcRoute(TransportMode.walk, fromFacility,
431  toFacility, departureTime, null, inputTrip.getOriginActivity().getAttributes() );
432 
433  result.addAll(planElements);
434  }
435 
436  // result.add( inputTrip.getDestinationActivity() ) ;
437  return result;
438  }
439 
440  static String toString(TripInfo info) {
441  StringBuilder strb = new StringBuilder();
442  strb.append("[ ");
443  strb.append("mode=").append(info.getMode());
444  strb.append(" | ");
445  strb.append("des/expBoardingTime=").append(info.getExpectedBoardingTime());
446  // strb.append(" | ") ;
447  // strb.append( "pickupLoc=" ).append( info.getPickupLocation() ) ;
448  // strb.append(" | ") ;
449  // strb.append( "dropoffLoc=" ).append( info.getDropoffLocation() ) ;
450  strb.append(" ]");
451  return strb.toString();
452  }
453 
454 }
synchronized List<? extends PlanElement > calcRoute(final String mainMode, final Facility fromFacility, final Facility toFacility, final double departureTime, final Person person, final Attributes routingAttributes)
void setInternalInterface(InternalInterface internalInterface)
static double calcEuclideanDistance(Coord coord, Coord other)
static List< Leg > findLegsWithModeInFuture(MobsimAgent agent, String mode)
Definition: EditPlans.java:366
OptionalTime decideOnActivityEndTime(Activity activity, double startTime)
static List< PlanElement > insertTrip(final Plan plan, final Activity origin, final List<? extends PlanElement > trip, final Activity destination)
void updateAgentPlan(MobsimAgent agent, TripInfo tripInfo)
static PlanElement getCurrentPlanElement(MobsimAgent agent)
Map< Id< Person >,? extends Person > getPersons()
static Trip findTripAtPlanElement(MobsimAgent agent, PlanElement pe)
Definition: EditTrips.java:126
final Map< MobsimAgent, TripInfo.Request > tripInfoRequestMap
List< PlanElement > createDrtTripInclAccessEgress(TripInfo tripInfo, TripStructureUtils.Trip inputTrip)
void decide(MobsimAgent agent, List< TripInfo > allTripInfos)
static List< Trip > getTrips(final Plan plan)
List< PlanElement > getPlanElements()
final Map< String, TripInfo.Provider > tripInfoProviders
final Map< MobsimAgent, Optional< TripInfo > > tripInfoUpdatesMap
static void assertNotNull(Object obj)
Definition: Gbl.java:212
List< DepartureHandler > getDepartureHandlers()
static Trip findTripAtPlanElement(PlanElement currentPlanElement, Plan plan)
void rescheduleActivityEnd(MobsimAgent agent)
Definition: EditPlans.java:314
static String createStageActivityType(String mode)
ActivityFacilities getActivityFacilities()
static Coord decideOnCoordForActivity(Activity act, Scenario sc)
static Coord decideOnCoord(final Facility facility, final Network network, final Config config)
synchronized void notifyChangedTripInformation(MobsimAgent agent, Optional< TripInfo > tripInfoUpdate)
void setEndTime(final double seconds)
synchronized void notifyTripInfoNeeded(MobsimAgent agent, TripInfo.Request tripInfoRequest)
static Facility toFacility(final Activity toWrap, ActivityFacilities activityFacilities)
void preplanLeg(MobsimAgent agent, double now, Leg leg)
void bookTrip(MobsimPassengerAgent agent)