-
Notifications
You must be signed in to change notification settings - Fork 453
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #2984 from moia-oss/drtSharingFactor
DRT: Sharing factor and pooling rate
- Loading branch information
Showing
5 changed files
with
496 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
145 changes: 145 additions & 0 deletions
145
.../src/main/java/org/matsim/contrib/drt/sharingmetrics/SharingMetricsControlerListener.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)); | ||
} | ||
} |
33 changes: 33 additions & 0 deletions
33
contribs/drt/src/main/java/org/matsim/contrib/drt/sharingmetrics/SharingMetricsModule.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)); | ||
} | ||
} |
110 changes: 110 additions & 0 deletions
110
contribs/drt/src/main/java/org/matsim/contrib/drt/sharingmetrics/SharingMetricsTracker.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |
Oops, something went wrong.