Skip to content

Commit

Permalink
Merge pull request #2984 from moia-oss/drtSharingFactor
Browse files Browse the repository at this point in the history
DRT: Sharing factor and pooling rate
  • Loading branch information
nkuehnel authored Dec 7, 2023
2 parents fcd4f03 + 787e0bb commit 7985778
Show file tree
Hide file tree
Showing 5 changed files with 496 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import org.matsim.contrib.drt.schedule.DrtDriveTask;
import org.matsim.contrib.drt.schedule.DrtStayTask;
import org.matsim.contrib.drt.scheduler.EmptyVehicleRelocator;
import org.matsim.contrib.drt.sharingmetrics.SharingMetricsModule;
import org.matsim.contrib.dvrp.analysis.ExecutedScheduleCollector;
import org.matsim.contrib.dvrp.fleet.FleetSpecification;
import org.matsim.contrib.dvrp.run.AbstractDvrpModeModule;
Expand Down Expand Up @@ -136,5 +137,7 @@ public void install() {
getter.getModal(DrtVehicleDistanceStats.class), getter.get(MatsimServices.class), getter.get(Network.class),
getter.getModal(DrtEventSequenceCollector.class), getter.getModal(VehicleOccupancyProfileCalculator.class))))
.asEagerSingleton();

install(new SharingMetricsModule(drtCfg));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
package org.matsim.contrib.drt.sharingmetrics;

import com.google.inject.Inject;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartUtils;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.renderer.category.BoxAndWhiskerRenderer;
import org.jfree.data.statistics.DefaultBoxAndWhiskerCategoryDataset;
import org.matsim.api.core.v01.Id;
import org.matsim.contrib.drt.run.DrtConfigGroup;
import org.matsim.contrib.dvrp.optimizer.Request;
import org.matsim.core.config.Config;
import org.matsim.core.controler.MatsimServices;
import org.matsim.core.controler.events.IterationEndsEvent;
import org.matsim.core.controler.listener.IterationEndsListener;
import org.matsim.core.utils.io.IOUtils;

import java.io.BufferedWriter;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

