Skip to content

Commit

Permalink
feat(ev): add charging priorities and reservations (#3632)
Browse files Browse the repository at this point in the history
  • Loading branch information
sebhoerl authored Dec 6, 2024
1 parent 78bf6b7 commit bee9a70
Show file tree
Hide file tree
Showing 10 changed files with 281 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -54,15 +54,18 @@ public void install() {

// this.addMobsimListenerBinding().to( ChargingHandler.class ).in( Singleton.class );
// does not work since ChargingInfrastructure is not available.

// standard charging priority for all chargers
bind(ChargingPriority.Factory.class).toInstance(ChargingPriority.FIFO);
}

@Provides @Singleton
ChargingWithQueueingLogic.Factory provideChargingWithQueueingLogicFactory(EventsManager eventsManager) {
return new ChargingWithQueueingLogic.Factory(eventsManager);
ChargingWithQueueingLogic.Factory provideChargingWithQueueingLogicFactory(EventsManager eventsManager, ChargingPriority.Factory chargingPriorityFactory) {
return new ChargingWithQueueingLogic.Factory(eventsManager, chargingPriorityFactory);
}

@Provides @Singleton
ChargingWithQueueingAndAssignmentLogic.Factory provideChargingWithQueueingAndAssignmentLogicFactory(EventsManager eventsManager) {
return new ChargingWithQueueingAndAssignmentLogic.Factory(eventsManager);
ChargingWithQueueingAndAssignmentLogic.Factory provideChargingWithQueueingAndAssignmentLogicFactory(EventsManager eventsManager, ChargingPriority.Factory chargingPriorityFactory) {
return new ChargingWithQueueingAndAssignmentLogic.Factory(eventsManager, chargingPriorityFactory);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package org.matsim.contrib.ev.charging;

import org.matsim.contrib.ev.charging.ChargingLogic.ChargingVehicle;
import org.matsim.contrib.ev.infrastructure.ChargerSpecification;

/**
* This interface is supposed to decide if a vehicle can be plugged right now or
* if it needs to go to / remain in the queue. While the condition whether
* enough of empty plugs are available is *always* checked, the presented method
* allows to define a more complex logic beyond that.
*
* @author Sebastian Hörl (sebhoerl), IRT SystemX
*/
public interface ChargingPriority {
/**
* The vehicle can start charging if the method returns true, otherwise it stays
* in the queue.
*/
boolean requestPlugNext(ChargingVehicle cv, double now);

public interface Factory {
ChargingPriority create(ChargerSpecification charger);
}

/**
* The default charging priority: first-in first-out.
*/
static public final Factory FIFO = charger -> (ev, now) -> true;
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ public class ChargingWithQueueingAndAssignmentLogic extends ChargingWithQueueing
implements ChargingWithAssignmentLogic {
private final Map<Id<Vehicle>, ChargingVehicle> assignedVehicles = new LinkedHashMap<>();

public ChargingWithQueueingAndAssignmentLogic(ChargerSpecification charger, EventsManager eventsManager) {
super(charger, eventsManager);
public ChargingWithQueueingAndAssignmentLogic(ChargerSpecification charger, EventsManager eventsManager, ChargingPriority priority) {
super(charger, eventsManager, priority);
}

@Override
Expand Down Expand Up @@ -68,14 +68,16 @@ public Collection<ChargingVehicle> getAssignedVehicles() {

static public class Factory implements ChargingLogic.Factory {
private final EventsManager eventsManager;
private final ChargingPriority.Factory priorityFactory;

public Factory(EventsManager eventsManager) {
public Factory(EventsManager eventsManager, ChargingPriority.Factory priorityFactory) {
this.eventsManager = eventsManager;
this.priorityFactory = priorityFactory;
}

@Override
public ChargingLogic create(ChargerSpecification charger) {
return new ChargingWithQueueingAndAssignmentLogic(charger, eventsManager);
return new ChargingWithQueueingAndAssignmentLogic(charger, eventsManager, priorityFactory.create(charger));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,17 @@
public class ChargingWithQueueingLogic implements ChargingLogic {
protected final ChargerSpecification charger;
private final EventsManager eventsManager;
private final ChargingPriority priority;

private final Map<Id<Vehicle>, ChargingVehicle> pluggedVehicles = new LinkedHashMap<>();
private final Queue<ChargingVehicle> queuedVehicles = new LinkedList<>();
private final Queue<ChargingVehicle> arrivingVehicles = new LinkedBlockingQueue<>();
private final Map<Id<Vehicle>, ChargingListener> listeners = new LinkedHashMap<>();

public ChargingWithQueueingLogic(ChargerSpecification charger, EventsManager eventsManager) {
public ChargingWithQueueingLogic(ChargerSpecification charger, EventsManager eventsManager, ChargingPriority priority) {
this.charger = Objects.requireNonNull(charger);
this.eventsManager = Objects.requireNonNull(eventsManager);
this.priority = priority;
}

@Override
Expand All @@ -71,21 +73,22 @@ 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 queuedVehiclesIter = queuedVehicles.iterator();
while (queuedVehiclesIter.hasNext() && pluggedVehicles.size() < charger.getPlugCount()) {
var cv = queuedVehiclesIter.next();
if (plugVehicle(cv, now)) {
queuedVehiclesIter.remove();
}
}

var arrivingVehiclesIter = arrivingVehicles.iterator();
while (arrivingVehiclesIter.hasNext()) {
var cv = arrivingVehiclesIter.next();
if (pluggedVehicles.size() < charger.getPlugCount()) {
plugVehicle(cv, now);
} else {
if (pluggedVehicles.size() >= charger.getPlugCount() || !plugVehicle(cv, now)) {
queueVehicle(cv, now);
}
arrivingVehiclesIter.remove();
}
arrivingVehicles.clear();
}

@Override
Expand All @@ -106,8 +109,13 @@ public void removeVehicle(ElectricVehicle ev, double now) {
eventsManager.processEvent(new ChargingEndEvent(now, charger.getId(), ev.getId(), ev.getBattery().getCharge()));
listeners.remove(ev.getId()).notifyChargingEnded(ev, now);

if (!queuedVehicles.isEmpty()) {
plugVehicle(queuedVehicles.poll(), now);
var queuedVehiclesIter = queuedVehicles.iterator();
while (queuedVehiclesIter.hasNext()) {
var queuedVehicle = queuedVehiclesIter.next();
if (plugVehicle(queuedVehicle, now)) {
queuedVehiclesIter.remove();
break;
}
}
} else {
// make sure ev was in the queue
Expand All @@ -123,12 +131,20 @@ private void queueVehicle(ChargingVehicle cv, double now) {
listeners.get(cv.ev().getId()).notifyVehicleQueued(cv.ev(), now);
}

private void plugVehicle(ChargingVehicle cv, double now) {
private boolean plugVehicle(ChargingVehicle cv, double now) {
assert pluggedVehicles.size() < charger.getPlugCount();

if (!priority.requestPlugNext(cv, now)) {
return false;
}

if (pluggedVehicles.put(cv.ev().getId(), cv) != null) {
throw new IllegalArgumentException();
}
eventsManager.processEvent(new ChargingStartEvent(now, charger.getId(), cv.ev().getId(), cv.ev().getBattery().getCharge()));
listeners.get(cv.ev().getId()).notifyChargingStarted(cv.ev(), now);

return true;
}

private final Collection<ChargingVehicle> unmodifiablePluggedVehicles = Collections.unmodifiableCollection(pluggedVehicles.values());
Expand All @@ -147,14 +163,16 @@ public Collection<ChargingVehicle> getQueuedVehicles() {

static public class Factory implements ChargingLogic.Factory {
private final EventsManager eventsManager;
private final ChargingPriority.Factory chargingPriorityFactory;

public Factory(EventsManager eventsManager) {
public Factory(EventsManager eventsManager, ChargingPriority.Factory chargingPriorityFactory) {
this.eventsManager = eventsManager;
this.chargingPriorityFactory = chargingPriorityFactory;
}

@Override
public ChargingLogic create(ChargerSpecification charger) {
return new ChargingWithQueueingLogic(charger, eventsManager);
return new ChargingWithQueueingLogic(charger, eventsManager, chargingPriorityFactory.create(charger));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
/**
* @author Michal Maciejewski (michalm)
*/
final class ElectricFleetSpecificationDefaultImpl implements ElectricFleetSpecification {
public final class ElectricFleetSpecificationDefaultImpl implements ElectricFleetSpecification {
private final Map<Id<Vehicle>, ElectricVehicleSpecification> specifications = new LinkedHashMap<>();

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
/**
* @author Michal Maciejewski (michalm)
*/
final class ElectricVehicleSpecificationDefaultImpl implements ElectricVehicleSpecification {
public final class ElectricVehicleSpecificationDefaultImpl implements ElectricVehicleSpecification {

private final Vehicle matsimVehicle;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
/**
* @author Michal Maciejewski (michalm)
*/
final class ChargingInfrastructureSpecificationDefaultImpl implements ChargingInfrastructureSpecification {
public final class ChargingInfrastructureSpecificationDefaultImpl implements ChargingInfrastructureSpecification {
private final SpecificationContainer<Charger, ChargerSpecification> container = new SpecificationContainer<>();

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package org.matsim.contrib.ev.reservation;

import java.util.LinkedList;
import java.util.List;

import org.matsim.api.core.v01.IdMap;
import org.matsim.contrib.ev.fleet.ElectricVehicle;
import org.matsim.contrib.ev.infrastructure.Charger;
import org.matsim.contrib.ev.infrastructure.ChargerSpecification;
import org.matsim.core.controler.events.IterationStartsEvent;
import org.matsim.core.controler.listener.IterationStartsListener;

/**
* This class is a singleton service that keeps a list of reservations for
* chargers. It can be used in combination with the
* ReservationBasedChargingPriority to let vehicle pass on to charging only if
* they have a proper reservation.
*
* @author Sebastian Hörl (sebhoerl), IRT SystemX
*/
public class ChargerReservationManager implements IterationStartsListener {
private final IdMap<Charger, List<Reservation>> reservations = new IdMap<>(Charger.class);

public boolean isAvailable(ChargerSpecification charger, ElectricVehicle vehicle, double startTile,
double endTime) {
if (charger.getPlugCount() == 0) {
return false;
}

if (!reservations.containsKey(charger.getId())) {
return true;
}

int remaining = charger.getPlugCount();
for (Reservation reservation : reservations.get(charger.getId())) {
if (reservation.vehicle != vehicle && isOverlapping(reservation, startTile, endTime)) {
remaining--;
}
}

return remaining > 0;
}

private boolean isOverlapping(Reservation reservation, double startTime, double endTime) {
if (startTime >= reservation.startTime && startTime <= reservation.endTime) {
return true; // start time within existing range
} else if (endTime >= reservation.startTime && endTime <= reservation.endTime) {
return true; // end time within existing range
} else if (startTime <= reservation.startTime && endTime >= reservation.endTime) {
return true; // new range covers existing range
} else {
return false;
}
}

public Reservation addReservation(ChargerSpecification charger, ElectricVehicle vehicle, double startTime,
double endTime) {
if (isAvailable(charger, vehicle, startTime, endTime)) {
List<Reservation> chargerReservations = reservations.get(charger.getId());

if (chargerReservations == null) {
chargerReservations = new LinkedList<>();
reservations.put(charger.getId(), chargerReservations);
}

Reservation reservation = new Reservation(charger, vehicle, startTime, endTime);
chargerReservations.add(reservation);

return reservation;
}

return null;
}

public boolean removeReservation(Reservation reservation) {
List<Reservation> chargerReservations = reservations.get(reservation.charger.getId());

if (chargerReservations != null) {
return chargerReservations.remove(reservation);
}

return false;
}

public Reservation findReservation(ChargerSpecification charger, ElectricVehicle vehicle, double now) {
List<Reservation> chargerReservations = reservations.get(charger.getId());

if (chargerReservations != null) {
for (Reservation reservation : chargerReservations) {
if (reservation.vehicle == vehicle && now >= reservation.startTime && now <= reservation.endTime) {
return reservation;
}
}
}

return null;
}

public record Reservation(ChargerSpecification charger, ElectricVehicle vehicle, double startTime, double endTime) {
}

@Override
public void notifyIterationStarts(IterationStartsEvent event) {
reservations.clear();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package org.matsim.contrib.ev.reservation;

import org.matsim.contrib.ev.charging.ChargingPriority;
import org.matsim.core.controler.AbstractModule;

import com.google.inject.Provides;
import com.google.inject.Singleton;

/**
* This module enables the reservation-based charging logic that requires
* vehicles to have or make a reservation when attempting to charge at a
* charger.
*
* @author Sebastian Hörl (sebhoerl), IRT SystemX
*/
public class ChargerReservationModule extends AbstractModule {
@Override
public void install() {
addControlerListenerBinding().to(ChargerReservationManager.class);
bind(ChargingPriority.Factory.class).to(ReservationBasedChargingPriority.Factory.class);
}

@Provides
@Singleton
ReservationBasedChargingPriority.Factory provideReservationBasedChargingPriorityFactory(
ChargerReservationManager reservationManager) {
return new ReservationBasedChargingPriority.Factory(reservationManager);
}

@Provides
@Singleton
ChargerReservationManager provideChargerReservationManager() {
return new ChargerReservationManager();
}
}
Loading

0 comments on commit bee9a70

Please sign in to comment.