From 808d723e18c8eebe385d3eac880c406c71ccc7dd Mon Sep 17 00:00:00 2001 From: nkuehnel Date: Mon, 25 Nov 2024 13:15:13 +0100 Subject: [PATCH] add min cost flow test plus option of setting target sensitivity alpha and beta per zone --- .../MinCostFlowRebalancingStrategy.java | 30 +- .../MinCostFlowRebalancingStrategyParams.java | 13 + ...eviousIterationDrtDemandEstimatorTest.java | 4 +- .../MinCostFlowRebalancingStrategyTest.java | 279 ++++++++++++++++++ 4 files changed, 320 insertions(+), 6 deletions(-) create mode 100644 contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/rebalancing/mincostflow/MinCostFlowRebalancingStrategyTest.java diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/rebalancing/mincostflow/MinCostFlowRebalancingStrategy.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/rebalancing/mincostflow/MinCostFlowRebalancingStrategy.java index 4e06666ed05..f3892019345 100644 --- a/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/rebalancing/mincostflow/MinCostFlowRebalancingStrategy.java +++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/rebalancing/mincostflow/MinCostFlowRebalancingStrategy.java @@ -40,6 +40,9 @@ */ public class MinCostFlowRebalancingStrategy implements RebalancingStrategy { + public static final String REBALANCING_ZONAL_TARGET_ALPHA = "rebalalpha"; + public static final String REBALANCING_ZONAL_TARGET_BETA = "rebalbeta"; + private final RebalancingTargetCalculator rebalancingTargetCalculator; private final ZoneSystem zonalSystem; private final Fleet fleet; @@ -68,18 +71,37 @@ public List calcRelocations(Stream rebalancab return calculateMinCostRelocations(time, rebalancableVehiclesPerZone, soonIdleVehiclesPerZone); } - private List calculateMinCostRelocations(double time, + List calculateMinCostRelocations(double time, Map> rebalancableVehiclesPerZone, Map> soonIdleVehiclesPerZone) { ToDoubleFunction targetFunction = rebalancingTargetCalculator.calculate(time, rebalancableVehiclesPerZone); var minCostFlowRebalancingStrategyParams = (MinCostFlowRebalancingStrategyParams)params.getRebalancingStrategyParams(); - double alpha = minCostFlowRebalancingStrategyParams.targetAlpha; - double beta = minCostFlowRebalancingStrategyParams.targetBeta; - List vehicleSurpluses = zonalSystem.getZones().values().stream().map(z -> { + List vehicleSurpluses = zonalSystem.getZones().values().stream().map(z -> { + double alpha; + double beta; int rebalancable = rebalancableVehiclesPerZone.getOrDefault(z, List.of()).size(); int soonIdle = soonIdleVehiclesPerZone.getOrDefault(z, List.of()).size(); + + switch (minCostFlowRebalancingStrategyParams.targetCoefficientSource) { + case Static -> { + alpha = minCostFlowRebalancingStrategyParams.targetAlpha; + beta = minCostFlowRebalancingStrategyParams.targetBeta; + } + case FromZoneAttribute -> { + alpha = (Double) z.getAttributes().getAttribute(REBALANCING_ZONAL_TARGET_ALPHA); + beta = (Double) z.getAttributes().getAttribute(REBALANCING_ZONAL_TARGET_BETA); + } + case FromZoneAttributeOrStatic -> { + Object alphaAttribute = z.getAttributes().getAttribute(REBALANCING_ZONAL_TARGET_ALPHA); + alpha = alphaAttribute == null ? minCostFlowRebalancingStrategyParams.targetAlpha : (Double) alphaAttribute; + Object betaAttribute = z.getAttributes().getAttribute(REBALANCING_ZONAL_TARGET_BETA); + beta = betaAttribute == null ? minCostFlowRebalancingStrategyParams.targetBeta : (Double) betaAttribute; + } + default -> throw new IllegalStateException("Unknown target coefficient source " + minCostFlowRebalancingStrategyParams.targetCoefficientSource); + } + int target = (int)Math.floor(alpha * targetFunction.applyAsDouble(z) + beta); int surplus = Math.min(rebalancable + soonIdle - target, rebalancable); return new DrtZoneVehicleSurplus(z, surplus); diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/rebalancing/mincostflow/MinCostFlowRebalancingStrategyParams.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/rebalancing/mincostflow/MinCostFlowRebalancingStrategyParams.java index 7c0a72e3a07..a09867d4785 100644 --- a/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/rebalancing/mincostflow/MinCostFlowRebalancingStrategyParams.java +++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/rebalancing/mincostflow/MinCostFlowRebalancingStrategyParams.java @@ -56,6 +56,19 @@ public enum RebalancingTargetCalculatorType { @NotNull public RebalancingTargetCalculatorType rebalancingTargetCalculatorType = RebalancingTargetCalculatorType.EstimatedDemand; + + public enum TargetCoefficientSource { + Static, FromZoneAttribute, FromZoneAttributeOrStatic + } + + @Parameter + @Comment("Defines whether the alpha and beta of the target function should be" + + " [Static] or [FromZoneAttribute] in which case alpha and beta can be provided per zone as an attribute." + + " [FromZoneAttributeOrStatic] will fall back to the static coefficients if no attribute is found for a given zone." + + " Use " + MinCostFlowRebalancingStrategy.REBALANCING_ZONAL_TARGET_ALPHA + " and " + MinCostFlowRebalancingStrategy.REBALANCING_ZONAL_TARGET_BETA + + " to set values accordingly.") + public TargetCoefficientSource targetCoefficientSource = TargetCoefficientSource.Static; + public enum ZonalDemandEstimatorType {PreviousIterationDemand, None} @Parameter diff --git a/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/rebalancing/demandestimator/PreviousIterationDrtDemandEstimatorTest.java b/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/rebalancing/demandestimator/PreviousIterationDrtDemandEstimatorTest.java index 7f43fd42d8b..cca630fcf0c 100644 --- a/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/rebalancing/demandestimator/PreviousIterationDrtDemandEstimatorTest.java +++ b/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/rebalancing/demandestimator/PreviousIterationDrtDemandEstimatorTest.java @@ -199,9 +199,9 @@ static Network createNetwork() { network.addNode(b); Link ab = network.getFactory().createLink(Id.createLinkId("link_1"), a, b); - Link bc = network.getFactory().createLink(Id.createLinkId("link_2"), b, a); + Link ba = network.getFactory().createLink(Id.createLinkId("link_2"), b, a); network.addLink(ab); - network.addLink(bc); + network.addLink(ba); return network; } } diff --git a/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/rebalancing/mincostflow/MinCostFlowRebalancingStrategyTest.java b/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/rebalancing/mincostflow/MinCostFlowRebalancingStrategyTest.java new file mode 100644 index 00000000000..8aba7eefc00 --- /dev/null +++ b/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/rebalancing/mincostflow/MinCostFlowRebalancingStrategyTest.java @@ -0,0 +1,279 @@ +package org.matsim.contrib.drt.optimizer.rebalancing.mincostflow; + +import com.google.common.collect.ImmutableMap; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; +import org.locationtech.jts.geom.prep.PreparedPolygon; +import org.matsim.api.core.v01.Coord; +import org.matsim.api.core.v01.Id; +import org.matsim.api.core.v01.TransportMode; +import org.matsim.api.core.v01.events.PersonDepartureEvent; +import org.matsim.api.core.v01.network.Link; +import org.matsim.api.core.v01.network.Network; +import org.matsim.api.core.v01.network.Node; +import org.matsim.contrib.common.zones.Zone; +import org.matsim.contrib.common.zones.ZoneImpl; +import org.matsim.contrib.common.zones.ZoneSystem; +import org.matsim.contrib.common.zones.ZoneSystemImpl; +import org.matsim.contrib.drt.analysis.zonal.MostCentralDrtZoneTargetLinkSelector; +import org.matsim.contrib.drt.optimizer.rebalancing.RebalancingParams; +import org.matsim.contrib.drt.optimizer.rebalancing.RebalancingStrategy; +import org.matsim.contrib.drt.optimizer.rebalancing.demandestimator.PreviousIterationDrtDemandEstimator; +import org.matsim.contrib.drt.optimizer.rebalancing.targetcalculator.DemandEstimatorAsTargetCalculator; +import org.matsim.contrib.drt.run.DrtConfigGroup; +import org.matsim.contrib.dvrp.fleet.*; +import org.matsim.core.network.NetworkUtils; +import org.matsim.core.utils.geometry.GeometryUtils; + +import java.util.*; + +public class MinCostFlowRebalancingStrategyTest { + + private static final int ESTIMATION_PERIOD = 1800; + + private final Network network = createNetwork(); + + private final Link link1 = network.getLinks().get(Id.createLinkId("link_1")); + private final Link link2 = network.getLinks().get(Id.createLinkId("link_2")); + + private final Zone zone1 = new ZoneImpl( + Id.create("zone_1", Zone.class), + new PreparedPolygon(GeometryUtils.createGeotoolsPolygon( + List.of( + new Coord(0, 0), + new Coord(0, 500), + new Coord(500, 500), + new Coord(500, 0), + new Coord(0, 0) + ))), "dummy"); + + private final Zone zone2 = new ZoneImpl( + Id.create("zone_2", Zone.class), + new PreparedPolygon(GeometryUtils.createGeotoolsPolygon( + List.of( + new Coord(500, 0), + new Coord(500, 500), + new Coord(1000, 500), + new Coord(1000, 0), + new Coord(500, 0) + ))), "dummy"); + + private final ZoneSystem zonalSystem = new ZoneSystemImpl(List.of(zone1, zone2), coord -> { + if (coord == link1.getToNode().getCoord()) { + return Optional.of(zone1); + } else if (coord == link2.getToNode().getCoord()) { + return Optional.of(zone2); + } else { + throw new RuntimeException(); + } + }, network); + + + @Test + void testEmptyDemandAndTarget() { + PreviousIterationDrtDemandEstimator estimator = createEstimator(); + DemandEstimatorAsTargetCalculator targetCalculator = new DemandEstimatorAsTargetCalculator(estimator, ESTIMATION_PERIOD); + + RebalancingParams rebalancingParams = new RebalancingParams(); + MinCostFlowRebalancingStrategyParams minCostFlowRebalancingStrategyParams = new MinCostFlowRebalancingStrategyParams(); + minCostFlowRebalancingStrategyParams.targetAlpha = 1.; + minCostFlowRebalancingStrategyParams.targetBeta = 0.; + rebalancingParams.addParameterSet(minCostFlowRebalancingStrategyParams); + + AggregatedMinCostRelocationCalculator relocationCalculator = new AggregatedMinCostRelocationCalculator(new MostCentralDrtZoneTargetLinkSelector(zonalSystem)); + MinCostFlowRebalancingStrategy strategy = new MinCostFlowRebalancingStrategy(targetCalculator, + zonalSystem, createEmptyFleet(), relocationCalculator, rebalancingParams); + + Map> rebalanceableVehicles = new HashMap<>(); + List relocations = strategy.calculateMinCostRelocations(0, rebalanceableVehicles, Collections.emptyMap()); + Assertions.assertThat(relocations.isEmpty()); + } + + @Test + void testDemandWithoutSurplus() { + PreviousIterationDrtDemandEstimator estimator = createEstimator(); + DemandEstimatorAsTargetCalculator targetCalculator = new DemandEstimatorAsTargetCalculator(estimator, ESTIMATION_PERIOD); + + RebalancingParams rebalancingParams = new RebalancingParams(); + MinCostFlowRebalancingStrategyParams minCostFlowRebalancingStrategyParams = new MinCostFlowRebalancingStrategyParams(); + minCostFlowRebalancingStrategyParams.targetAlpha = 1.; + minCostFlowRebalancingStrategyParams.targetBeta = 0.; + rebalancingParams.addParameterSet(minCostFlowRebalancingStrategyParams); + + AggregatedMinCostRelocationCalculator relocationCalculator = new AggregatedMinCostRelocationCalculator(new MostCentralDrtZoneTargetLinkSelector(zonalSystem)); + MinCostFlowRebalancingStrategy strategy = new MinCostFlowRebalancingStrategy(targetCalculator, + zonalSystem, createEmptyFleet(), relocationCalculator, rebalancingParams); + + //time bin 0-1800 + estimator.handleEvent(departureEvent(100, link1, TransportMode.drt)); + estimator.handleEvent(departureEvent(200, link1, TransportMode.drt)); + estimator.handleEvent(departureEvent(500, link2, TransportMode.drt)); + estimator.handleEvent(departureEvent(1500, link1, TransportMode.drt)); + estimator.reset(1); + + Map> rebalanceableVehicles = new HashMap<>(); + List relocations = strategy.calculateMinCostRelocations(0, rebalanceableVehicles, Collections.emptyMap()); + Assertions.assertThat(relocations.isEmpty()); + } + + @Test + void testDemandWithSurplus() { + PreviousIterationDrtDemandEstimator estimator = createEstimator(); + DemandEstimatorAsTargetCalculator targetCalculator = new DemandEstimatorAsTargetCalculator(estimator, ESTIMATION_PERIOD); + + RebalancingParams rebalancingParams = new RebalancingParams(); + MinCostFlowRebalancingStrategyParams minCostFlowRebalancingStrategyParams = new MinCostFlowRebalancingStrategyParams(); + minCostFlowRebalancingStrategyParams.targetAlpha = 1.; + minCostFlowRebalancingStrategyParams.targetBeta = 0.; + rebalancingParams.addParameterSet(minCostFlowRebalancingStrategyParams); + + AggregatedMinCostRelocationCalculator relocationCalculator = new AggregatedMinCostRelocationCalculator(new MostCentralDrtZoneTargetLinkSelector(zonalSystem)); + MinCostFlowRebalancingStrategy strategy = new MinCostFlowRebalancingStrategy(targetCalculator, + zonalSystem, createEmptyFleet(), relocationCalculator, rebalancingParams); + + // 3 expected trips in zone 1 + estimator.handleEvent(departureEvent(100, link1, TransportMode.drt)); + estimator.handleEvent(departureEvent(200, link1, TransportMode.drt)); + estimator.handleEvent(departureEvent(300, link1, TransportMode.drt)); + // 1 expected trip in zone 2 + estimator.handleEvent(departureEvent(100, link2, TransportMode.drt)); + estimator.reset(1); + + Map> rebalanceableVehicles = new HashMap<>(); + + // 4 vehicles in zone 1 (surplus = 1) + List rebalanceableVehiclesZone1 = new ArrayList<>(); + rebalanceableVehiclesZone1.add(getDvrpVehicle(Id.create("a1", DvrpVehicle.class), link1)); + rebalanceableVehiclesZone1.add(getDvrpVehicle(Id.create("a2", DvrpVehicle.class), link1)); + rebalanceableVehiclesZone1.add(getDvrpVehicle(Id.create("a3", DvrpVehicle.class), link1)); + rebalanceableVehiclesZone1.add(getDvrpVehicle(Id.create("a4", DvrpVehicle.class), link1)); + rebalanceableVehicles.put(zone1, rebalanceableVehiclesZone1); + + List relocations = strategy.calculateMinCostRelocations(0, rebalanceableVehicles, Collections.emptyMap()); + Assertions.assertThat(relocations.size()).isEqualTo(1); + Assertions.assertThat(relocations.getFirst().link.getId()).isEqualTo(link2.getId()); + + rebalanceableVehicles.clear(); + + // 5 vehicles in zone 1 (surplus = 2) + rebalanceableVehiclesZone1.add(getDvrpVehicle(Id.create("a1", DvrpVehicle.class), link1)); + rebalanceableVehiclesZone1.add(getDvrpVehicle(Id.create("a2", DvrpVehicle.class), link1)); + rebalanceableVehiclesZone1.add(getDvrpVehicle(Id.create("a3", DvrpVehicle.class), link1)); + rebalanceableVehiclesZone1.add(getDvrpVehicle(Id.create("a4", DvrpVehicle.class), link1)); + rebalanceableVehiclesZone1.add(getDvrpVehicle(Id.create("a5", DvrpVehicle.class), link1)); + rebalanceableVehicles.put(zone1, rebalanceableVehiclesZone1); + + //set alpha to 2 -> send two vehicles to zone 2 + minCostFlowRebalancingStrategyParams.targetAlpha = 2.; + List relocations2 = strategy.calculateMinCostRelocations(0, rebalanceableVehicles, Collections.emptyMap()); + Assertions.assertThat(relocations2.size()).isEqualTo(2); + Assertions.assertThat(relocations2.getFirst().link.getId()).isEqualTo(link2.getId()); + Assertions.assertThat(relocations2.getLast().link.getId()).isEqualTo(link2.getId()); + } + + @Test + void testDemandWithSurplusZoneBasedTargetRates() { + + // set attributes + zone1.getAttributes().putAttribute(MinCostFlowRebalancingStrategy.REBALANCING_ZONAL_TARGET_ALPHA, 0.); + zone1.getAttributes().putAttribute(MinCostFlowRebalancingStrategy.REBALANCING_ZONAL_TARGET_BETA, 0.); + zone2.getAttributes().putAttribute(MinCostFlowRebalancingStrategy.REBALANCING_ZONAL_TARGET_ALPHA, 1.); + zone2.getAttributes().putAttribute(MinCostFlowRebalancingStrategy.REBALANCING_ZONAL_TARGET_BETA, 0.); + + + PreviousIterationDrtDemandEstimator estimator = createEstimator(); + DemandEstimatorAsTargetCalculator targetCalculator = new DemandEstimatorAsTargetCalculator(estimator, ESTIMATION_PERIOD); + + RebalancingParams rebalancingParams = new RebalancingParams(); + MinCostFlowRebalancingStrategyParams minCostFlowRebalancingStrategyParams = new MinCostFlowRebalancingStrategyParams(); + minCostFlowRebalancingStrategyParams.targetCoefficientSource = MinCostFlowRebalancingStrategyParams.TargetCoefficientSource.FromZoneAttribute; + rebalancingParams.addParameterSet(minCostFlowRebalancingStrategyParams); + + AggregatedMinCostRelocationCalculator relocationCalculator = new AggregatedMinCostRelocationCalculator(new MostCentralDrtZoneTargetLinkSelector(zonalSystem)); + MinCostFlowRebalancingStrategy strategy = new MinCostFlowRebalancingStrategy(targetCalculator, + zonalSystem, createEmptyFleet(), relocationCalculator, rebalancingParams); + + // 3 expected trips in zone 1 + estimator.handleEvent(departureEvent(100, link1, TransportMode.drt)); + estimator.handleEvent(departureEvent(200, link1, TransportMode.drt)); + estimator.handleEvent(departureEvent(300, link1, TransportMode.drt)); + // 1 expected trip in zone 2 + estimator.handleEvent(departureEvent(100, link2, TransportMode.drt)); + estimator.reset(1); + + Map> rebalanceableVehicles = new HashMap<>(); + + // 4 vehicles in zone 1 (surplus = 1) + List rebalanceableVehiclesZone1 = new ArrayList<>(); + rebalanceableVehiclesZone1.add(getDvrpVehicle(Id.create("a1", DvrpVehicle.class), link1)); + rebalanceableVehiclesZone1.add(getDvrpVehicle(Id.create("a2", DvrpVehicle.class), link1)); + rebalanceableVehiclesZone1.add(getDvrpVehicle(Id.create("a3", DvrpVehicle.class), link1)); + rebalanceableVehiclesZone1.add(getDvrpVehicle(Id.create("a4", DvrpVehicle.class), link1)); + rebalanceableVehicles.put(zone1, rebalanceableVehiclesZone1); + + List relocations = strategy.calculateMinCostRelocations(0, rebalanceableVehicles, Collections.emptyMap()); + Assertions.assertThat(relocations.size()).isEqualTo(1); + Assertions.assertThat(relocations.getFirst().link.getId()).isEqualTo(link2.getId()); + + rebalanceableVehicles.clear(); + + // 5 vehicles in zone 1 (surplus = 2) + rebalanceableVehiclesZone1.add(getDvrpVehicle(Id.create("a1", DvrpVehicle.class), link1)); + rebalanceableVehiclesZone1.add(getDvrpVehicle(Id.create("a2", DvrpVehicle.class), link1)); + rebalanceableVehiclesZone1.add(getDvrpVehicle(Id.create("a3", DvrpVehicle.class), link1)); + rebalanceableVehiclesZone1.add(getDvrpVehicle(Id.create("a4", DvrpVehicle.class), link1)); + rebalanceableVehiclesZone1.add(getDvrpVehicle(Id.create("a5", DvrpVehicle.class), link1)); + rebalanceableVehicles.put(zone1, rebalanceableVehiclesZone1); + + //set alpha to 2 -> send two vehicles to zone 2 + zone2.getAttributes().putAttribute(MinCostFlowRebalancingStrategy.REBALANCING_ZONAL_TARGET_ALPHA, 2.); + List relocations2 = strategy.calculateMinCostRelocations(0, rebalanceableVehicles, Collections.emptyMap()); + Assertions.assertThat(relocations2.size()).isEqualTo(2); + Assertions.assertThat(relocations2.getFirst().link.getId()).isEqualTo(link2.getId()); + Assertions.assertThat(relocations2.getLast().link.getId()).isEqualTo(link2.getId()); + } + + private DvrpVehicleImpl getDvrpVehicle(Id id, Link link) { + return new DvrpVehicleImpl( + ImmutableDvrpVehicleSpecification.newBuilder() + .id(id) + .capacity(0) + .serviceBeginTime(0) + .serviceEndTime(0) + .startLinkId(link.getId()) + .build(), link); + } + + private static Fleet createEmptyFleet() { + return () -> ImmutableMap., DvrpVehicle>builder().build(); + } + + + private PreviousIterationDrtDemandEstimator createEstimator() { + RebalancingParams rebalancingParams = new RebalancingParams(); + rebalancingParams.interval = ESTIMATION_PERIOD; + + DrtConfigGroup drtConfigGroup = new DrtConfigGroup(); + drtConfigGroup.addParameterSet(rebalancingParams); + + return new PreviousIterationDrtDemandEstimator(zonalSystem, drtConfigGroup, ESTIMATION_PERIOD); + } + + private PersonDepartureEvent departureEvent(double time, Link link, String mode) { + return new PersonDepartureEvent(time, null, link.getId(), mode, mode); + } + + static Network createNetwork() { + Network network = NetworkUtils.createNetwork(); + Node a = network.getFactory().createNode(Id.createNodeId("a"), new Coord(0,0)); + Node b = network.getFactory().createNode(Id.createNodeId("b"), new Coord(500,0)); + network.addNode(a); + network.addNode(b); + + Link ab = network.getFactory().createLink(Id.createLinkId("link_1"), a, b); + Link ba = network.getFactory().createLink(Id.createLinkId("link_2"), b, a); + network.addLink(ab); + network.addLink(ba); + return network; + } +}