Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ev): add charging priorities and reservations #3632

Merged
merged 1 commit into from
Dec 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading