Skip to content

Commit

Permalink
Merge branch 'master' into dependabot/maven/com.google.protobuf-proto…
Browse files Browse the repository at this point in the history
…buf-java-4.28.0
  • Loading branch information
jfbischoff authored Sep 6, 2024
2 parents 9a2a3c3 + c5252ff commit 18f2466
Show file tree
Hide file tree
Showing 3 changed files with 142 additions and 64 deletions.
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
package org.matsim.contrib.drt.prebooking;

import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.PriorityQueue;
import java.util.Queue;
import java.util.Set;
import java.util.function.Supplier;

import org.matsim.api.core.v01.Id;
import org.matsim.api.core.v01.IdMap;
import org.matsim.api.core.v01.IdSet;
import org.matsim.api.core.v01.Identifiable;
import org.matsim.api.core.v01.population.Person;
import org.matsim.contrib.drt.passenger.AcceptedDrtRequest;
Expand Down Expand Up @@ -39,9 +39,13 @@ public class PrebookingStopActivity extends FirstLastSimStepDynActivity implemen
private final Map<Id<Request>, ? extends AcceptedDrtRequest> pickupRequests;
private final Map<Id<Request>, ? extends AcceptedDrtRequest> dropoffRequests;

private final IdMap<Request, Double> enterTimes = new IdMap<>(Request.class);
private final Queue<QueuedRequest> enterTimes = new PriorityQueue<>();
private final Queue<QueuedRequest> leaveTimes = new PriorityQueue<>();
private final Set<Id<Request>> enteredRequests = new HashSet<>();

private final IdSet<Request> enteredRequests = new IdSet<>(Request.class);

private final IdSet<Request> registeredPickups = new IdSet<>(Request.class);
private final IdMap<Request, AcceptedDrtRequest> expectedPickups = new IdMap<>(Request.class);

private final PrebookingManager prebookingManager;
private final PassengerHandler passengerHandler;
Expand Down Expand Up @@ -93,7 +97,6 @@ private void initDropoffRequests(double now) {
}

