diff --git a/contribs/ev/src/main/java/org/matsim/contrib/ev/discharging/DischargingModule.java b/contribs/ev/src/main/java/org/matsim/contrib/ev/discharging/DischargingModule.java index 96078d1eab1..473def02fcf 100644 --- a/contribs/ev/src/main/java/org/matsim/contrib/ev/discharging/DischargingModule.java +++ b/contribs/ev/src/main/java/org/matsim/contrib/ev/discharging/DischargingModule.java @@ -20,12 +20,13 @@ package org.matsim.contrib.ev.discharging; -import com.google.inject.Singleton; import org.matsim.contrib.ev.EvModule; import org.matsim.contrib.ev.temperature.TemperatureService; import org.matsim.core.controler.AbstractModule; import org.matsim.core.mobsim.qsim.AbstractQSimModule; +import com.google.inject.Singleton; + /** * @author Michal Maciejewski (michalm) */ @@ -34,16 +35,17 @@ public final class DischargingModule extends AbstractModule { public void install() { bind(DriveEnergyConsumption.Factory.class).toInstance(ev -> new OhdeSlaskiDriveEnergyConsumption()); bind(TemperatureService.class).toInstance(linkId -> 15);// XXX fixed temperature 15 oC - bind(AuxEnergyConsumption.Factory.class).to(OhdeSlaskiAuxEnergyConsumption.Factory.class).in( Singleton.class ); + bind(AuxEnergyConsumption.Factory.class).to(OhdeSlaskiAuxEnergyConsumption.Factory.class).in(Singleton.class); installQSimModule(new AbstractQSimModule() { @Override protected void configureQSim() { - this.bind(DriveDischargingHandler.class).in( Singleton.class ); + this.bind(DriveDischargingHandler.class).in(Singleton.class); addMobsimScopeEventHandlerBinding().to(DriveDischargingHandler.class); + this.addQSimComponentBinding(EvModule.EV_COMPONENT).to(DriveDischargingHandler.class); // event handlers are not qsim components - this.bind(IdleDischargingHandler.class).in( Singleton.class ); + this.bind(IdleDischargingHandler.class).in(Singleton.class); addMobsimScopeEventHandlerBinding().to(IdleDischargingHandler.class); this.addQSimComponentBinding(EvModule.EV_COMPONENT).to(IdleDischargingHandler.class); diff --git a/contribs/ev/src/main/java/org/matsim/contrib/ev/discharging/DriveDischargingHandler.java b/contribs/ev/src/main/java/org/matsim/contrib/ev/discharging/DriveDischargingHandler.java index ca8508a0e74..53b29390fac 100644 --- a/contribs/ev/src/main/java/org/matsim/contrib/ev/discharging/DriveDischargingHandler.java +++ b/contribs/ev/src/main/java/org/matsim/contrib/ev/discharging/DriveDischargingHandler.java @@ -19,7 +19,9 @@ package org.matsim.contrib.ev.discharging; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; import org.matsim.api.core.v01.Id; @@ -35,6 +37,8 @@ import org.matsim.contrib.ev.fleet.ElectricVehicle; import org.matsim.core.api.experimental.events.EventsManager; import org.matsim.core.events.MobsimScopeEventHandler; +import org.matsim.core.mobsim.framework.events.MobsimAfterSimStepEvent; +import org.matsim.core.mobsim.framework.listeners.MobsimAfterSimStepListener; import org.matsim.vehicles.Vehicle; import com.google.inject.Inject; @@ -45,7 +49,9 @@ * idle discharge process (see {@link IdleDischargingHandler}). */ public final class DriveDischargingHandler - implements LinkLeaveEventHandler, VehicleEntersTrafficEventHandler, VehicleLeavesTrafficEventHandler, MobsimScopeEventHandler { + implements LinkLeaveEventHandler, VehicleEntersTrafficEventHandler, VehicleLeavesTrafficEventHandler, MobsimScopeEventHandler, + MobsimAfterSimStepListener { + private static class EvDrive { private final Id vehicleId; private final ElectricVehicle ev; @@ -67,6 +73,9 @@ private boolean isOnFirstLink() { private final Map, ? extends ElectricVehicle> eVehicles; private final Map, EvDrive> evDrives; + private final List linkLeaveEvents = new ArrayList<>(); + private final List trafficLeaveEvents = new ArrayList<>(); + @Inject DriveDischargingHandler(ElectricFleet data, Network network, EventsManager eventsManager) { this.network = network; @@ -86,24 +95,35 @@ public void handleEvent(VehicleEntersTrafficEvent event) { @Override public void handleEvent(LinkLeaveEvent event) { - EvDrive evDrive = dischargeVehicle(event.getVehicleId(), event.getLinkId(), event.getTime()); - if (evDrive != null) { - evDrive.movedOverNodeTime = event.getTime(); - } + linkLeaveEvents.add(event); } @Override public void handleEvent(VehicleLeavesTrafficEvent event) { - EvDrive evDrive = dischargeVehicle(event.getVehicleId(), event.getLinkId(), event.getTime()); - if (evDrive != null) { - evDrives.remove(evDrive.vehicleId); + trafficLeaveEvents.add(event); + } + + @Override + public void notifyMobsimAfterSimStep(MobsimAfterSimStepEvent e) { + // We want to process events in the main thread (instead of the event handling threads). + // This is to eliminate race conditions, where the battery is read/modified by many threads without proper synchronisation + for (var event : linkLeaveEvents) { + EvDrive evDrive = dischargeVehicle(event.getVehicleId(), event.getLinkId(), event.getTime()); + if (evDrive != null) { + evDrive.movedOverNodeTime = event.getTime(); + } + } + linkLeaveEvents.clear(); + + for (var event : trafficLeaveEvents) { + EvDrive evDrive = dischargeVehicle(event.getVehicleId(), event.getLinkId(), event.getTime()); + if (evDrive != null) { + evDrives.remove(evDrive.vehicleId); + } } + trafficLeaveEvents.clear(); } - //XXX The current implementation is thread-safe because no other EventHandler modifies battery charge - // (for instance, AUX discharging and battery charging modifies charge outside event handling - // (as MobsimAfterSimStepListeners) - //TODO In the long term, it will be safer to move the discharging procedure to a MobsimAfterSimStepListener private EvDrive dischargeVehicle(Id vehicleId, Id linkId, double eventTime) { EvDrive evDrive = evDrives.get(vehicleId); if (evDrive != null && !evDrive.isOnFirstLink()) {// handle only our EVs, except for the first link @@ -111,11 +131,11 @@ private EvDrive dischargeVehicle(Id vehicleId, Id linkId, double double tt = eventTime - evDrive.movedOverNodeTime; ElectricVehicle ev = evDrive.ev; double energy = ev.getDriveEnergyConsumption().calcEnergyConsumption(link, tt, eventTime - tt) + ev.getAuxEnergyConsumption() - .calcEnergyConsumption(eventTime - tt, tt, linkId); + .calcEnergyConsumption(eventTime - tt, tt, linkId); //Energy consumption may be negative on links with negative slope ev.getBattery() - .dischargeEnergy(energy, - missingEnergy -> eventsManager.processEvent(new MissingEnergyEvent(eventTime, ev.getId(), link.getId(), missingEnergy))); + .dischargeEnergy(energy, + missingEnergy -> eventsManager.processEvent(new MissingEnergyEvent(eventTime, ev.getId(), link.getId(), missingEnergy))); eventsManager.processEvent(new DrivingEnergyConsumptionEvent(eventTime, vehicleId, linkId, energy, ev.getBattery().getCharge())); } return evDrive; diff --git a/contribs/taxi/test/input/org/matsim/contrib/etaxi/run/RunETaxiScenarioIT/testAssignment/output_events.xml.gz b/contribs/taxi/test/input/org/matsim/contrib/etaxi/run/RunETaxiScenarioIT/testAssignment/output_events.xml.gz index 21bc9c51d4c..88cd4466237 100644 Binary files a/contribs/taxi/test/input/org/matsim/contrib/etaxi/run/RunETaxiScenarioIT/testAssignment/output_events.xml.gz and b/contribs/taxi/test/input/org/matsim/contrib/etaxi/run/RunETaxiScenarioIT/testAssignment/output_events.xml.gz differ diff --git a/contribs/taxi/test/input/org/matsim/contrib/etaxi/run/RunETaxiScenarioIT/testOneTaxi/output_events.xml.gz b/contribs/taxi/test/input/org/matsim/contrib/etaxi/run/RunETaxiScenarioIT/testOneTaxi/output_events.xml.gz index bebb946b432..16c754539b8 100644 Binary files a/contribs/taxi/test/input/org/matsim/contrib/etaxi/run/RunETaxiScenarioIT/testOneTaxi/output_events.xml.gz and b/contribs/taxi/test/input/org/matsim/contrib/etaxi/run/RunETaxiScenarioIT/testOneTaxi/output_events.xml.gz differ diff --git a/contribs/taxi/test/input/org/matsim/contrib/etaxi/run/RunETaxiScenarioIT/testRuleBased/output_events.xml.gz b/contribs/taxi/test/input/org/matsim/contrib/etaxi/run/RunETaxiScenarioIT/testRuleBased/output_events.xml.gz index eb9edc4e49e..88cd4466237 100644 Binary files a/contribs/taxi/test/input/org/matsim/contrib/etaxi/run/RunETaxiScenarioIT/testRuleBased/output_events.xml.gz and b/contribs/taxi/test/input/org/matsim/contrib/etaxi/run/RunETaxiScenarioIT/testRuleBased/output_events.xml.gz differ