/* amodeus - Copyright (c) 2018, ETH Zurich, Institute for Dynamic Systems and Control */ package amodeus.amodeus.matsim; import java.io.File; import java.io.IOException; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.Random; import org.junit.AfterClass; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameters; import org.matsim.amodeus.AmodeusConfigurator; import org.matsim.amodeus.config.AmodeusConfigGroup; import org.matsim.amodeus.config.AmodeusModeConfig; import org.matsim.amodeus.config.modal.DispatcherConfig; import org.matsim.amodeus.config.modal.GeneratorConfig; import org.matsim.amodeus.framework.AmodeusQSimModule; import org.matsim.amodeus.scenario.TestScenarioAnalyzer; import org.matsim.amodeus.scenario.TestScenarioGenerator; import org.matsim.api.core.v01.Id; import org.matsim.api.core.v01.Scenario; import org.matsim.api.core.v01.events.LinkEnterEvent; import org.matsim.api.core.v01.events.handler.LinkEnterEventHandler; import org.matsim.api.core.v01.network.Link; import org.matsim.api.core.v01.network.Network; import org.matsim.api.core.v01.network.NetworkFactory; import org.matsim.api.core.v01.network.Node; import org.matsim.api.core.v01.population.Activity; import org.matsim.api.core.v01.population.Person; import org.matsim.api.core.v01.population.Plan; import org.matsim.api.core.v01.population.PlanElement; import org.matsim.api.core.v01.population.Population; import org.matsim.contrib.dvrp.run.DvrpConfigGroup; import org.matsim.contrib.dvrp.run.DvrpModes; import org.matsim.contrib.dvrp.run.ModalProviders; import org.matsim.core.config.Config; import org.matsim.core.config.ConfigUtils; import org.matsim.core.config.groups.PlanCalcScoreConfigGroup; import org.matsim.core.controler.AbstractModule; import org.matsim.core.controler.Controler; import org.matsim.core.gbl.MatsimRandom; import org.matsim.core.network.NetworkUtils; import org.matsim.core.network.algorithms.TransportModeNetworkFilter; import com.google.common.collect.ImmutableSet; import com.google.inject.TypeLiteral; import amodeus.amodeus.data.LocationSpec; import amodeus.amodeus.data.ReferenceFrame; import amodeus.amodeus.net.MatsimAmodeusDatabase; import amodeus.amodeus.options.LPOptions; import amodeus.amodeus.options.LPOptionsBase; import amodeus.amodeus.options.ScenarioOptions; import amodeus.amodeus.options.ScenarioOptionsBase; import amodeus.amodeus.parking.AmodeusParkingModule; import amodeus.amodeus.parking.ParkingCapacityGenerators; import amodeus.amodeus.parking.strategies.ParkingStrategies; import amodeus.amodeus.prep.MatsimKMeansVirtualNetworkCreator; import amodeus.amodeus.test.TestFileHandling; import amodeus.amodeus.traveldata.StaticTravelDataCreator; import amodeus.amodeus.traveldata.TravelData; import amodeus.amodeus.util.io.Locate; import amodeus.amodeus.util.io.MultiFileTools; import amodeus.amodeus.util.math.GlobalAssert; import amodeus.amodeus.virtualnetwork.core.VirtualNetwork; @RunWith(Parameterized.class) public class StandardMATSimScenarioTest { @Parameters(name = "{0}") public static Collection<Object[]> data() { // SingleHeuristic is added as a reference case, to check that the av package is // working properly // ATTENTION: DriveByDispatcher is not tested, because of long runtime. return Arrays.asList(new Object[][] { // { "SingleHeuristic" }, // { "DemandSupplyBalancingDispatcher" }, // { "GlobalBipartiteMatchingDispatcher" }, // { "FeedforwardFluidicRebalancingPolicy" }, // { "AdaptiveRealTimeRebalancingPolicy" }, // { "ExtDemandSupplyBeamSharing" }, // { "TShareDispatcher" }, // { "FirstComeFirstServedStrategy" }, // { "DynamicRideSharingStrategy" }, // { "RestrictedLinkCapacityDispatcher" }, // { "ModelFreeAdaptiveRepositioning" }, // { "DFRStrategy" }, // { "NoExplicitCommunication" }, // { "SBNoExplicitCommunication" }, // // This one doesn't finish all requests. Bug or not enough of time? Also it's not good in an automated unit test because it // produces large amounts of log output. // { "HighCapacityDispatcher" }, // Also has not enough of time to finish all requests // { "NorthPoleSharedDispatcher" }, }); } final private String dispatcher; public StandardMATSimScenarioTest(String dispatcher) { this.dispatcher = dispatcher; } private static void makeMultimodal(Scenario scenario) { // Add pt-links to the network to test a multimodal network as it appears in standard MATSim use cases Network network = scenario.getNetwork(); NetworkFactory factory = network.getFactory(); // Let's build a fast track through the scenario for (int i = 0; i < 9; i++) { Id<Link> ptFowardLinkId = Id.createLinkId(String.format("pt_fwd_%d:%d", i, i)); Id<Link> ptBackwardLinkId = Id.createLinkId(String.format("pt_bck_%d:%d", i, i)); Id<Node> fromNodeId = Id.createNodeId(String.format("%d:%d", i, i)); Id<Node> toNodeId = Id.createNodeId(String.format("%d:%d", i + 1, i + 1)); Link ptFowardLink = factory.createLink(ptFowardLinkId, network.getNodes().get(fromNodeId), network.getNodes().get(toNodeId)); ptFowardLink.setFreespeed(100.0 * 1000.0 / 3600.0); ptFowardLink.setLength(1000.0); ptFowardLink.setAllowedModes(Collections.singleton("pt")); network.addLink(ptFowardLink); Link ptBackwardLink = factory.createLink(ptBackwardLinkId, network.getNodes().get(toNodeId), network.getNodes().get(fromNodeId)); ptBackwardLink.setFreespeed(100.0 * 1000.0 / 3600.0); ptBackwardLink.setLength(1000.0); ptBackwardLink.setAllowedModes(Collections.singleton("pt")); network.addLink(ptBackwardLink); } // Also, a routed population may have "pt interaction" activities, which take place at links that are not part of the road network. Amodeus must be able // to // handle these cases. /* for (Person person : scenario.getPopulation().getPersons().values()) * for (Plan plan : person.getPlans()) { * Activity trickyActivity = PopulationUtils.createActivityFromCoordAndLinkId("pt interaction", new Coord(5500.0, 5500.0), Id.createLinkId("pt_fwd_5:5")); * * plan.getPlanElements().add(PopulationUtils.createLeg("walk")); * plan.getPlanElements().add(trickyActivity); * } */ // TODO @sebhoerl Difficult to keep this in as handling of "interaction" activities become much smarter in MATSim now. We would need to // set up a much more realistic test scenario. There is one in the AV package, so we can use that one! for (Link link : network.getLinks().values()) { if (link.getAllowedModes().contains("car")) { link.setAllowedModes(new HashSet<>(Arrays.asList("car", AmodeusModeConfig.DEFAULT_MODE))); } } } private static void fixInvalidActivityLocations(Network network, Population population) { // In the test fixture there are agents who start and end activities on non-car links. This should not be happen and is fixed here. Network roadNetwork = NetworkUtils.createNetwork(); new TransportModeNetworkFilter(network).filter(roadNetwork, Collections.singleton("car")); for (Person person : population.getPersons().values()) for (Plan plan : person.getPlans()) for (PlanElement element : plan.getPlanElements()) if (element instanceof Activity) { Activity activity = (Activity) element; Link link = network.getLinks().get(activity.getLinkId()); if (!link.getAllowedModes().contains("car")) { link = NetworkUtils.getNearestLink(roadNetwork, link.getCoord()); activity.setLinkId(link.getId()); } } } @BeforeClass public static void setUp() throws IOException { // copy scenario data into main directory File scenarioDirectory = new File(Locate.repoFolder(StandardMATSimScenarioTest.class, "amodeus"), "resources/testScenario"); File workingDirectory = MultiFileTools.getDefaultWorkingDirectory(); GlobalAssert.that(workingDirectory.isDirectory()); TestFileHandling.copyScnearioToMainDirectory(scenarioDirectory.getAbsolutePath(), workingDirectory.getAbsolutePath()); } @Test public void testStandardMATSimScenario() throws IOException { /* This test runs a small test scenario with the different dispatchers and makes * sure that all 100 generated agents arrive */ StaticHelper.setup(); MatsimRandom.reset(); // Set up Config config = ConfigUtils.createConfig(new AmodeusConfigGroup(), new DvrpConfigGroup()); Scenario scenario = TestScenarioGenerator.generateWithAVLegs(config); File workingDirectory = MultiFileTools.getDefaultWorkingDirectory(); ScenarioOptions simOptions = new ScenarioOptions(workingDirectory, ScenarioOptionsBase.getDefault()); LocationSpec locationSpec = simOptions.getLocationSpec(); ReferenceFrame referenceFrame = locationSpec.referenceFrame(); MatsimAmodeusDatabase db = MatsimAmodeusDatabase.initialize(scenario.getNetwork(), referenceFrame); PlanCalcScoreConfigGroup.ModeParams modeParams = config.planCalcScore().getOrCreateModeParams(AmodeusModeConfig.DEFAULT_MODE); modeParams.setMonetaryDistanceRate(0.0); modeParams.setMarginalUtilityOfTraveling(8.86); modeParams.setConstant(0.0); int i = new Random().nextInt(ParkingStrategies.values().length); simOptions.setProperty("parkingStrategy", ParkingStrategies.values()[i].name()); int j = new Random().nextInt(ParkingStrategies.values().length); simOptions.setProperty("parkingCapacityGenerator", ParkingCapacityGenerators.values()[j].name()); Controler controller = new Controler(scenario); AmodeusConfigurator.configureController(controller, db, simOptions); controller.addOverridingModule(new AmodeusParkingModule(simOptions, new Random())); // Make the scenario multimodal fixInvalidActivityLocations(scenario.getNetwork(), scenario.getPopulation()); makeMultimodal(scenario); // Config AmodeusConfigGroup avConfig = AmodeusConfigGroup.get(config); AmodeusModeConfig operatorConfig = new AmodeusModeConfig(AmodeusModeConfig.DEFAULT_MODE); operatorConfig.setUseModeFilteredSubnetwork(true); DvrpConfigGroup.get(config).setNetworkModes(ImmutableSet.of(AmodeusModeConfig.DEFAULT_MODE)); avConfig.addMode(operatorConfig); GeneratorConfig generatorConfig = operatorConfig.getGeneratorConfig(); generatorConfig.setType("VehicleToVSGenerator"); generatorConfig.setNumberOfVehicles(50); int endTime = (int) config.qsim().getEndTime().seconds(); // Choose a dispatcher DispatcherConfig dispatcherConfig = operatorConfig.getDispatcherConfig(); dispatcherConfig.addParam("DFR", "true"); dispatcherConfig.addParam("infoLinePeriod", "3600"); dispatcherConfig.setType(dispatcher); // Make sure that we do not need the SimulationObjectCompiler dispatcherConfig.addParam("publishPeriod", "-1"); // Set up a virtual network for the LPFBDispatcher controller.addOverridingModule(new AbstractModule() { @Override public void install() { // TODO: This is not modalized now! bind(DvrpModes.key(new TypeLiteral<VirtualNetwork<Link>>() { }, AmodeusModeConfig.DEFAULT_MODE)).toProvider(ModalProviders.createProvider(AmodeusModeConfig.DEFAULT_MODE, getter -> { Network network = getter.getModal(Network.class); return MatsimKMeansVirtualNetworkCreator.createVirtualNetwork(scenario.getPopulation(), network, 2, true); })); bind(DvrpModes.key(new TypeLiteral<TravelData>() { }, AmodeusModeConfig.DEFAULT_MODE)).toProvider(ModalProviders.createProvider(AmodeusModeConfig.DEFAULT_MODE, getter -> { try { LPOptions lpOptions = new LPOptions(simOptions.getWorkingDirectory(), LPOptionsBase.getDefault()); lpOptions.setProperty(LPOptionsBase.LPSOLVER, "timeInvariant"); lpOptions.saveAndOverwriteLPOptions(); VirtualNetwork<Link> virtualNetwork = getter.getModal(new TypeLiteral<VirtualNetwork<Link>>() { }); Network network = getter.getModal(Network.class); Population population = getter.get(Population.class); return StaticTravelDataCreator.create(simOptions.getWorkingDirectory(), virtualNetwork, network, population, simOptions.getdtTravelData(), generatorConfig.getNumberOfVehicles(), endTime); } catch (Exception e) { throw new RuntimeException(e); } })); } }); // Set up test analyzer and run TestScenarioAnalyzer analyzer = new TestScenarioAnalyzer(); controller.addOverridingModule(analyzer); controller.addOverridingModule(new AbstractModule() { @Override public void install() { addEventHandlerBinding().toInstance(new LinkEnterEventHandler() { @Override public void handleEvent(LinkEnterEvent event) { // Fail if an AV attempts to enter a pt link if (event.getVehicleId().toString().startsWith("amodeus") && event.getLinkId().toString().startsWith("pt")) { Assert.fail("AV attempted to enter PT link"); } } }); } }); controller.configureQSimComponents(AmodeusQSimModule.activateModes(avConfig)); controller.run(); if (analyzer.numberOfDepartures != analyzer.numberOfArrivals) { System.out.println("numberOfDepartures=" + analyzer.numberOfDepartures); System.out.println("numberOfArrivals =" + analyzer.numberOfArrivals); } Assert.assertEquals(analyzer.numberOfDepartures, analyzer.numberOfArrivals); } @AfterClass public static void tearDownOnce() throws IOException { TestFileHandling.removeGeneratedFiles(); } }