private boolean updateDropoffRequests(double now) {

while (!leaveTimes.isEmpty() && leaveTimes.peek().time <= now) {
Id<Request> requestId = leaveTimes.poll().id;
passengerHandler.dropOffPassengers(driver, requestId, now);
Expand All @@ -105,60 +108,94 @@ private boolean updateDropoffRequests(double now) {
}

private record QueuedRequest(Id<Request> id, double time) implements Comparable<QueuedRequest> {

@Override
public int compareTo(QueuedRequest o) {
return Double.compare(this.time, o.time);
}
}

private int cachedPickupRequestsHash = -1;

private boolean updatePickupRequests(double now, boolean isFirstStep) {
var pickupIterator = pickupRequests.values().iterator();

while (pickupIterator.hasNext()) {
var request = pickupIterator.next();

if (!enteredRequests.contains(request.getId()) && !enterTimes.containsKey(request.getId())) {
// this is a new request that has been added after the activity has been created
// or that had not arrived yet

if (passengerHandler.notifyWaitForPassengers(this, this.driver, request.getId())) {
// agent starts to enter
queuePickup(request, now);
} else if (now > request.getEarliestStartTime() && !isFirstStep) {
if (abandonVoter.abandonRequest(now, vehicle, request)) {
prebookingManager.abandon(request.getId());
}
int pickupRequestsHash = pickupRequests.hashCode();

// part 1: check if the pickup list has been updated dynamically

if (isFirstStep || pickupRequestsHash != cachedPickupRequestsHash) {
cachedPickupRequestsHash = pickupRequestsHash;

// added requests
for (AcceptedDrtRequest request : pickupRequests.values()) {
if (!registeredPickups.contains(request.getId())) {
// in the first step, this is a standard pickup request, later this is a request that has been added after the activity has been created
expectedPickups.put(request.getId(), request);
registeredPickups.add(request.getId());
}
}

// removed requests (for instance via cancellation)
var expectedIterator = expectedPickups.iterator();
while (expectedIterator.hasNext()) {
if (!pickupRequests.containsKey(expectedIterator.next().getId())) {
// a request has been removed from the list of expected pickups
expectedIterator.remove();
}
}
}

// part 2: handle the requests that we expect but which have not arrived yet

var expectedIterator = expectedPickups.values().iterator();
while (expectedIterator.hasNext()) {
AcceptedDrtRequest request = expectedIterator.next();

if (passengerHandler.notifyWaitForPassengers(this, this.driver, request.getId())) {
// agent starts to enter
queuePickup(request, now);
expectedIterator.remove();
} else if (now > request.getEarliestStartTime() && !isFirstStep) {
if (abandonVoter.abandonRequest(now, vehicle, request)) {
// abandon the request, but not in the first time step for the sake of event timing
prebookingManager.abandon(request.getId());
expectedIterator.remove();
}
}
}

// part 3: handle the requests that are currently entering the vehicle

var enterIterator = enterTimes.entrySet().iterator();
var enterIterator = enterTimes.iterator();

// logic is as follows:
// - let people enter in the order at which they arrived + their interaction time
// - but in case there is no capacity (others still disembarking) they need to wait

while (enterIterator.hasNext()) {
var entry = enterIterator.next();
int availableCapacity = vehicle.getCapacity() - onboard;

if (entry.getValue() <= now) {
int requiredCapacity = pickupRequests.get(entry.getKey()).getPassengerCount();
if (entry.time <= now) {
int requiredCapacity = pickupRequests.get(entry.id).getPassengerCount();

if (requiredCapacity <= availableCapacity) {
// let agent enter now
Verify.verify(passengerHandler.tryPickUpPassengers(this, driver, entry.getKey(), now));
enteredRequests.add(entry.getKey());
Verify.verify(passengerHandler.tryPickUpPassengers(this, driver, entry.id, now));
enteredRequests.add(entry.id);
onboard += requiredCapacity;
enterIterator.remove();
}
} else {
break;
}
}

return enterTimes.size() == 0 && pickupRequests.size() == enteredRequests.size();
return expectedPickups.size() == 0 && pickupRequests.size() == enteredRequests.size();
}

private void queuePickup(AcceptedDrtRequest request, double now) {
prebookingManager.notifyPickup(now, request);
double enterTime = now + stopDurationProvider.calcPickupDuration(vehicle, request.getRequest());
enterTimes.put(request.getId(), enterTime);
enterTimes.add(new QueuedRequest(request.getId(), enterTime));
}

@Override
Expand All @@ -169,7 +206,10 @@ protected void simStep(double now) {
@Override
public void notifyPassengersAreReadyForDeparture(List<MobsimPassengerAgent> passengers, double now) {
var request = getRequestForPassengers(passengers.stream().map(Identifiable::getId).toList());
queuePickup(request, now);
if(expectedPickups.containsKey(request.getId())) {
queuePickup(request, now);
expectedPickups.remove(request.getId());
}
}

private AcceptedDrtRequest getRequestForPassengers(List<Id<Person>> passengerIds) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
* <p>
* This class allows to implicitly apply penalties to plans by changing the duration of the egress walk after car interactions.
* By construction this only works for plans involving car trips and can not be used if you want to penalize all plans that
* fulfill a certain condition.
* fulfill a certain condition. <b>Note that if you bind this, you should also bind the return value of {@link #getBackChanger()}!</b>
* </p>
* <p>
* More precisely, the class will... </br>
Expand All @@ -49,13 +49,14 @@
* @author tkohl / Senozon
*
*/
class CarEgressWalkChanger implements BeforeMobsimListener, AfterMobsimListener {
class CarEgressWalkChanger implements BeforeMobsimListener {

public static final String PENALTY_ATTRIBUTE = "parkingPenalty";

private final CarEgressWalkObserver observer;
private final AccessEgressFinder egressFinder = new AccessEgressFinder(TransportMode.car);
private final Iter0Method iter0Method;
private final CarEgressWalkBackChanger backChanger;

/**
* Sets the class up with the specified {@linkplain PenaltyGenerator} and {@linkplain PenaltyFunction}.
Expand All @@ -66,6 +67,21 @@ class CarEgressWalkChanger implements BeforeMobsimListener, AfterMobsimListener
public CarEgressWalkChanger(PenaltyGenerator penaltyGenerator, PenaltyFunction penaltyFunction, CarEgressWalkObserver observer, Iter0Method iter0Method) {
this.observer = observer;
this.iter0Method = iter0Method;
this.backChanger = new CarEgressWalkBackChanger(this);
}

@Override
public double priority() { //we want this to happen very late, because it should only affect the mobsim, not other listeners
return -1001;
}

/**
* Returns the class that changes the egress walks bag in AfterMobsim. Note that you need to bind this separately!
*
* @return the {@linkplain CarEgressWalkBackChanger} to this class
*/
public CarEgressWalkBackChanger getBackChanger() {
return this.backChanger;
}

/**
Expand Down Expand Up @@ -93,33 +109,6 @@ public void notifyBeforeMobsim(BeforeMobsimEvent event) {
this.changeEgressTimesByGridcell(event.getServices().getScenario().getPopulation().getPersons().values(), false);
}
}

/**
* resets egress times before scoring / replanning
*/
@Override
public void notifyAfterMobsim(AfterMobsimEvent event) {
// we need to roll back the changes we made before the mobsim, otherwise we can't apply
// a different penalty next iteration.
if (event.getIteration() == 0) {
switch (this.iter0Method) {
case noPenalty:
case hourPenalty:
case estimateFromPlans:
this.changeEgressTimesByGridcell(event.getServices().getScenario().getPopulation().getPersons().values(), true);
break;
case takeFromAttributes:
this.changeEgressTimesByAttribute(event.getServices().getScenario().getPopulation().getPersons().values(), true);
break;
default:
throw new RuntimeException("Unknown iter0 mode");
}
} else {
this.changeEgressTimesByGridcell(event.getServices().getScenario().getPopulation().getPersons().values(), true);
}
// yyyy this is something we do not like: to just "fake" something and take it back afterwards. Would be good to find some other design
// eventually. Not so obvious, though ... kai, mar'20
}

/**
* Changes the egress times of all agents using cars according to the penalty for the corresponding space-time-gridcell and writes the penalty
Expand All @@ -130,12 +119,13 @@ public void notifyAfterMobsim(AfterMobsimEvent event) {
* by that time (calling this method twice, first with {@code false}, then with {@code true} should yield the original plans)
*/
private void changeEgressTimesByGridcell(Collection<? extends Person> population, boolean reverse) {
int sign = reverse ? -1 : 1;
for (Person p : population) {
for (LegActPair walkActPair : this.egressFinder.findEgressWalks(p.getSelectedPlan())) {
double penalty = sign * this.observer.getPenaltyCalculator().getPenalty(walkActPair.leg.getDepartureTime().seconds(), walkActPair.act.getCoord());
setTimes(walkActPair, penalty);
if (!reverse) {
double penalty = Math.round(this.observer.getPenaltyCalculator().getPenalty(walkActPair.leg.getDepartureTime().seconds(), walkActPair.act.getCoord()));
if (reverse) {
setTimes(walkActPair, -penalty);
} else {
setTimes(walkActPair, penalty);
walkActPair.leg.getAttributes().putAttribute(PENALTY_ATTRIBUTE, penalty);
}
}
Expand Down Expand Up @@ -171,4 +161,50 @@ private static void setTimes(LegActPair walkActPair, double penalty) {
walkActPair.leg.getRoute().setTravelTime(walkActPair.leg.getRoute().getTravelTime().seconds() + penalty);
walkActPair.act.setStartTime(walkActPair.act.getStartTime().seconds() + penalty);
}

/**
* The reverse to {@linkplain CarEgressWalkChanger}
*
* @author tkohl
*
*/
static class CarEgressWalkBackChanger implements AfterMobsimListener {
private final CarEgressWalkChanger forwardChanger;

CarEgressWalkBackChanger(CarEgressWalkChanger forwardChanger) {
this.forwardChanger = forwardChanger;
}

@Override
public double priority() { //we want this to happen very early to undo what we did before the mobsim before other listeners can pick it up
return 1001;
}

/**
* resets egress times before scoring / replanning
*/
@Override
public void notifyAfterMobsim(AfterMobsimEvent event) {
// we need to roll back the changes we made before the mobsim, otherwise we can't apply
// a different penalty next iteration.
if (event.getIteration() == 0) {
switch (forwardChanger.iter0Method) {
case noPenalty:
case hourPenalty:
case estimateFromPlans:
forwardChanger.changeEgressTimesByGridcell(event.getServices().getScenario().getPopulation().getPersons().values(), true);
break;
case takeFromAttributes:
forwardChanger.changeEgressTimesByAttribute(event.getServices().getScenario().getPopulation().getPersons().values(), true);
break;
default:
throw new RuntimeException("Unknown iter0 mode");
}
} else {
forwardChanger.changeEgressTimesByGridcell(event.getServices().getScenario().getPopulation().getPersons().values(), true);
}
// yyyy this is something we do not like: to just "fake" something and take it back afterwards. Would be good to find some other design
// eventually. Not so obvious, though ... kai, mar'20
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,9 @@ public void install() {
if (parkingConfig.getObserveOnly()) {
super.addControlerListenerBinding().toInstance(walkObserver);
} else {
super.addControlerListenerBinding().toInstance(new CarEgressWalkChanger(parkingHandler, penaltyFunction, walkObserver, parkingConfig.getIter0Method()));
CarEgressWalkChanger walkChanger = new CarEgressWalkChanger(parkingHandler, penaltyFunction, walkObserver, parkingConfig.getIter0Method());
super.addControlerListenerBinding().toInstance(walkChanger);
super.addControlerListenerBinding().toInstance(walkChanger.getBackChanger());
}
}

Expand Down

0 comments on commit 18f2466

Please sign in to comment.