Skip to content

Commit

Permalink
Merge pull request #2944 from matsim-org/fixedChargingTime
Browse files Browse the repository at this point in the history
ev: handle waiting time
  • Loading branch information
tschlenther authored Nov 27, 2023
2 parents e2a10c2 + cb32778 commit 65561d7
Show file tree
Hide file tree
Showing 12 changed files with 296 additions and 179 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ public static EvConfigGroup get(Config config) {
@Comment("Minimum activity duration for charging. Used in EvNetwork Routing.")
public int minimumChargeTime = 1200;

@Parameter("enforceChargingInteractionDuration")
@Comment("If true, prolongs the charging interaction for the amount of time waiting in the charger queue (plus 1 second), i.e." +
"enforces that charging interactions are undertaken as long as initially planned (by EVNetworkRoutingModule). Default is false.")
public boolean enforceChargingInteractionDuration = false;

@Parameter
@Comment("Location of the chargers file")
@NotNull
Expand Down
11 changes: 10 additions & 1 deletion contribs/ev/src/main/java/org/matsim/contrib/ev/EvModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

package org.matsim.contrib.ev;

import com.google.inject.Inject;
import com.google.inject.Singleton;
import org.matsim.contrib.ev.charging.VehicleChargingHandler;
import org.matsim.core.controler.AbstractModule;
Expand All @@ -27,6 +28,9 @@
public class EvModule extends AbstractModule {
public static final String EV_COMPONENT = "EV_COMPONENT";

@Inject
private EvConfigGroup evCfg;

public EvModule(){}

@Override
Expand All @@ -36,7 +40,12 @@ public void install() {
// this is not for DynVehicles. Does that mean that we cannot combine charging for normal vehicles with charging for eTaxis? Can't say ... kai, dec'22
installQSimModule(new AbstractQSimModule() {
@Override protected void configureQSim() {
addMobsimScopeEventHandlerBinding().to( VehicleChargingHandler.class ).in( Singleton.class );
bind(VehicleChargingHandler.class).in(Singleton.class);
addMobsimScopeEventHandlerBinding().to( VehicleChargingHandler.class);
if(evCfg.enforceChargingInteractionDuration){
this.addQSimComponentBinding(EvModule.EV_COMPONENT).to(VehicleChargingHandler.class);
addMobsimListenerBinding().to(VehicleChargingHandler.class);
}
}
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,20 +20,14 @@

package org.matsim.contrib.ev.charging;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import javax.annotation.Nullable;

import com.google.common.base.Preconditions;
import org.matsim.api.core.v01.Id;
import org.matsim.api.core.v01.IdMap;
import org.matsim.contrib.ev.fleet.ElectricVehicle;
import org.matsim.vehicles.Vehicle;

import com.google.common.base.Preconditions;
import javax.annotation.Nullable;
import java.util.*;
import java.util.stream.Collectors;

/**
* @author Michal Maciejewski (michalm)
Expand All @@ -44,7 +38,11 @@ public class ChargingEventSequenceCollector
public static class ChargingSequence {
@Nullable //null if no queueing occurred
private final QueuedAtChargerEvent queuedAtCharger;
@Nullable //null if queue was never quit early, i.e. if chargingStartEvent != null;
private QuitQueueAtChargerEvent quitQueueEvent;
@Nullable //null if queue was quit early, i.e. if QuitQueueAtChargerEvent != null;
private ChargingStartEvent chargingStartEvent;
@Nullable //null if quitQueueEvent != null OR if charging was never completed
private ChargingEndEvent chargingEndEvent;

public ChargingSequence(@Nullable QueuedAtChargerEvent queuedAtCharger) {
Expand All @@ -56,12 +54,19 @@ public Optional<QueuedAtChargerEvent> getQueuedAtCharger() {
return Optional.ofNullable(queuedAtCharger);
}

public ChargingStartEvent getChargingStart() {
return chargingStartEvent;
@Nullable
public Optional<QuitQueueAtChargerEvent> getQuitQueueAtChargerEvent() {
return Optional.ofNullable(quitQueueEvent);
}

public ChargingEndEvent getChargingEnd() {
return chargingEndEvent;
@Nullable
public Optional<ChargingStartEvent> getChargingStart() {
return Optional.ofNullable(chargingStartEvent);
}

@Nullable
public Optional<ChargingEndEvent> getChargingEnd() {
return Optional.ofNullable(chargingEndEvent);
}
}

Expand All @@ -72,6 +77,10 @@ public List<ChargingSequence> getCompletedSequences() {
return Collections.unmodifiableList(completedSequences);
}

public Set<ChargingSequence> getOnGoingSequences() {
return ongoingSequences.values().stream().collect(Collectors.toUnmodifiableSet());
}

@Override
public void handleEvent(QueuedAtChargerEvent event) {
Preconditions.checkState(ongoingSequences.put(event.getVehicleId(), new ChargingSequence(event)) == null,
Expand All @@ -87,6 +96,7 @@ public void handleEvent(QuitQueueAtChargerEvent event) {
event.getVehicleId(), event.getChargerId());
Preconditions.checkState(sequence.chargingStartEvent == null, "Vehicle (%s) is already plugged",
event.getVehicleId(), event.getChargerId());
sequence.quitQueueEvent = event;
completedSequences.add(sequence);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,23 +19,15 @@

package org.matsim.contrib.ev.charging;

import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.Objects;
import java.util.Queue;
import java.util.concurrent.LinkedBlockingQueue;

import com.google.common.base.Preconditions;
import org.matsim.api.core.v01.Id;
import org.matsim.contrib.ev.fleet.ElectricVehicle;
import org.matsim.contrib.ev.infrastructure.ChargerSpecification;
import org.matsim.core.api.experimental.events.EventsManager;
import org.matsim.vehicles.Vehicle;

import com.google.common.base.Preconditions;
import java.util.*;
import java.util.concurrent.LinkedBlockingQueue;

public class ChargingWithQueueingLogic implements ChargingLogic {
protected final ChargerSpecification charger;
Expand Down Expand Up @@ -73,6 +65,11 @@ public void chargeVehicles(double chargePeriod, double now) {
}
}

int queuedToPluggedCount = Math.min(queuedVehicles.size(), charger.getPlugCount() - pluggedVehicles.size());
for (int i = 0; i < queuedToPluggedCount; i++) {
plugVehicle(queuedVehicles.poll(), now);
}

var arrivingVehiclesIter = arrivingVehicles.iterator();
while (arrivingVehiclesIter.hasNext()) {
var ev = arrivingVehiclesIter.next();
Expand All @@ -83,11 +80,6 @@ public void chargeVehicles(double chargePeriod, double now) {
}
arrivingVehiclesIter.remove();
}

int queuedToPluggedCount = Math.min(queuedVehicles.size(), charger.getPlugCount() - pluggedVehicles.size());
for (int i = 0; i < queuedToPluggedCount; i++) {
plugVehicle(queuedVehicles.poll(), now);
}
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,9 @@
* This is an events based approach to trigger vehicle charging. Vehicles will be charged as soon as a person begins a charging activity.
*/

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableListMultimap;
import jakarta.inject.Inject;

import org.matsim.api.core.v01.Id;
import org.matsim.api.core.v01.events.ActivityEndEvent;
import org.matsim.api.core.v01.events.ActivityStartEvent;
Expand All @@ -37,17 +34,25 @@
import org.matsim.api.core.v01.events.handler.ActivityStartEventHandler;
import org.matsim.api.core.v01.events.handler.PersonLeavesVehicleEventHandler;
import org.matsim.api.core.v01.network.Link;
import org.matsim.api.core.v01.population.Activity;
import org.matsim.api.core.v01.population.Person;
import org.matsim.api.core.v01.population.PlanElement;
import org.matsim.contrib.ev.EvConfigGroup;
import org.matsim.contrib.ev.fleet.ElectricFleet;
import org.matsim.contrib.ev.fleet.ElectricVehicle;
import org.matsim.contrib.ev.infrastructure.Charger;
import org.matsim.contrib.ev.infrastructure.ChargingInfrastructure;
import org.matsim.contrib.ev.infrastructure.ChargingInfrastructureUtils;
import org.matsim.core.config.groups.ScoringConfigGroup;
import org.matsim.core.events.MobsimScopeEventHandler;
import org.matsim.core.mobsim.framework.MobsimAgent;
import org.matsim.core.mobsim.framework.events.MobsimBeforeSimStepEvent;
import org.matsim.core.mobsim.framework.listeners.MobsimBeforeSimStepListener;
import org.matsim.core.mobsim.qsim.QSim;
import org.matsim.core.mobsim.qsim.agents.WithinDayAgentUtils;
import org.matsim.vehicles.Vehicle;

import com.google.common.collect.ImmutableListMultimap;
import java.util.*;

/**
* This is an events based approach to trigger vehicle charging. Vehicles will be charged as soon as a person begins a charging activity.
Expand All @@ -56,23 +61,28 @@
* (It may work together, but that would need to be tested. kai based on michal, dec'22)
*/
public class VehicleChargingHandler
implements ActivityStartEventHandler, ActivityEndEventHandler, PersonLeavesVehicleEventHandler,
ChargingEndEventHandler, MobsimScopeEventHandler {
implements ActivityStartEventHandler, ActivityEndEventHandler, PersonLeavesVehicleEventHandler, QueuedAtChargerEventHandler, ChargingStartEventHandler,
ChargingEndEventHandler, QuitQueueAtChargerEventHandler,
MobsimBeforeSimStepListener, MobsimScopeEventHandler {

public static final String CHARGING_IDENTIFIER = " charging";
public static final String CHARGING_INTERACTION = ScoringConfigGroup.createStageActivityType(
CHARGING_IDENTIFIER);
private final Map<Id<Person>, Id<Vehicle>> lastVehicleUsed = new HashMap<>();
private final Map<Id<Vehicle>, Id<Person>> lastDriver = new HashMap<>();
private final Map<Id<Vehicle>, Id<Charger>> vehiclesAtChargers = new HashMap<>();
private final Set<Id<Person>> agentsInChargerQueue = new HashSet<>();

private final ChargingInfrastructure chargingInfrastructure;
private final ElectricFleet electricFleet;
private final ImmutableListMultimap<Id<Link>, Charger> chargersAtLinks;
private final EvConfigGroup evCfg;

@Inject
VehicleChargingHandler(ChargingInfrastructure chargingInfrastructure, ElectricFleet electricFleet) {
VehicleChargingHandler(ChargingInfrastructure chargingInfrastructure, ElectricFleet electricFleet, EvConfigGroup evConfigGroup) {
this.chargingInfrastructure = chargingInfrastructure;
this.electricFleet = electricFleet;
this.evCfg = evConfigGroup;
chargersAtLinks = ChargingInfrastructureUtils.getChargersAtLinks(chargingInfrastructure );
}

Expand Down Expand Up @@ -119,11 +129,78 @@ public void handleEvent(ActivityEndEvent event) {
@Override
public void handleEvent(PersonLeavesVehicleEvent event) {
lastVehicleUsed.put(event.getPersonId(), event.getVehicleId());
lastDriver.put(event.getVehicleId(), event.getPersonId());
}

@Override
public void handleEvent(ChargingEndEvent event) {
vehiclesAtChargers.remove(event.getVehicleId());
//Charging has ended before activity ends
vehiclesAtChargers.remove(event.getVehicleId());
agentsInChargerQueue.remove(lastDriver.get(event.getVehicleId()));
}

@Override
public void handleEvent(ChargingStartEvent event) {
agentsInChargerQueue.remove(event.getVehicleId());
}

@Override
public void handleEvent(QueuedAtChargerEvent event) {
//vehiclesAtChargers should normally already contain the vehicle, but assure this nevertheless
vehiclesAtChargers.put(event.getVehicleId(), event.getChargerId());
Id<Person> driver = lastDriver.get(event.getVehicleId());
agentsInChargerQueue.add(driver);
}

/**
* This method tries to extend the charging activity as long as the agent is still in the waiting queue. This aims to model
* charging for a fixed predefined time (that does not include waiting).
* It is inspired by org.matsim.freight.carriers.controler.WithinDayActivityReScheduling
* @param e
*/
@Override
public void notifyMobsimBeforeSimStep(MobsimBeforeSimStepEvent e) {
//TODO only do this every <evConfig.chargeTimeStep> seconds ?? (because

//not sure how we should best get the MobsimAgent in some other way
//as PopulationAgentSource does not provide a collection of MobsimAgents and injecting the qsim into this class did not seem like a better solution to me
//tschlenther, nov' 23
QSim qsim = (QSim) e.getQueueSimulation();
for (Id<Person> agentId : agentsInChargerQueue) {
MobsimAgent mobsimAgent = qsim.getAgents().get(agentId);

//ideally, we would have an instance of EditPlans and then call rescheduleCurrentActivityEndtime
//but I don't see an easy way to instantiate EditPlans right now,because it needs EditTrips, which needs a lot of heavy-weight infrastructure..
PlanElement currentPlanElement = WithinDayAgentUtils.getCurrentPlanElement(mobsimAgent);
if (currentPlanElement instanceof Activity act) {
Preconditions.checkState(act.getType().endsWith(CHARGING_INTERACTION),
"agent " + agentId + " is registered as waiting in a charger queue but the currentPlanElement is not an activity of type " + CHARGING_INTERACTION + "!");
//EvNetworkRoutingModule models the charging activity with a maximum duration and does not set an end time
//This means, we just have to call WithinDayAgentUtils.resetCaches, because this triggers recalculation of the activity end time
//based on the duration and the _current_ simulation time. This means, an adjustment of act.maximumDuration is not needed but rather obsolete and would need to too long extension!
//I am not sure, whether this causes some problems later, because the actual activity duration might then be longer than the act.maximumDuration...
//tschlenther, nov' 23
// act.setMaximumDuration(act.getMaximumDuration().orElseThrow(IllegalStateException::new) + 1d);
WithinDayAgentUtils.resetCaches(mobsimAgent);
WithinDayAgentUtils.rescheduleActivityEnd(mobsimAgent, qsim);
} else {
throw new IllegalStateException("agent " + agentId + " is registered as waiting in a charger queue but the currentPlanElement is not an activity!");
}
}
}


@Override
public void handleEvent(QuitQueueAtChargerEvent event) {
if(evCfg.enforceChargingInteractionDuration){
//this could actually happen when combining with edrt/etaxi/evrp
throw new RuntimeException("should currently not happen, as queue is only quit by the agent if the charging activity ended" +
" and this should not happen with fixed charging activity duration.\n" +
"If you run evrp together with conventional (preplanned) EV, please refer to VSP.");
} else {
//Charging has ended before activity ends
vehiclesAtChargers.remove(event.getVehicleId());
agentsInChargerQueue.remove(lastDriver.get(event.getVehicleId()));
}
}
}
Loading

0 comments on commit 65561d7

Please sign in to comment.