/**
* @author nkuehnel / MOIA
*/
public class SharingMetricsControlerListener implements IterationEndsListener {
private final MatsimServices matsimServices;

private final DrtConfigGroup drtConfigGroup;
private final SharingMetricsTracker sharingFactorTracker;

private boolean headerWritten = false;

private final String runId;

private final String delimiter;

private static final String notAvailableString = "NA";


@Inject
public SharingMetricsControlerListener(Config config,
DrtConfigGroup drtConfigGroup,
SharingMetricsTracker sharingFactorTracker,
MatsimServices matsimServices) {
this.drtConfigGroup = drtConfigGroup;
this.sharingFactorTracker = sharingFactorTracker;
this.matsimServices = matsimServices;
runId = Optional.ofNullable(config.controller().getRunId()).orElse(notAvailableString);
this.delimiter = config.global().getDefaultDelimiter();

}

@Override
public void notifyIterationEnds(IterationEndsEvent event) {
int createGraphsInterval = event.getServices().getConfig().controller().getCreateGraphsInterval();
boolean createGraphs = createGraphsInterval >0 && event.getIteration() % createGraphsInterval == 0;

Map<Id<Request>, Double> sharingFactors = sharingFactorTracker.getSharingFactors();
Map<Id<Request>, Boolean> poolingRates = sharingFactorTracker.getPoolingRates();

writeAndPlotSharingMetrics(
sharingFactors,
poolingRates,
filename(event, "sharingFactors", ".png"),
filename(event, "poolingRates", ".png"),
filename(event, "sharingMetrics", ".csv"),
createGraphs);

double nPooled = poolingRates.values().stream().filter(b -> b).count();
double nTotal = poolingRates.values().size();
double meanPoolingRate = nPooled / nTotal;
double meanSharingFactor = sharingFactors.values().stream().mapToDouble(d -> d).average().orElse(Double.NaN);

writeIterationPoolingStats(meanPoolingRate + delimiter + meanSharingFactor + delimiter + nPooled +delimiter + nTotal, event.getIteration());
}

private void writeAndPlotSharingMetrics(Map<Id<Request>, Double> sharingFactorByRequest,
Map<Id<Request>, Boolean> rates,
String sharingFactors,
String poolingRates,
String csvFile,
boolean createGraphs) {
try (var bw = IOUtils.getBufferedWriter(csvFile)) {
bw.append(line("Request", "SharingFactor", "Pooled"));

for (Map.Entry<Id<Request>, Double> sharingFactorEntry : sharingFactorByRequest.entrySet()) {
bw.append(line(sharingFactorEntry.getKey(), sharingFactorEntry.getValue(),rates.get(sharingFactorEntry.getKey())));
}
bw.flush();

if (createGraphs) {
final DefaultBoxAndWhiskerCategoryDataset sharingFactorDataset
= new DefaultBoxAndWhiskerCategoryDataset();

final DefaultBoxAndWhiskerCategoryDataset poolingRateDataset
= new DefaultBoxAndWhiskerCategoryDataset();

sharingFactorDataset.add(sharingFactorByRequest.values().stream().toList(), "", "");
poolingRateDataset.add(rates.values().stream().toList(), "", "");

JFreeChart chartRides = ChartFactory.createBoxAndWhiskerChart("Sharing factor", "", "Factor", sharingFactorDataset, false);
JFreeChart chartPooling = ChartFactory.createBoxAndWhiskerChart("Pooling rate", "", "Rate", poolingRateDataset, false);

((BoxAndWhiskerRenderer) chartRides.getCategoryPlot().getRenderer()).setMeanVisible(true);
((BoxAndWhiskerRenderer) chartPooling.getCategoryPlot().getRenderer()).setMeanVisible(true);
ChartUtils.writeChartAsPNG(new FileOutputStream(sharingFactors), chartRides, 1500, 1500);
ChartUtils.writeChartAsPNG(new FileOutputStream(poolingRates), chartPooling, 1500, 1500);
}
} catch (
IOException e) {
throw new RuntimeException(e);
}
}

private String filename(IterationEndsEvent event, String prefix, String extension) {
return matsimServices.getControlerIO()
.getIterationFilename(event.getIteration(), prefix + "_" + drtConfigGroup.getMode() + extension);
}

private String line(Object... cells) {
return Arrays.stream(cells).map(Object::toString).collect(Collectors.joining(delimiter, "", "\n"));
}


private void writeIterationPoolingStats(String summarizePooling, int it) {
try (var bw = getAppendingBufferedWriter("drt_sharing_metrics", ".csv")) {
if (!headerWritten) {
headerWritten = true;
bw.write(line("runId", "iteration", "poolingRate", "sharingFactor", "nPooled", "nTotal"));
}
bw.write(runId + delimiter + it + delimiter + summarizePooling);
bw.newLine();
} catch (IOException e) {
throw new RuntimeException(e);
}
}

private BufferedWriter getAppendingBufferedWriter(String prefix, String extension) {
return IOUtils.getAppendingBufferedWriter(matsimServices.getControlerIO().getOutputFilename(prefix + "_" + drtConfigGroup.getMode() + extension));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package org.matsim.contrib.drt.sharingmetrics;

import org.matsim.api.core.v01.Id;
import org.matsim.api.core.v01.population.Person;
import org.matsim.contrib.drt.run.DrtConfigGroup;
import org.matsim.contrib.dvrp.run.AbstractDvrpModeModule;
import org.matsim.core.controler.MatsimServices;

/**
* @author nkuehnel / MOIA
*/
public class SharingMetricsModule extends AbstractDvrpModeModule {

private final DrtConfigGroup drtConfigGroup;

public SharingMetricsModule(DrtConfigGroup drtConfigGroup) {
super(drtConfigGroup.getMode());
this.drtConfigGroup = drtConfigGroup;
}

@Override
public void install() {
bindModal(SharingMetricsTracker.class).toProvider(modalProvider(getter ->
new SharingMetricsTracker(personId -> true))).asEagerSingleton();
addEventHandlerBinding().to(modalKey(SharingMetricsTracker.class));
bindModal(SharingMetricsControlerListener.class).toProvider(modalProvider(getter ->
new SharingMetricsControlerListener(getConfig(), drtConfigGroup,
getter.getModal(SharingMetricsTracker.class),
getter.get(MatsimServices.class))
));
addControlerListenerBinding().to(modalKey(SharingMetricsControlerListener.class));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package org.matsim.contrib.drt.sharingmetrics;

import org.matsim.api.core.v01.Id;
import org.matsim.api.core.v01.population.Person;
import org.matsim.contrib.dvrp.fleet.DvrpVehicle;
import org.matsim.contrib.dvrp.optimizer.Request;
import org.matsim.contrib.dvrp.passenger.PassengerDroppedOffEvent;
import org.matsim.contrib.dvrp.passenger.PassengerDroppedOffEventHandler;
import org.matsim.contrib.dvrp.passenger.PassengerPickedUpEvent;
import org.matsim.contrib.dvrp.passenger.PassengerPickedUpEventHandler;
import org.matsim.core.mobsim.framework.events.MobsimBeforeCleanupEvent;
import org.matsim.core.mobsim.framework.listeners.MobsimBeforeCleanupListener;

import java.util.*;

/**
* @author nkuehnel / MOIA
*/
public class SharingMetricsTracker implements PassengerPickedUpEventHandler, PassengerDroppedOffEventHandler, MobsimBeforeCleanupListener {

private final GroupPredicate groupPredicate;

record Segment(double start, int occupancy) {
}

private final Map<Id<DvrpVehicle>, List<Id<Request>>> map = new HashMap<>();

private final Map<Id<Request>, List<Segment>> segments = new HashMap<>();

private final Map<Id<Request>, Double> sharingFactors = new HashMap<>();
private final Map<Id<Request>, Boolean> poolingRate = new HashMap<>();

public interface GroupPredicate {
boolean isGroupRepresentative(Id<Person> personId);
}

public SharingMetricsTracker(GroupPredicate groupPredicate) {
this.groupPredicate = groupPredicate;
}


@Override
public void handleEvent(PassengerDroppedOffEvent event) {
if (groupPredicate.isGroupRepresentative(event.getPersonId())) {

List<Id<Request>> occupancy = map.get(event.getVehicleId());
occupancy.remove(event.getRequestId());
occupancy.forEach(p -> segments.get(p).add(new Segment(event.getTime(), occupancy.size())));


List<Segment> finishedSegments = segments.remove(event.getRequestId());

double total = 0;
double portion = 0;

boolean pooled = false;

Segment last = finishedSegments.get(0);
if (last.occupancy > 1) {
pooled = true;
}
for (int i = 1; i < finishedSegments.size(); i++) {
Segment next = finishedSegments.get(i);
double duration = next.start - last.start;
total += duration;
portion += duration / last.occupancy;
last = next;
if (last.occupancy > 1) {
pooled = true;
}
}

double duration = event.getTime() - last.start;
total += duration;
portion += duration / last.occupancy;

double sharingFactor = total / portion;
sharingFactors.put(event.getRequestId(), sharingFactor);
poolingRate.put(event.getRequestId(), pooled);
}
}

@Override
public void handleEvent(PassengerPickedUpEvent event) {
if (groupPredicate.isGroupRepresentative(event.getPersonId())) {
map.computeIfAbsent(event.getVehicleId(), vehicleId -> new ArrayList<>());
List<Id<Request>> occupancy = map.get(event.getVehicleId());
occupancy.add(event.getRequestId());
occupancy.forEach(
p -> segments.computeIfAbsent(p, requestId -> new ArrayList<>()).add(new Segment(event.getTime(), occupancy.size()))
);
}
}

@Override
public void notifyMobsimBeforeCleanup(MobsimBeforeCleanupEvent e) {
map.clear();
segments.clear();
poolingRate.clear();
sharingFactors.clear();
}

public Map<Id<Request>, Double> getSharingFactors() {
return Collections.unmodifiableMap(sharingFactors);
}

public Map<Id<Request>, Boolean> getPoolingRates() {
return Collections.unmodifiableMap(poolingRate);
}
}
Loading

0 comments on commit 7985778

Please sign in to comment.